[Feat] 회원탈퇴기능 추가#77
Conversation
|
@code rabbit |
개요사용자 계정 삭제 기능이 도메인 이벤트 기반으로 구현되었습니다. 새로운 변경사항
순서도sequenceDiagram
participant User as 사용자
participant Controller as UserAuthController
participant Service as UserService
participant Repo as UserRepository
participant Publisher as ApplicationEventPublisher
participant NotifListener as NotificationUserEventListener
participant ProjListener as ProjectUserEventListener
participant RecListener as RecruitingUserEventListener
User->>Controller: DELETE /api/users/me
Controller->>Service: deleteUser(userId)
Service->>Repo: delete(user)
Repo->>Repo: 사용자 삭제 완료
Service->>Publisher: publish(UserDeletedEvent)
par 병렬 이벤트 처리
Publisher->>NotifListener: handleUserDeletedEvent
NotifListener->>NotifListener: 알림 설정 삭제
NotifListener->>NotifListener: 알림 삭제
and
Publisher->>ProjListener: handleUserDeletedEvent
ProjListener->>ProjListener: 프로젝트 정리
ProjListener->>ProjListener: OAuth 토큰 삭제
and
Publisher->>RecListener: handleUserDeletedEvent
RecListener->>RecListener: 댓글 삭제
end
예상 코드 리뷰 소요 시간🎯 3 (보통) | ⏱️ ~20분 토끼의 축시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/com/campusform/server/identity/application/service/UserService.java`:
- Around line 126-136: Re-enable and harden the S3 profile-image cleanup in the
user-deletion flow: in UserService (the method that removes the User row),
restore the TransactionSynchronizationManager.registerSynchronization block that
reads user.getProfileImageUrl(), and in afterCommit call
s3Service.deleteFile(profileImageUrl) so file deletion happens only after DB
commit; add a null-check for profileImageUrl, wrap the s3Service.deleteFile call
in a try/catch to log failures (use log.info/log.error) and avoid failing the
transaction, and ensure the block references TransactionSynchronization
(afterCommit) so cleanup runs asynchronously post-commit.
In
`@src/main/java/com/campusform/server/recruiting/infrastructure/persistence/CommentRepository.java`:
- Around line 51-54: The derived repository method deleteByAuthorId in
CommentRepository triggers entity loading and per-entity removes; replace it
with a true bulk DELETE by annotating a custom repository method (in
CommentRepository) with `@Modifying` and `@Query`("DELETE FROM Comment c WHERE
c.authorId = :authorId") and ensure the call runs in a `@Transactional` context
(either annotate the repository method or call from a `@Transactional` service)
and add imports for org.springframework.data.jpa.repository.Modifying and Query;
this makes the delete a JPQL bulk delete and avoids loading all Comment entities
into memory.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 449fd742-f2c6-403a-83c5-bdb3e73cc952
📒 Files selected for processing (12)
src/main/java/com/campusform/server/global/event/UserDeletedEvent.javasrc/main/java/com/campusform/server/identity/application/service/UserService.javasrc/main/java/com/campusform/server/identity/domain/repository/UserRepository.javasrc/main/java/com/campusform/server/identity/infrastructure/persistence/UserRepositoryImpl.javasrc/main/java/com/campusform/server/identity/presentation/UserAuthController.javasrc/main/java/com/campusform/server/notification/application/service/NotificationUserEventListener.javasrc/main/java/com/campusform/server/notification/domain/repository/UserNotificationSettingsRepository.javasrc/main/java/com/campusform/server/notification/infrastructure/persistence/NotificationJpaRepository.javasrc/main/java/com/campusform/server/notification/infrastructure/persistence/UserNotificationSettingsRepositoryImpl.javasrc/main/java/com/campusform/server/project/application/service/ProjectUserEventListener.javasrc/main/java/com/campusform/server/recruiting/application/service/RecruitingUserEventListener.javasrc/main/java/com/campusform/server/recruiting/infrastructure/persistence/CommentRepository.java
| // // 프로필 이미지가 있다면 S3에서도 삭제 | ||
| // String profileImageUrl = user.getProfileImageUrl(); | ||
| // if (profileImageUrl != null) { | ||
| // TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | ||
| // @Override | ||
| // public void afterCommit() { | ||
| // s3Service.deleteFile(profileImageUrl); | ||
| // log.info("S3 프로필 이미지 삭제 완료 (회원탈퇴): {}", profileImageUrl); | ||
| // } | ||
| // }); | ||
| // } |
There was a problem hiding this comment.
회원탈퇴 시 프로필 이미지 삭제 로직 주석 처리 상태는 개인정보 잔존 리스크입니다.
Line 126-136이 비활성화되어 탈퇴 후에도 S3 프로필 이미지가 남을 수 있습니다. 계정 삭제 흐름에서는 DB row뿐 아니라 외부 저장소의 개인정보도 함께 정리되어야 합니다.
🔧 제안 수정안
- // // 프로필 이미지가 있다면 S3에서도 삭제
- // String profileImageUrl = user.getProfileImageUrl();
- // if (profileImageUrl != null) {
- // TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- // `@Override`
- // public void afterCommit() {
- // s3Service.deleteFile(profileImageUrl);
- // log.info("S3 프로필 이미지 삭제 완료 (회원탈퇴): {}", profileImageUrl);
- // }
- // });
- // }
+ // 프로필 이미지가 있다면 트랜잭션 커밋 후 S3에서도 삭제
+ String profileImageUrl = user.getProfileImageUrl();
+ if (profileImageUrl != null) {
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ `@Override`
+ public void afterCommit() {
+ try {
+ s3Service.deleteFile(profileImageUrl);
+ log.info("S3 프로필 이미지 삭제 완료 (회원탈퇴): {}", profileImageUrl);
+ } catch (Exception e) {
+ log.error("S3 프로필 이미지 삭제 실패 (회원탈퇴): {}", profileImageUrl, e);
+ }
+ }
+ });
+ }원하시면 이 부분을 테스트 가능한 구조(예: 삭제 전담 컴포넌트 분리 + 이벤트/모킹 테스트)로 정리해 새 이슈 템플릿까지 같이 제안드릴 수 있습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // // 프로필 이미지가 있다면 S3에서도 삭제 | |
| // String profileImageUrl = user.getProfileImageUrl(); | |
| // if (profileImageUrl != null) { | |
| // TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| // @Override | |
| // public void afterCommit() { | |
| // s3Service.deleteFile(profileImageUrl); | |
| // log.info("S3 프로필 이미지 삭제 완료 (회원탈퇴): {}", profileImageUrl); | |
| // } | |
| // }); | |
| // } | |
| // 프로필 이미지가 있다면 트랜잭션 커밋 후 S3에서도 삭제 | |
| String profileImageUrl = user.getProfileImageUrl(); | |
| if (profileImageUrl != null) { | |
| TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | |
| `@Override` | |
| public void afterCommit() { | |
| try { | |
| s3Service.deleteFile(profileImageUrl); | |
| log.info("S3 프로필 이미지 삭제 완료 (회원탈퇴): {}", profileImageUrl); | |
| } catch (Exception e) { | |
| log.error("S3 프로필 이미지 삭제 실패 (회원탈퇴): {}", profileImageUrl, e); | |
| } | |
| } | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/campusform/server/identity/application/service/UserService.java`
around lines 126 - 136, Re-enable and harden the S3 profile-image cleanup in the
user-deletion flow: in UserService (the method that removes the User row),
restore the TransactionSynchronizationManager.registerSynchronization block that
reads user.getProfileImageUrl(), and in afterCommit call
s3Service.deleteFile(profileImageUrl) so file deletion happens only after DB
commit; add a null-check for profileImageUrl, wrap the s3Service.deleteFile call
in a try/catch to log failures (use log.info/log.error) and avoid failing the
transaction, and ensure the block references TransactionSynchronization
(afterCommit) so cleanup runs asynchronously post-commit.
| /** | ||
| * 특정 작성자의 모든 댓글 삭제 | ||
| */ | ||
| void deleteByAuthorId(Long authorId); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Spring Data JPA에서 derived deleteBy... 메서드가 벌크 DELETE로 실행되는지, 아니면 엔티티 단위 삭제 경로를 타는지 공식 문서 기준으로 확인해줘.
💡 Result:
Spring Data JPA에서 derived deleteBy... 메서드(예: deleteByRoleId)는 벌크 DELETE로 실행되지 않고, 엔티티 단위 삭제 경로를 타며, lifecycle 이벤트(@PreRemove 등)를 보장하기 위해 SELECT 쿼리로 엔티티를 먼저 로드한 후 CrudRepository.delete(Iterable)를 호출하여 하나씩 삭제합니다. 공식 문서에 명시되어 있습니다.
Citations:
탈퇴 정리 경로는 벌크 DELETE로 명시하는 편이 필수입니다.
Line 54의 파생 메서드 deleteByAuthorId()는 내부적으로 모든 해당 엔티티를 먼저 SELECT로 로드한 후, 라이프사이클 이벤트(@PreRemove 등)를 보장하기 위해 엔티티를 하나씩 삭제합니다. 탈퇴 일괄정리처럼 대량의 댓글을 삭제해야 하는 경우 메모리 오버헤드와 성능 저하가 발생하므로, @Modifying + JPQL DELETE로 진정한 벌크 DELETE를 명시하는 것이 필수적입니다.
변경 제안
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
@@
- void deleteByAuthorId(Long authorId);
+ `@Modifying`(clearAutomatically = true, flushAutomatically = true)
+ `@Query`("DELETE FROM Comment c WHERE c.authorId = :authorId")
+ int deleteByAuthorId(`@Param`("authorId") Long authorId);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * 특정 작성자의 모든 댓글 삭제 | |
| */ | |
| void deleteByAuthorId(Long authorId); | |
| /** | |
| * 특정 작성자의 모든 댓글 삭제 | |
| */ | |
| `@Modifying`(clearAutomatically = true, flushAutomatically = true) | |
| `@Query`("DELETE FROM Comment c WHERE c.authorId = :authorId") | |
| int deleteByAuthorId(`@Param`("authorId") Long authorId); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/campusform/server/recruiting/infrastructure/persistence/CommentRepository.java`
around lines 51 - 54, The derived repository method deleteByAuthorId in
CommentRepository triggers entity loading and per-entity removes; replace it
with a true bulk DELETE by annotating a custom repository method (in
CommentRepository) with `@Modifying` and `@Query`("DELETE FROM Comment c WHERE
c.authorId = :authorId") and ensure the call runs in a `@Transactional` context
(either annotate the repository method or call from a `@Transactional` service)
and add imports for org.springframework.data.jpa.repository.Modifying and Query;
this makes the delete a JPQL bulk delete and avoids loading all Comment entities
into memory.
관련 이슈번호
Key Changes
To Reviewers
Summary by CodeRabbit
릴리스 노트