have a good time 2021. 12. 12. 20:12

 

지금까지 했던 회원정보수정에 관해 2가지 처리를 해야한다.

(이전 글 : https://happy-fun.tistory.com/165 )

 

첫번째, 회원정보 수정 시, name과 password 값은 꼭 받아야 한다.

 

그래서 빈 값이 들어오지 않도록 유효성 검사를 해준다.

1) 프론트에서 막아주기

2) 유효성 검사 validation 사용

 

두번째, 만약 1번 유저를 수정하려면 데이터베이스에 1번 유저 데이터가 있어야 하는데, 없을 경우 어떻게 처리?

 

순서대로 처리해보겠다.

 

1. 회원정보 수정 시 name, password 필수 입력 받기 - 프론트

update.jsp 파일(회원정보 수정 페이지)

에서 name, password 부분에 required ="required" 추가

 

				<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>
                
                
                
                
                		<div class="content-item__04">
					<div class="item__title">패스워드</div>
					<div class="item__input">
						<input type="password" name="password" placeholder="패스워드"  required="required" />
					</div>
				</div>

이렇게 처리하면 이름, 패스워드 부분이 빈칸이면 제출 버튼이 작동이 안하게 된다.

 

그런데, 실제 실행해 보니 그렇지 않고, 수정이 잘 되었다.

그 이유는. 

 

전체 update.jsp 파일을 보면 

 

 

	
			<form id="profileUpdate"">
				<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>
				<div class="content-item__03">
					<div class="item__title">유저네임</div>
					<div class="item__input">
						<input type="text" name="username" placeholder="유저네임"
							value="${principal.user.username}" readonly="readonly" />
					</div>
				</div>
				<div class="content-item__04">
					<div class="item__title">패스워드</div>
					<div class="item__input">
						<input type="password" name="password" placeholder="패스워드"  required="required" />
					</div>
				</div>
				<div class="content-item__05">
					<div class="item__title">웹사이트</div>
					<div class="item__input">
						<input type="text" name="website" placeholder="웹 사이트"
							value="${principal.user.website}" />
					</div>
				</div>
				<div class="content-item__06">
					<div class="item__title">소개</div>
					<div class="item__input">
						<textarea name="bio" id="" rows="3">${principal.user.bio}</textarea>
					</div>
				</div>
				<div class="content-item__07">
					<div class="item__title"></div>
					<div class="item__input">
						<span><b>개인정보</b></span> <span>비즈니스나 반려동물 등에 사용된 계정인 경우에도
							회원님의 개인 정보를 입력하세요. 공개 프로필에는 포함되지 않습니다.</span>
					</div>
				</div>
				<div class="content-item__08">
					<div class="item__title">이메일</div>
					<div class="item__input">
						<input type="text" name="email" placeholder="이메일"
							value="${principal.user.email}" readonly="readonly" />
					</div>
				</div>
				<div class="content-item__09">
					<div class="item__title">전회번호</div>
					<div class="item__input">
						<input type="text" name="phone" placeholder="전화번호"
							value="${principal.user.phone}" />
					</div>
				</div>
				<div class="content-item__10">
					<div class="item__title">성별</div>
					<div class="item__input">
						<input type="text" name="gender" value="${principal.user.gender}" />
					</div>
				</div>

				<!--제출버튼-->
				<div class="content-item__11">
					<div class="item__title"></div>
					<div class="item__input">
						<button type="button" onclick="update(${principal.user.id})">제출</button>
					</div>
				</div>
				<!--제출버튼end-->

			</form>

 

update.jsp 파일에서 회원정보 수정 값 받는 form태그 버튼이 

submit 버튼이 아니라, 아래처럼 일반 버튼이기 때문이다.

	<button type="button" onclick="update(${principal.user.id})">제출</button>

그래서 required = "required" 와 같은 유효성 검사가 작동 안된다.

 

그래서 저 위 <button> 태그에 달린 뒤 내용들 지우고

	<button>제출</button>

이렇게 바꾼 뒤 

맨 위의 form 태그에서 아래와 같이 변경

 

	<form id="profileUpdate" onsubmit = "update(${principal.user.id})">

 

 

그러면, name 값, 즉 이름을 입력 안하고 제출버튼을 누르면 아래와 같이 메세지가 나오면서 제출이 안된다.

 

 

 

그래서 잘 완성되었다.

 

그런데, 여기에서 필수로 입력받아야 할 이름, 비밀번호를 입력한 뒤 제출버튼을 눌러서 

성공적으로 변경이 되면,

update.js 에 함수로

	location.href = `/user/${userId}`;

이쪽 주소로 이동하게 해놨지만, 이동을 하지 않는다.

즉, 코드가 제대로 작동이 되지 않고 있다.

 

그 이유는, form 태그안의 버튼(위의 update.jsp 파일 보면 form 태그 내부에 제출버튼이 들어가 있음)

(특히, form 태그 안의 버튼을 submit 버튼이라고 함)

이 클릭되면 

form 태그 안의 어떠한 action 이 일어남.

즉, form 태그가 진행되면서 어딘가로 이동하게 되는데,

 

	<form id="profileUpdate" onsubmit = "update(${principal.user.id})" action = "주소">

위처럼 action = 주소 형태가 있어야, form 태그가 진행되면서 그리로 이동하는데,

여기서는 적지 않았다.(내가임의로 적어넣은 거임)

안 적혀있으면 자기자신한테 다시 돌아옴 

그런데, 우리는 onsubmit = "update" 부분, 즉

update.js 파일의 update 함수가 실행되야 하기 때문에 이러면 안된다.

 

그래서 event 를 날린다.

 

	<form id="profileUpdate" onsubmit = "update(${principal.user.id}, event)">

그리고 update.js 의 update 함수에서 event 를 받음

그리고 event.preventDefault(); 추가

이 코드는, 폼태그 액션을 막는다.

더 이상 진행되지 않게

 

 

<update.js> 파일

 

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=>{ 
		console.log("update 성공");
		location.href = `/user/${userId}`;

	}).fail(error=>{
		console.log("update 실패");
	});

}

그러면 이제 잘 진행됨

 

 

1. 회원정보 수정 시 name, password 필수 입력 받기 - validation 체크

 

위의 방법은 프론트에서 잘 작동하나, postman같이 외부에서 회원정보를 수정하면 작동이 안된다.

(즉 name, password 를 입력 안하고 수정가능)

 

UserUpdateDto.java 에서

@NotBlank 사용

@Data
public class UserUpdateDto {
	@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)
				.password(password) 
				.website(website)
				.bio(bio)
				.phone(phone)
				.gender(gender)
				.build();
	}
}

 

UserApiController.java 에서 메서드 매개변수로

@Valid , BindingResult 사용

특히, BindingResult는 @Valid 다음에 위치해야함

 

<UserApiController.java> 파일

@RequiredArgsConstructor
@RestController
public class UserApiController {

	
	private final UserService userService;
	
	@PutMapping("/api/user/{id}")
	public CMRespDto<?> update(
			@PathVariable int id, 
			@Valid UserUpdateDto userUpdateDto, 
			BindingResult bindingResult,
			@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.회원수정(id, userUpdateDto.toEntity());
		     	  principalDetails.setUser(userEntity);
		     	  return new CMRespDto<>(1,  "회원수정완료", userEntity);
		   	  }
	     	 }
      	 }

 

그리고 CustomValidationApiException.java 파일을 만듦.

CustomValidationException.java 와 동일한데,(복붙으로 만듦)

 

 

<CustomValidationApiException.java> 파일 - CustomValidationException.java 파일과 똑같음

 

public class CustomValidationApiException extends RuntimeException{

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

 

ControllerExceptionHandler.java 파일 

 

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

	
	@ExceptionHandler(CustomValidationException.class)
	public String validationException(CustomValidationException e) {
			return Script.back(e.getErrorMap().toString());
		}
		
	
	@ExceptionHandler(CustomValidationApiException.class)
	public CMRespDto<?> validationApiException(CustomValidationApiException e) {
		return new CMRespDto<>(-1, e.getMessage(), e.getErrorMap());
		
	}
	
	
	
}

 

첫번째에서 

CustomValidationException.java 는 자바스크립트로 사용자한테 메세지를 리턴라는 방식이고,

 

두번째에서

CustomValidationApiException.java 는 현재 ajax를 이용해 Api 통신을 하고 있으므로, 

CMRespDto 형태로 리턴하도록 함.

 

 

그리고 성공 시 응답, 실패 시 에러를 f 12, console 창에 뿌려보기 위해

update.js 에서

 

console.log("성공", res);

console.log("실패", error);

 

이렇게 추가

 

그리고 console 창에 일단 메세지 보기 위해

//location.href = `/user/${userId}`;

주석처리

 

<update.js> 파일

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=>{ 
		console.log("성공", res);
		//location.href = `/user/${userId}`;

	}).fail(error=>{
		console.log("실패", error);
	});

}

그러면

사이트에서 회원정보 수정이 잘 완료되면(모든 값을 hi 로 변경)

console 창에 결과값이 나옴

 

 

실패

 

위의 경우는 회원정보수정에 성공한 경우이지만, 실패한 경우를 살펴보자.

즉, 실패하면 update.js 의 update 함수에서

 

 

		.fail(error=>{
		console.log("실패", error);

이 부분이 실행되야 한다.

 

그래서 일단, update.jsp 파일에서 

name 부분에 required = "required" 했던 부분을 지우고

(회원정보 수정페이지에서 이름을 입력하지 않아도 제출 버튼이 작동)

 

회원정보 수정페이지에서는 이름(name) 부분을 입력하지 않고 제출해보자.

 

그러면

 

 

위와 같은 메세지를 console 창에서 볼 수 있다.

즉, 성공은 했다고 하면서, 그 결과는 실패한. 이상한 경우이다.

 

즉, UserApiController.java 에서 validation 체크를 해놔서,

여기에 대한 오류, 즉

	throw new CustomValidationApiException("유효성 검사 실패함", errorMap);

가 발생한 것이다. 

 

다시 정리하면, 유효성 검사를 프론트와 서버쪽? (validation ) 체크를 해놓았는데,

프론트는 통과했고, validation 체크는 통과를 못해서,

update.js 의 성공부분을 통과하고,

		.done(res=>{ 
		console.log("성공", res);

 

UserApiController.java (validation 에 대한 오류처리 )에서는 오류가 던져진 경우이다.

 

	throw new CustomValidationApiException("유효성 검사 실패함", errorMap);

 

 

 

이처럼 ajax로 브라우저끼리 통신할 때는 httpStatus 상태코드를 같이 던져주면서

해결을 하면 된다.

 

그래서, 성공할 때, 즉 HttpStatus 상태코드가 200번 일 때는 update.js 파일의

 

		.done(res=>{ 
		console.log("성공", res);

성공 부분이 실행되고

 

실패할 때, HttpStatus 상태코드가 200번이 아닐 때는 

 

		.fail(error=>{
		console.log("실패", error);

실패부분이 실행되도록 한다.

 

그래서 CMRespDto가 아니라, 

ResponseEntity<> 를 사용한다.

 

<ControllerExceptionHandler.java 파일>

아래와 같이 수정. (리턴이 원래 CMRespDto 였는데, ResponseEntity 로 수정)

	@ExceptionHandler(CustomValidationApiException.class)
	public ResponseEntity<?> validationApiException(CustomValidationApiException e) {
		return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
	}

 

HttpStatus.BAD_REQUEST : 상태코드 400번. 네가 요청을 잘못함

즉, 회원정보수정페이지에서 name 값을 입력한 뒤에 제출버튼 눌러야 하는데,

그렇게 하지 않음.

이렇게 한 다음, 회원수정페이지에서 name 입력하지 않고 제출 버튼 누르면 console 창에 아래와같이

실패했다는 메세지가 나옴. (다른 내용도 나왔으나, 실패 문구만 첨부)

 

 

즉, update.js 에서 실패코드가 실행됨

		.fail(error=>{
		console.log("실패", error);

 

그런데 console 창에 "실패" 라는 문구랑 같이 나온 부분 부면

위와 같은 내용이 있어서,

 

update.js 에서 실패코드를 아래와같이 변경

		.fail(error=>{
		console.log("실패", error.responseJSON);

그런다음 name 입력 없이 회원정보수정하면,

 

실패 문구옆에 responseJSON 이 같이 나옴

 

 

위의 내용을 보면 errorMap 이라고 나온다. 

그런데 원래 수업에서 강사님이 data 라고 입력했는데, 내가 errorMap 이라고 잘못입력했다.

CMRespDto.java 파일에서 입력을 잘못했는데,

이 다음부터는 수정해서 errorMap 이 아니라 data로 보이도록 하겠다.

 

<CMRespDto.java 파일> - 원래 T errorMap 이였는데, T data 로 변경

 

 

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
	private int code;
	private String message;
	private T data;
}
더보기

(CMRespDto.java 파일은, 

https://happy-fun.tistory.com/158 - 회원가입 유효성 검사 2에서 만들어서,

 

로그인 - https://happy-fun.tistory.com/159

회원정보 수정 1 - https://happy-fun.tistory.com/164

회원정보 수정 2 - https://happy-fun.tistory.com/165

글에서도 모두 쓰였다. 그래서 잘 참고 바람) 

 

그러면 

이와 같이 errorMap 이 아니라 data로 나옴

 

특히, 자세히 살펴보면 아래와 같이 볼 수 있는데,

이 내용들을 사용자에게 뿌려주면 된다.

 

<update.js > 파일(수정)

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=>{ 
		console.log("성공", res);
		location.href = `/user/${userId}`;

	}).fail(error=>{
		alert(JSON.stringify(error.responseJSON.data));		
	});

}

수정 사항 :

 

- 실패 코드 

 

alert 를 사용하여, 메세지 형태로 나올 수 있도록 함.

 

JSON.stringify 는

자바스크립트 오브젝트(error.responseJSON.data)

를 JSON 문자열로 변환

 

- 성공 코드 

 

회원수정 성공 시 /user/${userId} 로 이동하게 함

 

 

 

이제 회원정보수정 페이지에서

name 을 입력하지 않고 제출을 누르면 아래 메세지가 나옴

 

 

 

 

그리고 마지막으로, update.jsp 파일(회원정보수정 페이지)

에서 name 부분에 required = "required" 추가.

 

 

 

-끝-