[ 작업한 내용 ]
< 사용자: 본인 인증 정보 >
# UserInfoResDTO
- 사용자 본인 인증 정보 조회 응답 DTO 정의
- 사용자 아이디, 이메일, 가입 날짜, 마지막 로그인 날짜 (username, email, joinDate, lastLoginDate)
- User 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class UserInfoResDTO {
private String username;
private String email;
private LocalDateTime joinDate;
private LocalDateTime lastLoginDate;
public UserInfoResDTO(User user) {
this.username = user.getUsername();
this.email = user.getEmail();
this.joinDate = user.getJoinDate();
this.lastLoginDate = user.getLastLoginDate();
}
}
# UserController
- GET /api/v1/user/me 로 요청합니다.
- 파라미터로 @AuthenticationPrincipal 형태로 사용자 정보를 가져와 User 엔티티 형태로 저장합니다.
- Servicer 계층으로 user.getId()를 전달합니다.
- 결과는 UserInfoResDTO 형태로 저장합니다.
- 클라이언트 측으로 성공 메시지와 결과 데이터를 ApiResponse.success()로 감싸서 반환합니다.
@GetMapping()
public ResponseEntity<?> getMyInfo(@AuthenticationPrincipal User user) {
// 서비스 로직
UserInfoResDTO responseDTO = userService.getMyInfo(user.getId());
// 성공메시지
String message = messageProvider.getMessage(AuthSuccessCode.AUTH_USER_VERIFIED.getMessage());
// 반환
return ResponseEntity
.status(AuthSuccessCode.AUTH_USER_VERIFIED.getHttpStatus())
.body(ApiResponse.success(
AuthSuccessCode.AUTH_USER_VERIFIED,
message,
responseDTO));
}
# UserService
- userRepository.findById(userId)로 사용자 정보를 조회한 후 User 형태로 저장합니다. 사용자 정보가 유효하지 않다면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- Controller 계층으로 user 데이터를 기반으로 UserInfoResDTO 객체를 생성해 반환합니다.
@Transactional(readOnly = true)
public UserInfoResDTO getMyInfo(Long userId) {
// User 조회 및 객체 생성, 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 반환
return new UserInfoResDTO(user);
}
< 사용자: 본인 이메일 주소 변경 >
# UpdateEmailReqDTO
- 사용자 이메일 주소 변경 요청 DTO 정의
- 이메일 (email)
@Getter
public class UpdateEmailReqDTO {
@Email
private String email;
}
# UpdateEmailResDTO
- 사용자 이메일 주소 변경 응답 DTO 정의
- 사용자 고유번호, 사용자 아이디, 이메일, 수정 날짜 (id, username, email, updatedAt)
- User 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class UpdateEmailResDTO {
private Long id;
private String username;
private String email;
private LocalDateTime updatedAt;
public UpdateEmailResDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
this.updatedAt = user.getUpdatedAt();
}
}
# UserController
- PATCH /api/v1/user/me/email 로 요청합니다.
- 파라미터로 UpdateEmailReqDTO(email 필드)을 전달받고, @AuthenticationPrincipal 형태로 사용자 정보를 가져와 User 엔티티 형태로 저장합니다.
- Servicer 계층으로 user.getId()와 requestDTO(email)를 전달합니다.
- 결과는 UpdateEmailResDTO 형태로 저장합니다.
- 클라이언트 측으로 성공 메시지와 결과 데이터를 ApiResponse.success()로 감싸서 반환합니다.
@PatchMapping("/email")
public ResponseEntity<?> updateEmail(
@RequestBody UpdateEmailReqDTO requestDTO,
@AuthenticationPrincipal User user) {
// 서비스로직
UpdateEmailResDTO responseDTO = userService.updateEmail(user.getId(), requestDTO);
// 성공메시지
String message = messageProvider.getMessage(UserSuccessCode.USER_EMAIL_UPDATE.getMessage());
// 응답
return ResponseEntity
.status(UserSuccessCode.USER_EMAIL_UPDATE.getHttpStatus())
.body(ApiResponse.success(
UserSuccessCode.USER_EMAIL_UPDATE,
message,
responseDTO));
}
# UserService
- userRepository.findById(userId)로 사용자 정보를 조회한 후 User 형태로 저장합니다. 사용자 정보가 유효하지 않다면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- 변경하려는 이메일 주소가 기존 이메일과 동일한지 체크하고 동일하면 UserErrorCode.USER_EMAIL_SAME 예외를 던집니다.
- 변경하려는 이메일 주소가 중복되는지 체크하고 중복된다면 UserErrorCode.USER_EMAIL_DUPLICATED 예외를 던집니다.
- User 객체에 새로운 이메일 주소와 변경 날짜를 저장하고 userRepository.save(user)을 사용해 DB에 저장합니다.
- Controller 계층으로 user 데이터를 기반으로 UpdateEmailResDTO 객체를 생성해 반환합니다.
@Transactional
public UpdateEmailResDTO updateEmail(Long userId, UpdateEmailReqDTO requestDTO) {
// 사용자 조회 및 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 기존과 변경 이메일이 동일한지 체크
String newEmail = requestDTO.getEmail();
if (user.getEmail().equals(newEmail)) {
throw new BaseException(UserErrorCode.USER_EMAIL_SAME);
}
// 이메일 중복 체크
if (userRepository.existsByEmail(newEmail)) {
throw new BaseException(UserErrorCode.USER_EMAIL_DUPLICATED);
}
// user객체에 변경할 이메일, 수정된 날짜 저장 후 DB에 user객체 저장.
user.setEmail(newEmail);
user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
// 반환
return new UpdateEmailResDTO(user);
}
< 사용자: 본인 비밀번호 변경 >
# UpdatePasswordReqDTO
- 사용자 비밀번호 변경 요청 DTO 정의
- 변경할 비밀번호, 변경할 비밀번호 확인용 (password, repassword)
@Getter
public class UpdatePasswordReqDTO {
private String password;
private String repassword;
}
# UpdatePasswordResDTO
- 사용자 비밀번호 변경 응답 DTO 정의
- 사용자 고유번호, 사용자 아이디, 수정 날짜 (id, username, updatedAt)
- User 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class UpdatePasswordResDTO {
private Long id;
private String username;
private LocalDateTime updatedAt;
public UpdatePasswordResDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.updatedAt = user.getUpdatedAt();
}
}
# UserController
- PATCH /api/v1/user/me/email 로 요청합니다.
- 파라미터로 UpdatePasswordReqDTO(password, repassword)를 전달받고, @AuthenticationPrincipal 형태로 사용자 정보를 가져와 User 엔티티 형태로 저장합니다.
- Servicer 계층으로 user.getId()와 requestDTO(password, repassword)를 전달합니다.
- 결과는 UpdatePasswordResDTO 형태로 저장합니다.
- 클라이언트 측으로 성공 메시지와 결과 데이터를 ApiResponse.success()로 감싸서 반환합니다.
@PatchMapping("/password")
public ResponseEntity<?> updatePassword(
@RequestBody UpdatePasswordReqDTO requestDTO,
@AuthenticationPrincipal User user) {
// 서비스로직
UpdatePasswordResDTO responseDTO = userService.updatePassword(user.getId(), requestDTO);
// 성공메시지
String message = messageProvider.getMessage(UserSuccessCode.USER_PASSWORD_UPDATE.getMessage());
return ResponseEntity
.status(UserSuccessCode.USER_PASSWORD_UPDATE.getHttpStatus())
.body(ApiResponse.success(
UserSuccessCode.USER_PASSWORD_UPDATE,
message,
responseDTO));
}
# UserService
- userRepository.findById(userId)로 사용자 정보를 조회한 후 User 형태로 저장합니다. 사용자 정보가 유효하지 않다면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- 새로운 비밀번호와 확인용 새로운 비밀번호를 변수에 각각 저장합니다.
- 기존 비밀번호와 새로운 비밀번호가 동일한지 체크합니다. 만약 동일하면 UserErrorCode.USER_PASSWORD_SAME 예외를 던집니다.
- 새로운 비밀번호와 확인용 새 비밀번호가 동일한지 체크하고 만약 동일하지 않으면 UserErrorCode.USER_PASSWORD_MISMATCH 예외를 던집니다.
- 새로운 비밀번호를 passwordEncoder.encode(newPassword)를 사용하여 암호화 하여 변수에 저장합니다.
- 암호화한 비밀번호는 User 객체에 저장하고, 수정날짜도 저장합니다.
- userRepository.save(user)를 사용하여 DB에 비밀번호와 수정날짜를 저장합니다.
- Controller 계층으로 user 데이터를 기반으로 UpdatePasswordResDTO 객체를 생성해 반환합니다.
@Transactional
public UpdatePasswordResDTO updatePassword(Long userId, UpdatePasswordReqDTO requestDTO) {
// 사용자 조회 및 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 새로운 비밀번호, 확인용 새로운 비밀번호를 변수에 저장
String newPassword = requestDTO.getPassword();
String rePassword = requestDTO.getRepassword();
// 기존 비밀번호와 새로운 비밀번호가 동일한지 체크
if (passwordEncoder.matches(newPassword, user.getPassword())) {
throw new BaseException(UserErrorCode.USER_PASSWORD_SAME);
}
// 새 비밀번호와 확인용 새 비밀번호가 동일한지 체크
if (!newPassword.equals(rePassword)) {
throw new BaseException(UserErrorCode.USER_PASSWORD_MISMATCH);
}
// 새로운 비밀번호 암호화, 수정된 날짜 저장 후 DB에 user 객체 저장.
String encodedNewPassword = passwordEncoder.encode(newPassword);
user.setPassword(encodedNewPassword);
user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
// 반환
return new UpdatePasswordResDTO(user);
}
< 사용자: 회원 탈퇴 >
# DeleteAccountReqDTO
- 사용자 회원 탈퇴 요청 DTO 정의
- 현재 비밀번호 (password)
@Getter
public class DeleteAccountReqDTO {
private String password;
}
# DeleteAccountResDTO
- 사용자 회원 탈퇴 응답 DTO 정의
- 사용자 고유번호, 사용자 아이디, 상태, 삭제 날짜 (id, username, status, inactiveAt)
- User 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class DeleteAccountResDTO {
private Long id;
private String username;
private UserStatus status;
private LocalDateTime inactiveAt;
public DeleteAccountResDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.status = user.getStatus();
this.inactiveAt = user.getInactiveAt();
}
}
# UserController
- POST /api/v1/user/me/withdraw 로 요청합니다.
- 파라미터로 DeleteAccountReqDTO(password)를 전달받고, @AuthenticationPrincipal 형태로 사용자 정보를 가져와 User 엔티티 형태로 저장합니다.
- Servicer 계층으로 user.getId()와 requestDTO(password)를 전달합니다.
- 결과는 DeleteAccountResDTO 형태로 저장합니다.
- HttpServletResonse를 사용하여 Cookie에 저장될 JWT를 다시 설정합니다. 이때 토큰은 빈 값으로 정하고 .maxAge()를 0으로 설정합니다. 그 다음 응답시 쿠키를 전달합니다.
- 클라이언트 측으로 성공 메시지와 결과 데이터를 ApiResponse.success()로 감싸서 반환합니다.
@PostMapping("/withdraw")
public ResponseEntity<?> deleteAccount(
@RequestBody DeleteAccountReqDTO requestDTO,
@AuthenticationPrincipal User user,
HttpServletResponse response) {
// 서비스로직
DeleteAccountResDTO responseDTO = userService.deleteAccount(user.getId(), requestDTO);
// 쿠키 삭제
ResponseCookie deleteCookie = ResponseCookie.from("JWT", "")
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(0)
.sameSite("Strict")
.build();
// 응답시 전달할 쿠키
response.addHeader("Set-Cookie", deleteCookie.toString());
// 성공메시지
String message = messageProvider.getMessage(UserSuccessCode.USER_ACCOUNT_DELETED.getMessage());
// 응답
return ResponseEntity
.status(UserSuccessCode.USER_ACCOUNT_DELETED.getHttpStatus())
.body(ApiResponse.success(
UserSuccessCode.USER_ACCOUNT_DELETED,
message,
responseDTO));
}
# UserService
- userRepository.findById(userId)로 사용자 정보를 조회한 후 User 형태로 저장합니다. 사용자 정보가 유효하지 않다면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- 요청받은 비밀번호와 로그인한 사용자의 비밀번호가 일치한지 확인 후 일치하지 않으면 UserErrorCode.INVALID_PASSWORD 예외를 던집니다.
- User 객체에 상태를 INACTIVE로 저장하고 inactiveAt도 현재 날짜로 저장 후, userRepository.save(user)를 사용하여 DB에 User를 저장합니다.
- Controller 계층으로 user 데이터를 기반으로 DeleteAccountResDTO 객체를 생성해 반환합니다.
@Transactional
public DeleteAccountResDTO deleteAccount(Long userId, DeleteAccountReqDTO requestDTO) {
// 사용자 조회 및 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 비밀번호가 일치하는지 체크
String password = requestDTO.getPassword();
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BaseException(UserErrorCode.INVALID_PASSWORD);
}
// INACTIVE로 상태 변경 + inactiveAt 시각 저장 후 DB에 user 객체 업데이트
user.setStatus(UserStatus.INACTIVE);
user.setInactiveAt(LocalDateTime.now());
userRepository.save(user);
// 반환
return new DeleteAccountResDTO(user);
}
# UserRepository
- INACTIVE 상태이고, inactiveAt이 30일 지난 사용자를 찾는 메서드를 정의합니다.
List<User> findByStatusAndInactiveAtBefore(UserStatus status, LocalDateTime inactiveAt);
# UserStatusScheduler
- @Scheduled 어노테이션을 사용하여 updateInactiveUsersToDeleted()가 매일 새벽 3시에 자동으로 실행되도록 합니다.
- updateInactiveUsersToDeleted() 메서드는 다음과 같은 내용의 코드가 작성되어 있습니다.
- 사용자의 상태가 INACTIVE이고 INACTIVE 상태가 된지 30일 지난 사용자를 찾아서 usersToDelete 변수에 저장합니다.
- User 형태로 특정 사용자의 정보 중 상태를 DELETED로 변경하고 삭제 변경 날짜도 현재로 변경해준 뒤 userRepository.save(user)를 사용하여 DB에 저장합니다.
@Slf4j
@Component
@RequiredArgsConstructor
public class UserStatusScheduler {
private final UserRepository userRepository;
// 매일 새벽 3시에 실행 (corn 표현식: 초 분 시 일 월 요일)
@Scheduled(cron = "0 0 3 * * *")
@Transactional
public void updateInactiveUsersToDeleted() {
log.info("UserStatusScheduler started - update");
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
// INACTIVE 상태면서 inactiveAt이 30일 지난 유저 찾기
var usersToDelete = userRepository.findByStatusAndInactiveAtBefore(UserStatus.INACTIVE, cutoffDate);
for (User user : usersToDelete) {
user.setStatus(UserStatus.DELETED);
user.setDeletedAt(LocalDateTime.now());
userRepository.save(user);
log.info("User id={} status updated to DELETED", user.getId());
}
log.info("UserStatusScheduler finished");
}
}