[회원정보 수정] 유효성 검사
현재 상황
1. 필수로 받아야 하는 데이터가 공백으로 들어갈 수 있다. >> 서버단(프론트,백엔드)에서 해결
2. 현재 userId를 1번으로 고정시켜놔서 이것도 변수로 처리해줘야 한다. >> DB단에서 해결
프론트단
update.js에서 수정버튼을 누르면 required가 제대로 동작하지않고 데이터간다
따라서 update()함수를 호출하는 것을 button태그가 아닌 form태그에 두었다.
<!--프로필 수정-->
<!--버튼을 누르면 form태그 실행, action이 없으면 자기자신호출-->
<form id="profileUpdate" onsubmit="update(${principal.user.id},event)">
<div class="content-item__02">
<div class="item__title">이름</div>
<div class="item__input">
<input type="text" name="name" placeholder="이름"
value="${principal.user.name}" required="required"/>
</div>
</div>
form태그에서는 action경로를 지정하지않고 update()에 event를 담아
event.preventDefault();
를 사용해서 ajax의 .done에 지정한 경로로 이동할 수 있게 했다.
백엔드단
프론트단뿐만 아니라 백엔드단에서도 막아야하는 이유: 제공한 페이지가 아닌 Postman을 이용해 요청하면 프론트단에서의 제약조건이 의미가 없기 때문이다.
1. 필수값엔 @NotBlank 걸기
UserUpdateDto
@Data
public class UserUpdateDto {
@NotBlank
private String name; //필수값
@NotBlank
private String password; //필수값
private String website;
private String bio;
private String phone;
private String gender;
// 필수값이 아닌값까지 고려해서 Entity를 만드는 것은 위험함
// 사용자가 이름,패스워드 기재 안하면 DB에 공백이 들어가므로 validation체크해야함
public User toEntity() {
return User.builder()
.name(name) // Validation 체크
.password(password) // Validation 체크
.website(website)
.bio(bio)
.phone(phone)
.gender(gender)
.build();
}
}
2. @NotBlank가 걸린 UserUpdateDto에 @Valid 어노테이션을 걸어 제대로 작동하게 하고 유효성검사가 실패 했을때를 고려해 에러를 담아줄 BindingResult를 사용한다. BindingResult의 위치는 꼭 @Valid 되는 파라미터 다음에 적는다.
@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()) {
// 유효성 검사 에러가 발생한 경우
// 유효성 검사 에러를 담기 위한 'errorMap'생성
Map<String, String> errorMap = new HashMap<>();
// 각 필드에 대한 에러를 errorMap에 저장
// bindingResult객체가 발생한 모든 에러를 순회하면서 각 필드에 대한 에러 정보 추출
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
// 유효성 검사 실패 예외를 발생시킴
throw new CustomValidationApiException("유효성검사 실패함", errorMap);
} else {
// 유효성 검사 통과한 경우
// 회원 정보 수정 요청 처리
User userEntity = userService.회원수정(id, userUpdateDto.toEntity());
// 수정된 회원 정보를 세션에 반영
principalDetails.setUser(userEntity);
// 성공 응답 반환
return new CMRespDto<>(1,"회원수정완료", userEntity);
}
}
}
3. ApiController에서 작동할 CustomValidationApiException을 만든다. 기존 CustomValidationException이 있음에도 하나 더 만드는 이유는 ajax, android통신에는 데이터를 보내므로 CMRespDto로 응답하는 것이 더 좋다. 따라서 ControllerExceptionHandler에서 CMREspDto로 응답하는 Exception을 만들어준다.
이렇게 하면 ajax가 .done을 타서 성공한듯 보이지만 백엔드에 막혀 DB에 반영되지 않는다. done은 HTTPStatus상태코드가 done이 200번(정상)일 때 실행되고 fail은 HTTPStatus상태코드가 200번대가 아닐 때(에러) 실행된다. 따라서 HTTPStatus상태코드도 함께 응답해주어야 컨트롤이 가능하다.
// 데이터 리턴 (Ajax를 통해 응답할때)
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validataionApiException(CustomValidationApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(),e.getErrorMap()),HttpStatus.BAD_REQUEST);
}
3번 적용
public class CustomValidationApiException extends RuntimeException{
// 객체를 구분하기 위해 시리얼넘버를 넣어주는것
// JVM을 위해 걸어주는 것
private static final long serialVersionUID = 1L;
// 유효성 검사 실패 시 발생한 에러메세지를 담은 맵
private Map<String, String> errorMap;
// 생성자로 전달된 메세지와 에러맵을 사용해 예외객체 생성
public CustomValidationApiException(String message,Map<String, String> errorMap) {
super(message);
this.errorMap = errorMap;
}
// 에러메세지를 담은 맵을 반환하는 메서드
public Map<String, String> getErrorMap(){
return errorMap;
}
}
@RestController
@ControllerAdvice // 모든 exception을 낚아채는 어노테이션
public class ControllerExceptionHandler {
// CMRespDto와 Script 비교
// 1. 클라이언트에게 응답할 때는 Script좋음
// 2. Ajax통신 - CMRespDto
// 3. Android통신 -CMREspDto
// 2, 3은 개발자가 응답받을 때는 코드로 받는것이 좋다.
// 자바스크립트 리턴
@ExceptionHandler(CustomValidationException.class)
public String validataionException(CustomValidationException e) {
return Script.back(e.getErrorMap().toString());
}
// 데이터 리턴 (Ajax를 통해 응답할때)
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validataionApiException(CustomValidationApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(),e.getErrorMap()),HttpStatus.BAD_REQUEST);
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
private int code; // 1(성공),-1(실패)
private String message;
private T data;
}