반응형

HTML 파일에서는 PUT 요청을 할 수 없으므로 자바스크립트를 이용해야한다
-> 버튼을 클릭했을때 onsubmit 으로 update() 자바스크립트 함수가 실행된다.

update.html

<html xmlns:th="http://www.thymeleaf.org">

...

            <!--프로필 수정-->
            <form id="profileUpdate" th:object="${principal}"  th:onsubmit="|update(*{id}, event)|">
            <div class="content-item__02">
                <div class="item__title">이름</div>
                <div class="item__input">
                    <input type="text" name="name" placeholder="이름" value="겟인데어" th:field="*{name}" required="required" />
                </div>
            </div>
            <div class="content-item__03">
                <div class="item__title">유저네임</div>
                <div class="item__input">
                    <input type="text" name="username" placeholder="유저네임" value="TherePrograming" readonly="readonly"  th:field="*{username}"/>
                </div>
            </div>
            
            ...
            
            <!--제출버튼-->
            <div class="content-item__11">
                <div class="item__title"></div>
                <div class="item__input">
                    <button>제출</button>
                </div>
            </div>
            <!--제출버튼end-->
            </form>
            <!--프로필수정 form end-->
        </article>
    </section>
</main>

<script src="/js/update.js"></script>

 

update.js

-  $("#profileUpdate").serialize() : form의 profileUpdate테그를 찾아서 그 폼테그가 들고 있는 정보를 시리얼라이즈한다.
- done과 faile은 리턴된 HttpStatus 상태코드에 따라 결정된다.
  200번대 -> done
  200번대가 아닐때 -> fail
- db에 유저정보가 없을 경우, 즉 CustomValidationApiException 에 errorMap 정보 가 없이 전달되는 경우에는 error.data에 정보가 담기지 않는다 그럴 상황을 분기해야한다. 

function update(userId, event) {

    event.preventDefault(); //폼태그 액션을 막기

    let data = $("#profileUpdate").serialize();

    console.log(data);
    $.ajax({
        type:"put",
      url : `/api/user/${userId}`,
        data: data,
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        dataType: "json"
    }).done(res =>{ //Http상태코드 200번대
        console.log("update 성공", res);
        alert("update 성공");
        location.href = `/user/${userId}`;
    }).fail(error => {
       if(error.data ==null){
           alert(error.responseJSON.message);
       }else{
           //json object를 Json 문자열로 변환
           alert(JSON.stringify(error.responseJSON.data));
    });


}

profileUpdate테그를 찾아서 그 폼테그가 들고 있는 정보를 시리얼라이즈한다

UserApiController

- ajax통신으로 실행되는 update메소드를 구성한다.
- form으로부터 전달되는 값을 받기 위해 data transform 오브젝트가 필요하다, 그래서 dto 안에 
- username, email을 제외한 값을 받도록 한다

1.유효성 검사에 통과하면
-> userService의 update 메소드 실행,
  파라미터에 수정하고자 하는 id값과 userUpdateDto의 toEntity() 메소드를 실행시켜서 User객체로 변환한다.
  여기서 끝나는게 아니라 principalDetails의 setUser 설정으로 수정된 값에 따라 현재 세션 정보를 변경해주도록 한다.

- update메소드의 반환 타입은 CMRespDto로 설정한다. => 수정에 성공했으니 성공코드1, 성공메시지, 데이터에는 userEntity(수정된 유저정보)를 담아준다.

2.유효성 검사에 실패하면
- bindingResult의 에러를 errorMap에 담는다.
- CustomValidationApiException 을 메시지와 errorMap을 담아서 터트린다.

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto <T> {
    private int code; //1(성공), -1(실패)
    private String message;
    private T data;
}
@RequiredArgsConstructor
@RestController
public class UserApiController {

    private final UserService userService;

    @PutMapping("/api/user/{id}")
    public CMRespDto<?> update(@PathVariable int id,
                               @Valid UserUpdateDto userUpdateDto,
                               BindingResult bindingResult, // 꼭 @Valid가 적혀있는 다음 파라미터에 적어야됨.
                               @AuthenticationPrincipal PrincipalDetails principalDetails){

        if (bindingResult.hasErrors()) {
            Map<String, String> errorMap = new HashMap<>();

            for (FieldError error : bindingResult.getFieldErrors()) {
                errorMap.put(error.getField(), error.getDefaultMessage());
                            }
            throw new CustomValidationApiException("유효성 검사 실패함", errorMap);

        }else {

            User userEntity = userService.update(id, userUpdateDto.toEntity());
            principalDetails.setUser(userEntity); //수정된 값에 따라 세션정보 변경
            return new CMRespDto<>(1, "회원수정완료", userEntity);


        }
    }


}

 

유효성 검사 실패시

CustomValidationApiException

public class CustomValidationApiException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    private Map<String, String> errorMap;

    public CustomValidationApiException(String message){
        super(message);
    }

    public CustomValidationApiException(String message, Map<String, String> errorMap){
        super(message);
        this.errorMap = errorMap;
    }

    public Map<String, String > getErrorMap(){
        return errorMap;
    }
}

 

ControllerExceptionHandler
- 모든 Exception은 낚아채는 핸들러
- ajax통신할때는 ResponseEntity 를 사용해야지 http 상태코드를 전달할 수 있다. 그래서 분기하기가 쉽다.(done과 fail로)

@RestController
@ControllerAdvice //모든 Exception들을 낚아챔
public class ControllerExceptionHandler {

...
    //object리턴 - api통신때
    @ExceptionHandler(CustomValidationApiException.class)
    public ResponseEntity<?> validationApiException(CustomValidationApiException e){
        return new ResponseEntity<>
        (new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
    }

...
}

UserUpdateDto

- 유저정보 수정을 위한 DTO다

@Data
public class UserUpdateDto {
    //username, email은 안받는다.
    @NotBlank
    private String name; //필수
    @NotBlank
    private String password; //필수
    private String website;
    private String bio;
    private String phone;
    private String gender;

    //코드 수정이 필요할 예정
    public User toEntity(){
        return User.builder()
                .name(name) //이름을 기재 안하면 문제 -> validation체크
                .password(password) //비번을 기재 안하면 문제 -> validation체크
                .website(website)
                .bio(bio)
                .phone(phone)
                .gender(gender)
                .build();
    }
}

 

UserService.class

- 실제 회원 수정 로직
- @Transactional 어노테이션 붙여준다
- 파라미터로 수정계정의 id와 user객체 가 들어간다.
  여기서 받는 user 객체는 UserDto에서 toEntity()로 반환된 값이다.
- orElesThrow : 못찾았을때 Exception을 발동시킨다.
  -> 만약 db에서 회원 정보를 찾지 못하면 CustomValidationApiException 을 리턴한다.
  기존 CustomValidationApiException 에는 생성자가 message,와 errorMap을 전달해야함으로 message만 받는 생성자를 1개 더 만들어준다.

* 사실 회원정보 수정할때 로그인이 되어있기 때문에 DB에 회원정보가 없을 일은 없지만, 혹시나 없는유저를 찾으면 서버에 문제가 발생할 수 있기에 해당 문제를 원천 차단했다.

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Transactional
    public User update(int id, User user){
        //1.영속화
        //1) get() :무조건 찾았다. 걱정마
        //2) orElseThrow() :못찾았어 exception발동시킬께

        User userEntity = userRepository.findById(id).orElseThrow(() 
        -> {return new CustomValidationApiException("찾을수 없는id입니다."); });

        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);

        //2.영속화된 오브젝를 수정 - 더티체킹(업데이트 완료)
        userEntity.setName(user.getName());
        userEntity.setPassword(encPassword);
        userEntity.setBio(user.getBio());
        userEntity.setWebsite(user.getWebsite());
        userEntity.setPhone(user.getPhone());
        userEntity.setGender(user.getGender());

        return userEntity;
    }   
}

+ Recent posts