회원정보 수정 로직(Controller, Service)

Jongwon·2023년 4월 1일
1

DMS

목록 보기
15/18

추후에 코드가 변경될 가능성이 높지만, 현재 제가 아는 방식 내에서 회원정보를 수정할 수 있는 로직을 만들어보도록 하겠습니다.


회원정보 수정은 앞서 프로젝트를 따라왔다면 2가지 케이스가 존재합니다.

  • Local로 가입한 경우
  • 소셜 로그인을 통해 가입한 경우

기본적으로 두 방식 모두 userId, 즉 ID값은 변경하지 못한다는 것을 전제로 시작합니다. 소셜 로그인을 통해 가입한 회원은 이메일 변경이 불가능하고, Local로 가입하였다면 인증을 통해 변경이 가능할 것입니다.



Controller

기존의 Entity는 재활용하고, Controller부터 생성하겠습니다.

✅MemberController

@Tag(name = "사용자 관련", description = "사용자 관련 API")
@RestController
@Log4j2
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final PasswordEncoder passwordEncoder;

...생략

    @Operation(summary = "회원정보 수정")
    @PostMapping("/modify")
    public ResponseEntity memberModify(@ModelAttribute MemberRequestDTO memberRequestDTO) {
        if(!SecurityUtil.getCurrentUsername().equals(memberRequestDTO.getUserId())) {
            log.warn("잘못된 회원 ID로 접근하였습니다.");
            return new ResponseEntity(HttpStatus.BAD_REQUEST);
        }
        memberRequestDTO.setPassword(passwordEncoder.encode(memberRequestDTO.getPassword()));
        memberService.updateMember(memberRequestDTO, SecurityUtil.getCurrentUsername());
        return new ResponseEntity(HttpStatus.OK);
    }
}
  • 먼저 수정 로직으로 접근할 때는 헤더에 서버에서 발행했던 JWT 토큰을 같이 보내는데, 이때 JWT 내부의 ID값과, 회원정보 수정 요청으로 온 DTO의 ID값이 다르다면 이는 오류입니다. 이 경우에는 Bad_Request응답을 보내서 거절합니다.

  • 다음으로 회원가입 시 진행했던 방식대로, 패스워드를 암호화하여 DB에 저장할 수 있도록 암호화를 진행합니다.

  • 이제 Service계층에서 updateMember 메서드를 호출하여, 회원정보를 업데이트 할 수 있도록 합니다.




Service

서비스 계층에서 큰 문제를 맞이했는데, 준영속상태의 엔티티를 DB에 save를 통해 저장하고자 하는데, 문제는 절대로 변경되지 않을 provider, social등의 값들이 Client쪽에서 오는 데이터가 아니기 때문에 DTO -> Entity 매핑을 하게 되면 null값이 저장이 된다는 것입니다. Null값이 있는 채로 DB에 Merge를 하게 되면, null로 값이 update가 되기 때문에 이를 깔끔하게 해결할 방법이 없는지 고민하고 찾아보았습니다.

결론부터 말하면 결국은 Dirty Checking이었습니다. 며칠동안 고민하고 찾아보았지만, 제 문제를 완벽히 해결해줄 별도의 기술이 없었습니다.

참고한 글 중 가장 정리가 잘 된 글의 링크입니다.
https://leegicheol.github.io/jpa/jpa-is-new/

따라서 Service계층에서 별도로 DB에 저장되어 있는 회원의 데이터 몇 부분을 다시 입력해주는 방식을 적용할 예정입니다.

✅MemberService

@Service
@Log4j2
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {

    private final MemberRepository memberRepository;
    private final MemberImageRepository imageRepository;
    private final TokenService tokenService;
    private final FileService fileService;
    private final AuthService authService;
    
...생략

	@Transactional(readOnly = false)
    public void updateMember(MemberRequestDTO memberRequestDTO, String userId) {

        Member member = memberRepository.findByUserId(userId).orElseThrow(() -> new RuntimeException("존재하지 않는 사용자입니다."));

        //중복가입  에러해결필요
        if(member.isSocial()) {
            if(!memberRequestDTO.getEmail().equals(member.getEmail())) {
                throw new RuntimeException("소셜회원은 이메일 변경이 불가합니다.");
            }
        }

        if((member.getMemberImage() != null) && (memberRequestDTO.getMemberImage() != null)) {
            imageRepository.deleteById(member.getMemberImage().getId());
        }
        MemberDTO memberDTO = MemberMapper.INSTANCE.requestDTOToMemberDTO(memberRequestDTO);
        memberDTO.setRoles(member.getRoles());
        memberDTO.setCreatedDate(member.getCreatedDate());
        memberDTO.setSocial(member.isSocial());
        memberDTO.setProvider(member.getProvider());

        if(!(memberRequestDTO.getMemberImage() == null)) {
            MemberImage memberImage = saveMemberImage(memberRequestDTO.getMemberImage());
            memberDTO.setMemberImage(memberImage);
        }

        Member updatedMember = MemberMapper.INSTANCE.memberDTOToMember(memberDTO);
        if(memberRequestDTO.getPassword() == null) {
            updatedMember.updatePassword(memberRequestDTO.getPassword());
        }
        else {
            updatedMember.updatePassword(member.getPassword());
        }

        memberRepository.save(updatedMember);
    }
}

Update, Delete, Insert 등의 여러 쿼리들이 한번에 실행되므로 @Transactional어노테이션을 지정해주어야 합니다.

  • 먼저 userId를 통해 회원 데이터를 가져옵니다. 이는 소셜 사용자인지 여부를 확인하기 위해 필요합니다.

  • 소셜 회원은 이메일 변경이 불가합니다. 변경 시도 시 거부합니다.

  • 프로필 사진이 존재하는 상태에서 새로운 프로필 사진을 입력하였다면, 기존의 사진을 삭제하고 새로운 사진을 저장합니다.

  • 회원정보 수정에서 입력을 받지않고, 기존 데이터를 유지해야하는 데이터는 아래와 같습니다.

    • roles
    • createdDate
    • social
    • provider

    따라서 이를 setter로 직접 다시 입력해줍니다.

  • 비밀번호 역시, 변경하지 않는다면 기존의 데이터를 유지해야합니다. Client에서 기존의 데이터를 다시 돌려받으면 된다고 생각하겠지만, 이 데이터는 encryption된 데이터이기 때문에 Controller에서 또 다시 암호화를 하게됩니다. 따라서 Client에서 비밀번호 값이 null이라면 DB에서, 있다면 클라이언트에서 받은 비밀번호를 DB에 저장합니다.



Repository역시 기존의 Repository를 이용하므로 생략하겠습니다.


profile
Backend Engineer

0개의 댓글