회원 탈퇴 과정에서 즉시 삭제하지 않고, 회원 상태를 ACTIVE에서 DELETE로 업데이트한 후, 일주일 후에 삭제를 하는방식으로 개발을 진행했다.
페이지는 아래와 같이 구현을 하였고 회원테이블에 회원상태와 탈퇴사유 컬럼을 추가했다.

/*-------회원 탈퇴 섹션------*/
document.getElementById("withdrawalForm").addEventListener("submit", function(event) {
event.preventDefault(); // 기본 제출 막음
// 동의 체크 박스 체크 여부 확인
const confirmationChecked = document.getElementById("confirmation").checked;
if (!confirmationChecked) {
alert("회원 탈퇴를 진행하시려면 동의란에 체크해 주세요.");
return;
}
// 탈퇴 사유 가져오기
const selectedReason = document.querySelector('input[name="reason"]:checked');
let reason = "";
if (selectedReason) {
reason = selectedReason.value;
console.log("탈퇴사유 : ", reason);
}
// 기타 선택한 경우, 기타사유 입력값
if (reason === "기타") {
reason = document.getElementById("otherReason").value;
console.log("기타탈퇴사유 : ", reason);
}
// 회원 탈퇴 요청 보내기
$.ajax({
type: "POST",
url: "/withdraw-account",
contentType: "application/json",
data: JSON.stringify({
withdrawalReason: reason // 서버에 전달될 값
}),
success: function(response) {
alert('회원 탈퇴가 정상적으로 처리되었습니다.');
// 페이지 이동
window.location.href = '/';
},
error: function(xhr, status, error) {
console.error('회원 탈퇴 중 오류 발생: ', error);
alert('회원 탈퇴 중 오류가 발생했습니다.');
}
});
});
- 회원 탈퇴 양식 -> 탈퇴 사유 선택하고, 기타 사유일 경우에는 입력 필드를 통해 사유 입력
- 탈퇴 요청시 탈퇴 사유를 ajax를 통해 서버에 전달
// 마이페이지 회원 탈퇴
@PostMapping("/withdraw-account")
public String withdrawAccount(@RequestBody Map<String, String> param,
@CookieValue(value = "Authorization", required = false) String token,
HttpServletResponse response) {
String withdrawalReason = param.get("withdrawalReason"); // 탈퇴사유
String userId = jwtUtil.getUserIdFromToken(token);
try {
// 회원 상태를 DELETED로 업데이트하고, 탈퇴 사유를 저장
memberServiceImpl.updateUserStatus(userId, "DELETED", withdrawalReason);
// 쿠키 삭제 (JWT 토큰 삭제)
Cookie cookie = new Cookie("Authorization", null);
cookie.setMaxAge(0); // 쿠키 만료
cookie.setHttpOnly(true);
cookie.setPath("/"); // 쿠키의 경로 설정, 해당 경로의 모든 요청에 대해 쿠키가 전송됨
response.addCookie(cookie); // 클라이언트에 쿠키 추가/삭제 명령 전송
// 성공적으로 처리되었으면 메인 페이지로 리다이렉트
return "redirect:/";
} catch (Exception e) {
// 오류 발생 시 오류 페이지로 리다이렉트
return "redirect:/error";
}
}
- memberServiceImpl.updateUserStatus(userId, "DELETED", withdrawalReason) : 회원 상태 업데이트를 처리하며, 회원의 상태를 "DELETED"로 전달된 탈퇴 사유를 업데이트하는 메서드
- 매개변수로 받아온 정보들(회원 상태, 탈퇴 사유 등)을 UserDTO 객체의 setter 메서드를 통해 해당 객체에 저장한 후, updateUserStatus(userDTO) 메서드를 호출하여 데이터베이스에 있는 회원 정보를 업데이트 시킨다.
(아래코드 참조)
// 회원 상태 및 탈퇴 사유 업데이트
@Override
public void updateUserStatus(String userId, String status, String reason) {
UserDTO userDTO = memberMapper.findByUserInfo(userId);
userDTO.setUserId(userId); // 조회한 회원의 id
userDTO.setUserStatus(status); // 회원의 상태 DELETED
userDTO.setWithdrawalReason(reason); // 탈퇴 사유
userDTO.setDeletedDate(LocalDateTime.now()); // 탈퇴 일자를 현재 시간으로 설정
memberMapper.updateUserStatus(userDTO); // 데이터베이스에 회원 정보 업데이트
}
// 관리자 계정 여부 확인
boolean isAdmin = "ROLE_SITE_ADMIN".equals(signRole) || "ROLE_ACCOMMODATION_ADMIN".equals(signRole);
// 사용자 탈퇴 여부 확인 (관리자는 무시)
if (!isAdmin && (user == null || "DELETED".equals(user.getUserStatus()))) {
redirectAttributes.addFlashAttribute("error", "탈퇴한 계정이거나 존재하지 않는 사용자 입니다.");
return "redirect:/signform";
}
그리고 위와 같이 로그인요청 메서드(SignIn)에 코드를 추가하여
로그인 요청 시, 관리자 계정 여부를 확인한 후, 관리자가 아닌 일반 사용자가 탈퇴한 상태( 회원상태가 DELETED 또는 존재하지 않는 사용자)인 경우에는 로그인을 제한하도록 했다.
// 탈퇴 상태인 회원 삭제( DELETED 업데이트된 날짜 기준으로 일주일 뒤)
@Override
@Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 (초 분 시 일 월 요일) "0 0/1 * * * ?" : 1분마다 실행
public void deleteUser() {
LocalDateTime oneWeekAgo = LocalDateTime.now().minusWeeks(1); //현재 시간 기준 1주일 전
// LocalDateTime.now()..minusDays(1) //현재 시간 기준 1일 전
try {
int deletedCount = memberMapper.deleteUser(oneWeekAgo); // 1주일 이상 지난 DELETED 회원 삭제
System.out.println(deletedCount + "개의 계정이 삭제되었습니다.");
} catch (Exception e) {
System.err.println("탈퇴 대기 중인 회원 삭제 중 오류 발생: " + e.getMessage());
}
}
- 탈퇴한 회원은 즉시 삭제되지 않고, 데이터베이스에서 탈퇴 후 1주일이 경과하면 계정이 영구 삭제된다
- 매일 자정에 스케줄링 작업이 실행되어 탈퇴일이 일주일 지난 회원을 삭제한다.
*** Spring Boot에서 제공하는 Scheduler 기능으로 구현진행 (따로 의존성 추가 XX)
메인 Application 클래스에 @EnableScheduling 추가하여 Scheduler 활성화
@SpringBootApplication
@EnableScheduling // 스케줄러 활성화
public class SwmApplication {
public static void main(String[] args) {
SpringApplication.run(SwmApplication.class, args);
}
}
@Scheduled를 적용 시키기 위해서는 해당 클래스가 스프링 빈이여야 한다.
즉, @Component나 스프링이 관리하는 빈으로 등록되어 있어야 하며, 만약 해당 클래스가 이미 스프링 빈으로 등록되어 있다면, 추가적인 설정 없이 바로 @Scheduled를 적용할 수 있다.
@Scheduled를 사용하는 메서드 규칙
- **반환 타입은 void**
- 매개변수 사용 XX
- 주기적으로 실행되기 때문에, 작업 스케줄링 시 불필요한 리턴값이나 외부에서의 파라미터를 받지 않게 하기 위한 제한을 준다고 한다.
@Scheduled를 사용하기 위해서는 여러 속성이 있는데 나는 Cron 표현식를 사용
@Scheduled(cron = "표현식") 형태로 사용되며, Cron 표현식은 작업을 언제 실행할지 구체적인 시간을 설정하는 데 사용한다.
cron = "초 분 시 일 월 요일"
"0 0 0 * * *" --> 매일 자정에 실행
"0 0/1 * * * ?" --> 1분마다 실행
이런식으로 적용해 주면 적용된 시간마다 실행이 된다!