Compare commits
2 Commits
5fec464f90
...
f28ecd853e
| Author | SHA1 | Date | |
|---|---|---|---|
| f28ecd853e | |||
| 8142b8b518 |
@@ -80,6 +80,15 @@ public class AuthController {
|
||||
return current;
|
||||
}
|
||||
|
||||
/** 회원 탈퇴 — 본인 계정과 모든 데이터 삭제 */
|
||||
@DeleteMapping("/me")
|
||||
public ResponseEntity<Void> withdraw(
|
||||
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current,
|
||||
HttpServletRequest request) {
|
||||
authService.withdraw(current.getId(), AuthInterceptor.resolveToken(request));
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/** 현재 사용자 활동 포인트 (최신값 — 게시판 작성으로 수시 변동) */
|
||||
@GetMapping("/points")
|
||||
public java.util.Map<String, Long> points(
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.sb.web.auth.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 회원 탈퇴 시 가계부(BackupMapper) 외의 사용자 데이터 정리 — 게시판/포인트/결제/세션.
|
||||
*/
|
||||
@Mapper
|
||||
public interface WithdrawMapper {
|
||||
|
||||
// 게시판 — 내가 누른 표
|
||||
int deletePostVotesByMember(@Param("memberId") Long memberId);
|
||||
int deleteCommentVotesByMember(@Param("memberId") Long memberId);
|
||||
|
||||
// 게시판 — 내 글에 달린 것들 (남이 단 댓글/표 포함)
|
||||
int deleteCommentVotesOnMyPosts(@Param("memberId") Long memberId);
|
||||
int deletePostVotesOnMyPosts(@Param("memberId") Long memberId);
|
||||
int deletePostTagsOnMyPosts(@Param("memberId") Long memberId);
|
||||
int deleteCommentsOnMyPosts(@Param("memberId") Long memberId);
|
||||
|
||||
// 게시판 — 내 댓글(남의 글에 단 것)과 그 표
|
||||
int deleteCommentVotesOnMyComments(@Param("memberId") Long memberId);
|
||||
int deleteCommentsByAuthor(@Param("memberId") Long memberId);
|
||||
|
||||
// 게시판 — 내 글
|
||||
int deletePostsByAuthor(@Param("memberId") Long memberId);
|
||||
int deleteBoardImagesByMember(@Param("memberId") Long memberId);
|
||||
|
||||
// 포인트 / 결제 / 세션
|
||||
int deletePointHistory(@Param("memberId") Long memberId);
|
||||
int deleteIapPurchases(@Param("memberId") Long memberId);
|
||||
int deleteAuthSessions(@Param("memberId") Long memberId);
|
||||
}
|
||||
@@ -38,6 +38,8 @@ public class AuthService {
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final com.sb.web.admin.service.AppSettingService appSettingService;
|
||||
private final com.sb.web.auth.mapper.AuthSessionMapper authSessionMapper;
|
||||
private final com.sb.web.auth.mapper.WithdrawMapper withdrawMapper;
|
||||
private final com.sb.web.account.mapper.BackupMapper backupMapper;
|
||||
|
||||
/** 구글 OAuth 클라이언트 ID (ID 토큰 aud 검증용). 미설정 시 구글 로그인 비활성. */
|
||||
@org.springframework.beans.factory.annotation.Value("${app.google-client-id:}")
|
||||
@@ -289,6 +291,50 @@ public class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원 탈퇴 — 사용자가 소유한 모든 데이터를 삭제하고 회원을 제거한다.
|
||||
* (게시판 글/댓글/추천 · 가계부 전체 · 포인트 · 결제기록 · 세션 → 회원)
|
||||
*/
|
||||
@Transactional
|
||||
public void withdraw(Long memberId, String token) {
|
||||
Member member = memberMapper.findById(memberId);
|
||||
if (member == null) {
|
||||
throw new ApiException(HttpStatus.NOT_FOUND, "회원을 찾을 수 없습니다.");
|
||||
}
|
||||
// 1) 게시판 — 표/댓글/글/이미지 (참조 순서 고려)
|
||||
withdrawMapper.deletePostVotesByMember(memberId);
|
||||
withdrawMapper.deleteCommentVotesByMember(memberId);
|
||||
withdrawMapper.deleteCommentVotesOnMyPosts(memberId);
|
||||
withdrawMapper.deleteCommentVotesOnMyComments(memberId);
|
||||
withdrawMapper.deleteCommentsOnMyPosts(memberId);
|
||||
withdrawMapper.deleteCommentsByAuthor(memberId);
|
||||
withdrawMapper.deletePostVotesOnMyPosts(memberId);
|
||||
withdrawMapper.deletePostTagsOnMyPosts(memberId);
|
||||
withdrawMapper.deletePostsByAuthor(memberId);
|
||||
withdrawMapper.deleteBoardImagesByMember(memberId);
|
||||
// 2) 가계부(계정) 데이터 (복구 삭제와 동일 순서)
|
||||
backupMapper.deleteEntryTags(memberId);
|
||||
backupMapper.deleteEntries(memberId);
|
||||
backupMapper.deleteRecurrings(memberId);
|
||||
backupMapper.deleteBudgets(memberId);
|
||||
backupMapper.deleteBudgetIncome(memberId);
|
||||
backupMapper.deleteQuickEntries(memberId);
|
||||
backupMapper.deleteInvestTrades(memberId);
|
||||
backupMapper.deleteInvestHoldings(memberId);
|
||||
backupMapper.deleteCategories(memberId);
|
||||
backupMapper.deleteTags(memberId);
|
||||
backupMapper.deleteWallets(memberId);
|
||||
// 3) 포인트 / 결제 / 세션
|
||||
withdrawMapper.deletePointHistory(memberId);
|
||||
withdrawMapper.deleteIapPurchases(memberId);
|
||||
withdrawMapper.deleteAuthSessions(memberId);
|
||||
// 4) 회원 삭제
|
||||
memberMapper.deleteById(memberId);
|
||||
// 5) 현재 세션(Redis) 정리
|
||||
logout(token);
|
||||
log.info("[withdraw] member {} ({}) 및 데이터 전체 삭제", memberId, member.getLoginId());
|
||||
}
|
||||
|
||||
/** 만료된 백업 세션 정리 (매일 새벽 4시) */
|
||||
@org.springframework.scheduling.annotation.Scheduled(cron = "0 0 4 * * *")
|
||||
public void cleanupExpiredSessions() {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.sb.web.auth.mapper.WithdrawMapper">
|
||||
|
||||
<!-- 내가 누른 표 -->
|
||||
<delete id="deletePostVotesByMember">
|
||||
DELETE FROM post_vote WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteCommentVotesByMember">
|
||||
DELETE FROM comment_vote WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
<!-- 내 글에 달린 것들 -->
|
||||
<delete id="deleteCommentVotesOnMyPosts">
|
||||
DELETE cv FROM comment_vote cv
|
||||
JOIN comment c ON c.id = cv.comment_id
|
||||
JOIN post p ON p.id = c.post_id
|
||||
WHERE p.author_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deletePostVotesOnMyPosts">
|
||||
DELETE pv FROM post_vote pv
|
||||
JOIN post p ON p.id = pv.post_id
|
||||
WHERE p.author_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deletePostTagsOnMyPosts">
|
||||
DELETE pt FROM post_tag pt
|
||||
JOIN post p ON p.id = pt.post_id
|
||||
WHERE p.author_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteCommentsOnMyPosts">
|
||||
DELETE c FROM comment c
|
||||
JOIN post p ON p.id = c.post_id
|
||||
WHERE p.author_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
<!-- 내 댓글과 그 표 -->
|
||||
<delete id="deleteCommentVotesOnMyComments">
|
||||
DELETE cv FROM comment_vote cv
|
||||
JOIN comment c ON c.id = cv.comment_id
|
||||
WHERE c.author_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteCommentsByAuthor">
|
||||
DELETE FROM comment WHERE author_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
<!-- 내 글 / 업로드 이미지 -->
|
||||
<delete id="deletePostsByAuthor">
|
||||
DELETE FROM post WHERE author_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteBoardImagesByMember">
|
||||
DELETE FROM board_image WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
<!-- 포인트 / 결제 / 세션 -->
|
||||
<delete id="deletePointHistory">
|
||||
DELETE FROM point_history WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteIapPurchases">
|
||||
DELETE FROM iap_purchase WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
<delete id="deleteAuthSessions">
|
||||
DELETE FROM auth_session WHERE member_id = #{memberId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@@ -47,12 +47,15 @@ class AuthServiceTest {
|
||||
@Mock ValueOperations<String, Object> valueOps;
|
||||
@Mock AppSettingService appSettingService;
|
||||
@Mock AuthSessionMapper authSessionMapper;
|
||||
@Mock com.sb.web.auth.mapper.WithdrawMapper withdrawMapper;
|
||||
@Mock com.sb.web.account.mapper.BackupMapper backupMapper;
|
||||
|
||||
AuthService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new AuthService(memberMapper, passwordEncoder, redisTemplate, appSettingService, authSessionMapper);
|
||||
service = new AuthService(memberMapper, passwordEncoder, redisTemplate, appSettingService, authSessionMapper,
|
||||
withdrawMapper, backupMapper);
|
||||
}
|
||||
|
||||
private Member localMember() {
|
||||
|
||||
Reference in New Issue
Block a user