📝 View
- 회원정보 수정 기능을 본격적으로 구현해보기 전에 먼저 회원정보 변경 창에 생성되어 있는 모든 입력란에 현재 접속중인 사용자의 정보가 들어가 있을 수 있도록 하려고 합니다.
- 이를 위해 먼저 View 페이지에 아래와 같은 코드를 추가해주었습니다.
<sec:authorize access="isAuthenticated()">
<sec:authentication property="principal" var="principal"/>
</sec:authorize>
- 위 코드를 통해 우리는 현재 접속중인 사용자에 대한 정보를 'principal'이라는 변수를 통해 ${principal.user.id}와 같이 접근할 수 있습니다.
<!--프로필 수정-->
<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>
<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>제출</button>
</div>
</div>
<!--제출버튼end-->
</form>
<!--프로필수정 form end-->
📝 JavaScript
- View 단의 코드를 보면 회원정보 수정 창의 '제출' 버튼을 클릭할 경우
update()
함수가 호출되도록 하고 있는 것을 볼 수 있는데, 해당 함수는 아래와 같이 구현되어 있습니다.
// (1) 회원정보 수정
function update(user_id, event) {
event.preventDefault(); //form 태그 액션 막기
let data = $("#profileUpdate").serialize();
$.ajax({
type:"put",
url:`/api/user/${user_id}`,
data: data,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
dataType: "json"
}).done(resp => { //Http status: 2xx
location.href=`/user/${user_id}`;
}).fail(error => {
if(error.data == null)
alert(error.responseJSON.message);
else
alert(JSON.stringify(error.responseJSON.data));
})
}
- 여기서
event.preventDefault();
구문은 form 태그의 액션으로 인해 ajax 함수가 무시되는 것을 방지하기 위함입니다. serialize()
함수는 form 태그 안에 있는 모든 입력 데이터(username, password, ...)를 하나의 변수에 한꺼번에 저장하기 위해 사용하였습니다.
📝 Controller
- 컨트롤러에는 위에 설정되어 있는 경로를 요청받아 회원 정보 수정을 수행할 수 있도록 아래와 같이 함수를 구현해주었습니다.
package com.cos.photogram.web.api;
...
@RequiredArgsConstructor
@RestController
public class UserApiController {
private final UserService userService;
@PutMapping("/api/user/{id}")
public CMRespDto<?> update(
@PathVariable Long 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);
}
User userEntity = userService.update(id, userUpdateDto.toEntity());
principalDetails.setUser(userEntity);
return new CMRespDto<>(1, "회원수정 완료", userEntity);
}
}
package com.cos.photogram.web.dto.user;
...
@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();
}
}
📝 Service
- 서비스 단에서는 영속화 및 더티 체킹을 통한 회원정보 수정 로직을 수행합니다.
package com.cos.photogram.service;
...
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Transactional
public User update(Long id, User user) {
User userEntity = userRepository.findById(id).orElseThrow(() -> {
return new CustomValidationApiException("회원정보 수정 실패: 회원 ID를 찾을 수 없습니다.");
});
userEntity.setName(user.getName());
userEntity.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userEntity.setWebsite(user.getWebsite());
userEntity.setBio(user.getBio());
userEntity.setPhone(user.getPhone());
userEntity.setGender(user.getGender());
return userEntity;
}
}
- 여기서 보면 데이터베이스 단에서 오류가 발생할 경우를 대비하여
orElseThrow()
함수에서 예외를 내보내고 있는데, 이를 기존에 우리가 구현해놓았던CustomValidationApiException
클래스를 재사용하고 있습니다. 따라서 이를 위해 해당 클래스에 새로운 생성자를 추가해주었습니다.
package com.cos.photogram.handler.ex;
...
public class CustomValidationApiException extends RuntimeException {
...
//추가된 부분
public CustomValidationApiException(String message) {
super(message);
}
public CustomValidationApiException(String message, Map<String, String> erorrMap) {
super(message);
this.erorrMap = erorrMap;
}
...
}
📝 ExceptionHandler
- 다음으로 이전 시간에 구현해놓았던 ExceptionHandler를 조금 수정해주었습니다.
package com.cos.photogram.handler;
...
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public String validationException(CustomValidationException e) {
return Script.back(e.getErorrMap().toString());
}
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validationApiException(CustomValidationApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErorrMap()), HttpStatus.BAD_REQUEST);
}
}
- 이렇게 한 이유는 ajax를 사용할 시 Http 상태 코드에 따라서
done()
또는fail()
함수가 호출되게 되는데, 이를 위해서ResponseEntity
를 활용하여 기존의 Response DTO와 함께HttpStatus
도 함께 반환해준 것입니다.
}).done(resp => { //Http status: 2xx
location.href=`/user/${user_id}`;
}).fail(error => {
if(error.data == null)
alert(error.responseJSON.message);
else
alert(JSON.stringify(error.responseJSON.data));
})
'🚗 Backend Toy Project > Photogram' 카테고리의 다른 글
[Photogram] 프로필 페이지 - 서버에 이미지 업로드 (0) | 2022.07.10 |
---|---|
[Photogram] 구독하기 (0) | 2022.07.10 |
[Photogram] 로그인 (0) | 2022.07.08 |
[Photogram] 회원가입 - 공통 응답 DTO, Script 만들기 (0) | 2022.07.08 |
[Photogram] 회원가입 - Validation (0) | 2022.07.07 |