카테고리 없음

[TaskApp] 회원정보 수정하기 (E-mail, Phone)

MoveForward 2024. 6. 9. 17:00

회원 정보 수정을 구현 하도록 하자.

 

[Member Entity]

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity{

    /**
     * member_id(PK)
     * username
     * password
     * email
     * phone
     * userRole
     */

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;
    private String password;

    private String email;
    private String phone;

    @Enumerated(EnumType.STRING)
    private UserRole userRole = UserRole.USER;

    @OneToMany(mappedBy = "member")
    private List<Task> tasks = new ArrayList<>();
 }

 

 

회원 엔티티는 위와 같이 구성되어 있다. 

수정 가능한 회원 정보는 

1. password

2. email

3. phone 

이다.

 

{'password'}

{'email', 'phone'}

이렇게 2그룹으로 나누어 수정을 구현하겠다.

 

이번 글에선 {'email', 'phone'} 을 수정한다.

 

[MemberRequestDto.class]

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberRequestDto {

    public interface Create {}
    public interface UpdateEmailPhone {} //Email, Phone 수정 그룹
    public interface UpdatePassword {}
    public interface IdOnly {}


    @NotNull(groups = {UpdateEmailPhone.class, UpdatePassword.class, IdOnly.class}, message = "ID는 필수 입력 값입니다.")
    private Long id;

    @Pattern(regexp = "^[a-zA-Z0-9]{4,20}$", groups = Create.class,
            message = "아이디는 영문 대소문자, 숫자로 이루어진 4~20자리여야 합니다.")
    @NotBlank(groups = Create.class, message = "아이디는 필수 입력 값입니다.")
    private String username;

    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", groups = {Create.class, UpdatePassword.class},
            message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
    @NotBlank(groups = {Create.class, UpdatePassword.class}, message = "비밀번호는 필수 입력 값입니다.")
    private String password;

    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", groups = {Create.class, UpdatePassword.class},
            message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
    @NotBlank(groups = {Create.class, UpdatePassword.class}, message = "비밀번호는 필수 입력 값입니다.")
    private String passwordCheck;

    @Pattern(regexp = "^(?:\\w+\\.?)*\\w+@(?:\\w+\\.)+\\w+$", groups = {Create.class, UpdateEmailPhone.class},
            message = "이메일 형식이 올바르지 않습니다.")
    @NotBlank(groups = {Create.class, UpdateEmailPhone.class}, message = "이메일은 필수 입력 값입니다.")
    private String email;

    @Pattern(regexp = "^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", groups = {Create.class, UpdateEmailPhone.class},
            message = "전화번호 형식이 올바르지 않습니다.")
    @NotBlank(groups = {Create.class, UpdateEmailPhone.class}, message = "전화번호는 필수 입력 값입니다.")
    private String phone;
    
    
    
    // update Form
    /**
     * 수정 가능 필드
     * @param email
     * @param phone
     */
    public MemberRequestDto(Long id, String email, String phone) {
        this.id = id;
        this.email = email;
        this.phone = phone;
    }
}

{'email', 'phone'} 가 'UpdateEmailPhone'으로 한 그룹으로 묶인것을 확인할 수 있다.

 

 

[MemberResponseDto.class]

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberResponseDto {

    private Long id;
    private String username;
    private String password;
    private String email;
    private String phone;

    private String createdAt;
    private String updatedAt;


    //등록 / 삭제 요청 - return
    public MemberResponseDto(Long id) {
        this.id = id;
    }

    //Entity -> DTO
    //조회 / 수정 요청 - return
    public MemberResponseDto(Member entity) {
        this.id = entity.getId();
        this.username = entity.getUsername();
        this.password = entity.getPassword();
        this.email = entity.getEmail();
        this.phone = entity.getPhone();

        this.createdAt = entity.getCreatedAt();
        this.updatedAt = entity.getUpdatedAt();
    }


}

1. 회원 정보 수정 메서드

//Update (수정일자 업데이트)
    public void update(MemberRequestDto dto) {
        if (dto.getPassword() != null) {
            this.password = dto.getPassword();
        }
        if (dto.getEmail() != null) {
            this.email = dto.getEmail();
        }
        if (dto.getPhone() != null) {
            this.phone = dto.getPhone();
        }
    }

Member Entity 에 회원 정보 수정 메서드(update) 를 작성하였다.

수정 값이 들어온 필드에 한해서 수정이 일어난다.

'password', 'email', 'phone' 수정에 모두 사용할 수 있다.


2. 회원 정보 수정 Service 기능 구현

//MemberService.class 中...

//Update Member(회원 정보 수정)
@Transactional
public MemberResponseDto updateMember(MemberRequestDto requestDto) {

    //1. member 찾기
    Member member = memberRepository.findById(requestDto.getId())
            .orElseThrow(() -> new DataNotFoundException("존재하지 않는 회원 입니다."));

    //2. update member
    member.update(requestDto);

    //3. member -> MemberResponseDto
    return new MemberResponseDto(member);
}

 


3. 회원 정보 수정 Controller

 

이 부분 부터 아주 중요하다고 생각한다.

회원 정보 수정에 대한 @GetMapping, @PostMapping을 나눠 설명하겠다.

 

[@GetMapping - infoUpdateForm]

//MemberController.class 中...

/**
 * member Information update
 */
@GetMapping("/info/update")
public String infoUpdateForm(Model model) {
    Member currentMember = getCurrentMember();

    MemberResponseDto responseDto = new MemberResponseDto(currentMember);
    // username, createAt, updatedAt 을 보여주기 위함
    model.addAttribute("responseDto", responseDto);

    //responseDto -> requestDto
    // email , phone 수정가능
    MemberRequestDto requestDto = new MemberRequestDto(responseDto.getId(), responseDto.getEmail(), responseDto.getPhone());
    model.addAttribute("requestDto", requestDto);

    return "members/info-update";
}

Controller -> View 로 보내는 데이터는 크게 2가지 이다.

1. responseDto : id , username , password , email , phone , createdAt , updatedAt

2. resquestDto : id , email , phone

이렇게 구성되어 있다.

 

responseDto의 데이터는 수정이 불가하며, View 에 정보를 띄우는 용도이다.

requestDto의 데이터 중 email, phone 은 수정이 가능한 데이터이며,

id 는 데이터 수정을 위한 '회원 객체 식별자'로 사용된다.

 

[@PostMapping - updateMemberInfo]

//MemberController.class 中...

@PostMapping("/info/update")
public String updateMemberInfo(@ModelAttribute("requestDto") @Validated(MemberRequestDto.UpdateEmailPhone.class) MemberRequestDto dto,
                               BindingResult bindingResult, Model model) {
    // 현재 로그인된 회원 객체
    Member currentMember = getCurrentMember();

    //유효성 검사 오류 발생시
    if (bindingResult.hasErrors()) {
        //기존 정보 그대로 반환 (수정 불가 정보 : username, createdAt, updatedAt)
        MemberResponseDto responseDto = new MemberResponseDto(currentMember);
        model.addAttribute("responseDto", responseDto);

        //기존 정보 그대로 반환 (수정 가능 정보 : email, phone)
        MemberRequestDto requestDto = new MemberRequestDto(responseDto.getId(), responseDto.getEmail(), responseDto.getPhone());
        model.addAttribute("requestDto", requestDto);

        //에러 메시지 반환
        List<String> errorMassage = bindingResult.getAllErrors().stream()
                .map(objectError -> objectError.getDefaultMessage())
                .collect(Collectors.toList());
        model.addAttribute("errorMessage", errorMassage);

        return "members/info-update"; // 유효성 검사 실패 시 다시 폼으로
    }

    try {
        //회원 정보 업데이트
        memberService.updateMember(dto);
    }

    // 중복 검사 오류 시, 에러 처리 로직
    catch (DataAlreadyExistsException | PasswordCheckFailedException ex) {
        bindingResult.reject("errorMessage", ex.getMessage());
        model.addAttribute("errorMessage", ex.getMessage());

        return "members/info-update";
    }

    return "redirect:/members/info";
}

View -> Controller 로 받아오는 데이터는 "requestDto" 이다.

requestDto : id, email, phone 

의 데이터가 들어있다.

 

유효성 검사, 중복 검사의 예외 처리에 해당하지 않는 경우

"memberService.updateMember(dto)" 를 통해 회원 정보 업데이트가 이루어진다.

 

 


4. 회원 정보 수정 VIEW (info-update.html)

<!--info-update.html 中...-->
<!-- Member Info Form -->
<div class="info-box">
    <h2>Member Info</h2>
    <form th:action="@{/members/info/update}" th:object="${requestDto}" method="post">

        <!-- Hidden ID Field -->
        <input type="hidden" th:field="*{id}">

        <div>
            <input id="info-username" th:value="${responseDto.username}" type="text" readonly>
            <label for="info-username">Username</label>
        </div>

        <div>
            <input id="info-email" th:field="*{email}" type="email" >
            <label for="info-email">Email</label>
        </div>

        <div>
            <input id="info-phone" th:field="*{phone}" type="text" >
            <label for="info-phone">Phone</label>
        </div>

        <div>
            <input id="info-created-at" th:value="${responseDto.createdAt}" type="text" readonly>
            <label for="info-created-at">Created At</label>
        </div>

        <div>
            <input id="info-updated-at" th:value="${responseDto.updatedAt}" type="text" readonly>
            <label for="info-updated-at">Updated At</label>
        </div>

        <button type="submit" style="background: green;">Save</button>


        <div th:if="${errorMessage}" style="color: red;">
            <p th:text="${errorMessage}">Error message here</p>
        </div>

    </form>
</div>

구성은 다음과 같다.

 

1. responseDto : id , username , password , email , phone , createdAt , updatedAt

2. resquestDto : id , email , phone

 

이 두가지의 데이터가 Controller 단 -> VIEW 단 으로 왔다.

 

responseDto 의 데이터 중 'username' , 'createdAt' , 'updatedAt' 만 사용자에게 보여지고, 수정이 불가하다.

resquestDto 의 데이터 중 'id' 는 회원 정보 수정 식별자 로서 , 'hidden' 속성으로 사용자에게 보여지지 않는다.

'email' , 'phone' 속성은 수정 가능하며, 수정된 정보가 'th:field' 를 통해 resquestDto 에 저장된다.

 

'Save' 버튼을 누르면, <form th:action="@{/members/info/update}" th:object="${requestDto}" method="post"> 에 따라, post 요청이 보내진다.

 


5. 완성 화면

Email 

"member2@example.com" -> "member2@update.com"

Phone

"010-1111-1111" -> "010-9999-9999"

로 수정을 진행한다.

 

Email 과 Phone 이 정상적으로 수정된 것을 확인 할 수 있다.

Updated At (수정일자) 가 정상적으로 갱신된 것을 확인 할 수 있다.

DB 에서도 정상 반영이 된 것을 확인 할 수 있다.