쇼핑몰 8 - [배송지 관리]

2025. 1. 24. 12:39·Java/쇼핑몰
목차
  1. ✅ 이전 포스팅
  2. ✅ 컨트롤러
  3. ✅ 서비스
  4. ✅ DTO
  5. 🟨 AddressListResponseDTO
  6. 🟨 AddressSaveRequestDTO
  7. 🟨 AddressUpdateRequestDTO
  8. ✅ Repository
  9. ✅ 마무리
728x90

✅ 이전 포스팅

2024.12.13 - [Java/쇼핑몰] - 쇼핑몰 7 - [이메일로 비밀번호 변경 URL 전송]

 

쇼핑몰 7 - [이메일로 비밀번호 변경 URL 전송]

✅ 이전 포스팅2024.12.02 - [Java/쇼핑몰] - 쇼핑몰 - 6 [Spring Security + JWT + OAuth2 + Redis] 쇼핑몰 - 6 [Spring Security + JWT + OAuth2 + Redis]✅ 이전 포스팅2024.11.19 - [Java/쇼핑몰] - 쇼핑몰 - 5 [spring security + JWT + Red

magicmk.tistory.com

 

이전 포스팅을 올리고 한 달이 넘는 시간이 지났다.

그동안 골프, 웹툰 등에 빠져서 책도 많이 읽지 않고 나태한 나날을 보냈다..

쇼핑몰 개발은 거의 끝나가는데 블로그는 올리지도 않고 반성해야겠다. ㅠㅠ


상품을 주문하기 위해서는 회원의 배송지를 설정할 수 있어야 하기 때문에 이번 포스팅에서는

배송지 로직을 작성해보도록 하겠다.

 

✅ 컨트롤러

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/address")
public class AddressController implements AddressControllerDocs{

    private final AddressService addressService;

    @GetMapping
    public ResponseEntity<Response<?>> addressList(Authentication authentication) {
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        List<AddressListResponseDTO> result = addressService.addressList(userDetails);

        return buildResponse(HttpStatus.OK, "배송지 목록 조회 성공", result);
    }

    @PostMapping
    public ResponseEntity<Response<?>> saveAddress(@RequestBody @Valid AddressSaveRequestDTO parameter, Authentication authentication) {
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        Long result = addressService.saveAddress(parameter, userDetails);

        return buildResponse(HttpStatus.CREATED, "배송지 등록 성공", result);
    }

    @PutMapping("/{addressId}")
    public ResponseEntity<Response<?>> updateAddress(@PathVariable("addressId") Long addressId, @RequestBody @Valid AddressUpdateRequestDTO parameter, Authentication authentication) {
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        Long result = addressService.updateAddress(addressId, parameter, userDetails);

        return buildResponse(HttpStatus.OK, "배송지 수정 성공", result);
    }

    @DeleteMapping("/{addressId}")
    public ResponseEntity<Response<?>> deleteAddress(@PathVariable("addressId") Long addressId, Authentication authentication) {
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        addressService.deleteAddress(addressId, userDetails);

        return buildResponse(HttpStatus.OK, "배송지 삭제 성공", null);
    }

    @PostMapping("/defaultAddress/{addressId}")
    public ResponseEntity<Response<?>> updateDefaultAddress(@PathVariable("addressId") Long addressId, Authentication authentication) {
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        Long result = addressService.chooseDefaultAddress(addressId, userDetails);

        return buildResponse(HttpStatus.OK, "기본 배송지 설정 성공", result);
    }
}

✅ 서비스

@Service
@RequiredArgsConstructor
public class AddressService {

    private final MemberRepository memberRepository;
    private final AddressRepository addressRepository;

    /**
     * 사용자의 모든 주소 정보를 조회하여 AddressListResponseDTO 리스트로 반환합니다.
     *
     * @param userDetails 현재 로그인된 사용자의 정보를 담고 있는 객체
     * @return 사용자의 주소 정보를 포함하는 AddressListResponseDTO 리스트
     */
    public List<AddressListResponseDTO> addressList(UserDetailsImpl userDetails) {
        Member member = getMember(userDetails.getUserId());
        List<AddressListResponseDTO> DTOList = new ArrayList<>();

        addressRepository.findAllByMember(member).forEach(address -> {
            AddressListResponseDTO dto = AddressListResponseDTO.builder()
                    .id(address.getId())
                    .name(address.getName())
                    .addr(address.getAddr())
                    .addrDetail(address.getAddrDetail())
                    .addrNickName(address.getAddrName())
                    .phone(address.getPhone())
                    .request(address.getRequest())
                    .defaultType(address.getDefaultType())
                    .build();

            DTOList.add(dto);
        });

        return DTOList;
    }

    /**
     * 사용자의 주소 정보를 저장합니다.
     *
     * @param parameter 주소 저장 요청 데이터를 담고 있는 DTO 객체
     * @param userDetails 현재 인증된 사용자의 정보를 담고 있는 객체
     * @return 저장된 주소의 ID
     * @throws UsernameNotFoundException 사용자 정보가 존재하지 않을 경우 발생
     */
    public Long saveAddress(AddressSaveRequestDTO parameter, UserDetailsImpl userDetails) {
        Member member = memberRepository.findById(userDetails.getUserId())
                .orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 사용자입니다."));

        Address address = Address.builder()
                .name(parameter.getName())
                .addr(parameter.getAddr())
                .addrName(parameter.getAddrNickName())
                .addrDetail(parameter.getAddrDetail())
                .phone(parameter.getPhone())
                .zipCode(parameter.getZipCode())
                .request(parameter.getRequest())
                .member(member)
                .build();

        Address savedAddress = addressRepository.save(address);

        return savedAddress.getId();
    }

    /**
     * 사용자의 배송지 정보를 업데이트합니다.
     *
     * @param addressId 업데이트할 배송지의 ID
     * @param parameter 배송지 업데이트 요청 데이터를 담고 있는 DTO 객체
     * @param userDetails 현재 인증된 사용자의 정보를 담고 있는 객체
     * @return 업데이트된 배송지의 ID
     * @throws UsernameNotFoundException 사용자 정보가 존재하지 않을 경우 발생
     * @throws NotFoundException 배송지 정보가 존재하지 않을 경우 발생
     * @throws AccessDeniedException 로그인된 사용자의 배송지가 아닐 경우 발생
     */
    public Long updateAddress(Long addressId, AddressUpdateRequestDTO parameter, UserDetailsImpl userDetails) {
        Member member = getMember(userDetails.getUserId());
        Address address = getAddress(addressId);

        if (!address.getMember().getId().equals(member.getId())) {
            throw new AccessDeniedException("로그인된 회원의 배송지가 아닙니다.");
        }

        address.updateAddress(parameter);

        return address.getId();
    }

    /**
     * 사용자의 배송지 정보를 삭제합니다.
     *
     * @param addressId 삭제할 배송지의 ID
     * @param userDetails 현재 인증된 사용자의 정보를 담고 있는 객체
     * @throws AccessDeniedException 로그인된 사용자의 배송지가 아닐 경우 발생
     */
    public void deleteAddress(Long addressId, UserDetailsImpl userDetails) {
        Member member = getMember(userDetails.getUserId());
        Address address = getAddress(addressId);

        if (!address.getMember().getId().equals(member.getId())) {
            throw new AccessDeniedException("로그인된 회원의 배송지가 아닙니다.");
        }

        addressRepository.deleteById(addressId);
    }

    /**
     * 사용자의 특정 배송지를 기본 배송지로 설정합니다.
     *
     * <p>현재 로그인한 사용자의 배송지 목록 중 기존 기본 배송지를 해제하고,
     * 선택한 배송지를 기본 배송지로 설정합니다.</p>
     *
     * @param addressId 기본 배송지로 설정할 배송지의 ID
     * @param userDetails 로그인한 사용자의 정보
     * @return 기본 배송지로 설정된 배송지의 ID
     * @throws AccessDeniedException 선택한 배송지가 로그인한 사용자의 배송지가 아닌 경우
     */
    @Transactional
    public Long chooseDefaultAddress(Long addressId, UserDetailsImpl userDetails) {
        Member member = getMember(userDetails.getUserId());
        Address address = getAddress(addressId);

        if (!address.getMember().getId().equals(member.getId())) {
            throw new AccessDeniedException("로그인된 회원의 배송지가 아닙니다.");
        }

        addressRepository.resetDefaultTypeForMember(member.getId());
        address.updateDefaultType('Y');

        return address.getId();
    }

    private Member getMember(Long userId) {
        return memberRepository.findById(userId)
                .orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 사용자입니다."));
    }

    private Address getAddress(Long addressId) {
        return addressRepository.findById(addressId)
                .orElseThrow(() -> new NotFoundException("존재하지 않는 배송지입니다."));
    }
}

✅ DTO

🟨 AddressListResponseDTO

@Data
@Builder
public class AddressListResponseDTO {

    private Long id;
    private String name;
    private String addr;
    private String addrDetail;
    private String addrNickName;
    private String phone;
    private String request;
    private char defaultType;
}

 

🟨 AddressSaveRequestDTO

@Data
@Schema(description = "배송지 등록에 필요한 요청 정보", requiredProperties = {"name", "addr", "addrDetail", "phone", "zipCode"})
public class AddressSaveRequestDTO {

    @Schema(description = "받는 분 이름")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String name;

    @Schema(description = "주소")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String addr;

    @Schema(description = "배송지 별명")
    private String addrNickName;

    @Schema(description = "상세주소")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String addrDetail;

    @Schema(description = "받는 분 연락처")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String phone;

    @Schema(description = "우편번호")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String zipCode;

    @Schema(description = "요청 사항")
    private String request;
}

 

🟨 AddressUpdateRequestDTO

@Data
@Schema(description = "배송지 수정에 필요한 요청 정보", requiredProperties = {"name", "addr", "addrDetail", "phone", "zipCode"})
public class AddressUpdateRequestDTO {

    @Schema(description = "받는 분 이름")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String name;

    @Schema(description = "주소")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String addr;

    @Schema(description = "배송지 별명")
    private String addrNickName;

    @Schema(description = "상세주소")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String addrDetail;

    @Schema(description = "받는 분 연락처")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String phone;

    @Schema(description = "우편번호")
    @NotBlank(message = "해당 값은 필수값 입니다.")
    private String zipCode;

    @Schema(description = "요청 사항")
    private String request;
}

✅ Repository

public interface AddressRepository extends JpaRepository<Address, Long> {

    @Modifying
    @Query("update Address a set a.defaultType = 'N' where a.member.id = :memberId and a.defaultType = 'Y'")
    void resetDefaultTypeForMember(@Param("memberId") Long memberId);

    List<Address> findAllByMember(Member member);
}

✅ 마무리

아무래도 단순한 CRUD라서 코드를 설명할 것이 딱히 없는 것 같다.

포스팅하는 것이 간단하니 리마인드 하는 겸 자주자주 올려야겠다...

이래놓고 또 잉여롭게 살겠지...

저작자표시 비영리 (새창열림)

'Java > 쇼핑몰' 카테고리의 다른 글

쇼핑몰 7 - [이메일로 비밀번호 변경 URL 전송]  (0) 2024.12.13
쇼핑몰 - 6 [Spring Security + JWT + OAuth2 + Redis]  (2) 2024.12.02
쇼핑몰 - 5 [spring security + JWT + Redis 로그인]  (0) 2024.11.19
쇼핑몰 - 4 [회원 가입 구현]  (0) 2024.11.12
쇼핑몰 - 3 [GitHub Actions를 통한 CI/CD]  (3) 2024.11.06
  1. ✅ 이전 포스팅
  2. ✅ 컨트롤러
  3. ✅ 서비스
  4. ✅ DTO
  5. 🟨 AddressListResponseDTO
  6. 🟨 AddressSaveRequestDTO
  7. 🟨 AddressUpdateRequestDTO
  8. ✅ Repository
  9. ✅ 마무리
'Java/쇼핑몰' 카테고리의 다른 글
  • 쇼핑몰 7 - [이메일로 비밀번호 변경 URL 전송]
  • 쇼핑몰 - 6 [Spring Security + JWT + OAuth2 + Redis]
  • 쇼핑몰 - 5 [spring security + JWT + Redis 로그인]
  • 쇼핑몰 - 4 [회원 가입 구현]
요술공주밍키
요술공주밍키
조금씩이라도 꾸준히..
  • 요술공주밍키
    삽질의흔적
    요술공주밍키
  • 전체
    오늘
    어제
    • 분류 전체보기 (130)
      • Java (42)
        • Spring Boot (14)
        • Spring Boot 게시판 (14)
        • 공중화장실 찾기 (4)
        • 쇼핑몰 (8)
      • JavaScript (8)
        • NodeJS (2)
      • Python (5)
        • Django (4)
      • Server (10)
        • Docker (4)
        • K8S (0)
        • Jenkins (1)
      • 알고리즘 (22)
        • 프로그래머스 (17)
        • 백준 (5)
      • Etc (21)
        • 개발 팁 (1)
      • 일상 (21)
        • 독서 포스트 (19)
        • 회고록 (2)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
요술공주밍키
쇼핑몰 8 - [배송지 관리]

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.