Wanna be Brilliant Full-Stack Developer
SpringBoot 회원정보수정 - 유효성 검사하기! 본문
목적
회원정보수정은 완료된 상태이지만 완료는 됬지만 서비스 하기에는 불안하다
왜 서비스하기에 불안하냐면 서버입장에서 보면 일단은 name값과 패스워드값은 무조건 받아야한다.
만약에 패스워드를 받지 않으면 공백이 데이터베이스에 들어가게되면 문제가 된다.
그래서 name과 패스워드가 안들어오는것을 막으려면 앞단에서 막아야한다!
이 앞단에서 막는것은 유효성 검사라는것인데!
그리고 또한 프론트단에서도 막아주는것이 좋다! ( 패스워드를 안적으면 전송이 안되도록)
이 둘을 통해 정보가 잘들어왔으면 두번쨰로도 문제가 생길수도 있는데
두번째 문제는 내가 1번 유저를 수정하려고하는데 영속화를 시켜놨더니 1번유저가 없다.
그러면 수정을 못하는데 이것을 처리 해야한다!
이것들은 프론트단과 유효성검사는 데이터베이스에 접근 하지 않더라도 확인할 수 있는것들이다.
두번쨰 문제들은 데이터베이스에 접근해야 확인할 수있는 영역이기떄문에
앞단에서 처리할 문제와 뒷단에서 처리할 문제로 나눠진다.
값만 검증을 하거나 DB에서 확인하는것 두가지 로직이 필요하다 .
그러면 프론트단에서 막는것부터 해보려고한다!
프론트단에 required="required"로만 설정 하면 넘어가지 말아야하는데 넘어간다
왜넘어가냐면 제출할때 쓰이는 버튼이 일반적인 버튼이고 submit버튼이 아니기때문이다
그래서 조금 수정을 해야하는데 어떻게 하는지 보자
<button type="button" onclick="update(${principal.user.id})">제출</button>
에서 <button>제출</button> 이렇게 바꾼다!
버튼이 눌리면 form 태그가 실행이 된다.
<form id="profileUpdate"" onsubmit="update(${principal.user.id})">
이렇게 하면 이름과 비밀번호를 입력하지 않고 제출을 하지 못하도록 막는다 !
하지만 제대로 로직을 타지 않는다!
왜냐하면 form 태그는 버튼을 넣으면 일반적으로 submit버튼이 되는데
이 버튼을 누르면 form 태그에 액션이 일어나는데 그 액션이 머냐면 form 태그는 어딘가로 이동을 해야하는데
action에 어떠한 주소가 적혀 있지 않다.
안적혀 있으면 자기주소 default로 돌아온다.
우리는 그러면 안되고 update함수가 실행이 되어야한다!
잘되면 update.js로 가고 안되면 update실패가 떠야한다
// (1) 회원정보 수정
function update(userId,event) {
event.preventDefault(); // form 태그 액션을 막기!
let data =$("#profileUpdate").serialize();
console.log(data);
$.ajax({
type: "put",
url: `/api/user/${userId}`,
data:data,
conentType:"application/x-www-form-urlencoded; charset=utf-8",
dataType:"json"
}).done(res=>{
console.log("update 성공");
location.href=`/user/${userId}`;
}).fail(error=>{
console.log("update 실패");
});
}
이것을 통해 프론트단은 다막았고
두번쨰로는 postman을 통해 요청하면 프론트단에서 막아봤자 소용이 없다.
그래서 유효성 검사를 할것이다.
UserUpdateDto에서 필수적인 것들에 @NotBlank 어노테이션을 추가한다
이렇게 건다고 작동하는것은 아니다.
UserApiController에서
package com.cos.photogramstart.web.api;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.UserService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.user.UserUpdateDto;
import lombok.RequiredArgsConstructor;
@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());
System.out.println("==========================");
System.out.println(error.getDefaultMessage());
System.out.println("==========================");
}
throw new CustomValidationException("유효성 검사 실패함", errorMap);
}
User userEntity = userService.회원수정(id, userUpdateDto.toEntitiy());
principalDetails.setUser(userEntity); // 세션 정보 변경
return new CMRespDto<>(1, "회원수정완료", userEntity);
}
}
여기서 CustomValidationException이 발동하는데 이건 우리가 새로 무언갈 만들어야한다 .
여기서 핸들러를 보면 커스텀벨리드익셉션을 우리가 복사 붙여넣기로 새로운 CustomValidationApiException을 만든다.
똑같은건데 하나를 더만든 이유는?
package com.cos.photogramstart.handler;
import java.util.Map;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.util.Script;
import com.cos.photogramstart.web.dto.CMRespDto;
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class) // RuntimeExcpetion -> CustomValidaionException 으로 변경
public String validationException(CustomValidationException e) {
// CMRespDto, Script 비교
// 1. 클라이언트에게 응답할 때는 Script 좋음.
// 2. Ajax통신 - CMRespDto
// 3. Android 통신 -CMRespDto
return Script.back(e.getErrorMap().toString());
}
@ExceptionHandler(CustomValidationApiException.class)
public CMRespDto<?> validationApiException(CustomValidationApiException e) {
return new CMRespDto<>(-1,e.getMessage(),e.getErrorMap());
}
}
지금 두개로 나뉘어지는데 하나는 자바스크립트가 응답이 되고( 자바스크립트를 리턴)
하나는 오브젝트 CMRespDto가 응답이 된다. ( 데이터를 리턴) : API 통신 할때는 Ajax로 통신할때는 데이터로 응답한다
이 두가지를 구분해야한다!
또한 유효성 검사 실패를 하면 UserApiController에서
throw new CustomValidationException("유효성 검사 실패함", errorMap); ->
package com.cos.photogramstart.web.api;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.UserService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.user.UserUpdateDto;
import lombok.RequiredArgsConstructor;
@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());
System.out.println("==========================");
System.out.println(error.getDefaultMessage());
System.out.println("==========================");
}
throw new CustomValidationApiException("유효성 검사 실패함", errorMap);
} else
{
User userEntity = userService.회원수정(id, userUpdateDto.toEntitiy());
principalDetails.setUser(userEntity); // 세션 정보 변경
return new CMRespDto<>(1, "회원수정완료", userEntity);
}
}
}
만약에 프론트 단에서(jsp)에서 required를 이름에서 속성을 제거하고 하면 회원정보 수정에서 이름을 빼고 업로드 하였을때 결과는 성공이라 나오지만 message는 유효성 검사 실패함으로 나온다.
// (1) 회원정보 수정
function update(userId,event) {
event.preventDefault(); // form 태그 액션을 막기!
let data =$("#profileUpdate").serialize();
console.log(data);
$.ajax({
type: "put",
url: `/api/user/${userId}`,
data:data,
conentType:"application/x-www-form-urlencoded; charset=utf-8",
dataType:"json"
}).done(res=>{ // HttpStatus 상태코드 200번대
console.log("성공",res);
//location.href=`/user/${userId}`;
}).fail(error=>{ // HttpStatus 상태코드 200번대가 이날때
console.log("실패",eroor);
});
}
Ajax통신 할떄는 브라우저끼리 통신할떄는 HTTP 상태코드를 같이 던져주는게 좋다!
package com.cos.photogramstart.handler;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.util.Script;
import com.cos.photogramstart.web.dto.CMRespDto;
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class) // RuntimeExcpetion -> CustomValidaionException 으로 변경
public String validationException(CustomValidationException e) {
// CMRespDto, Script 비교
// 1. 클라이언트에게 응답할 때는 Script 좋음.
// 2. Ajax통신 - CMRespDto
// 3. Android 통신 -CMRespDto
return Script.back(e.getErrorMap().toString());
}
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validationApiException(CustomValidationApiException e) {
return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(-1,e.getMessage(),e.getErrorMap()),HttpStatus.BAD_REQUEST);
}
}