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;
}
}