-
Notifications
You must be signed in to change notification settings - Fork 8
fix: 탈퇴한 사용자가 물리적 삭제가 되지 않았던 문제를 해결한다 #574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,20 @@ | ||
| package com.example.solidconnection.chat.repository; | ||
|
|
||
| import com.example.solidconnection.chat.domain.ChatParticipant; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| public interface ChatParticipantRepository extends JpaRepository<ChatParticipant, Long> { | ||
|
|
||
| boolean existsByChatRoomIdAndSiteUserId(long chatRoomId, long siteUserId); | ||
|
|
||
| Optional<ChatParticipant> findByChatRoomIdAndSiteUserId(long chatRoomId, long siteUserId); | ||
|
|
||
| void deleteAllBySiteUserId(long siteUserId); | ||
whqtker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Query("SELECT cp.id FROM ChatParticipant cp WHERE cp.siteUserId = :siteUserId") | ||
| List<Long> findAllIdsBySiteUserId(@Param("siteUserId") long siteUserId); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,32 @@ | ||||||||||||||||
| package com.example.solidconnection.scheduler; | ||||||||||||||||
|
|
||||||||||||||||
| import com.example.solidconnection.application.repository.ApplicationRepository; | ||||||||||||||||
| import com.example.solidconnection.chat.repository.ChatParticipantRepository; | ||||||||||||||||
| import com.example.solidconnection.chat.repository.ChatReadStatusRepository; | ||||||||||||||||
| import com.example.solidconnection.community.comment.repository.CommentRepository; | ||||||||||||||||
| import com.example.solidconnection.community.post.repository.PostLikeRepository; | ||||||||||||||||
| import com.example.solidconnection.community.post.repository.PostRepository; | ||||||||||||||||
| import com.example.solidconnection.location.country.repository.InterestedCountryRepository; | ||||||||||||||||
| import com.example.solidconnection.location.region.repository.InterestedRegionRepository; | ||||||||||||||||
| import com.example.solidconnection.mentor.repository.MentorApplicationRepository; | ||||||||||||||||
| import com.example.solidconnection.mentor.repository.MentorRepository; | ||||||||||||||||
| import com.example.solidconnection.mentor.repository.MentoringRepository; | ||||||||||||||||
| import com.example.solidconnection.news.repository.LikedNewsRepository; | ||||||||||||||||
| import com.example.solidconnection.news.repository.NewsRepository; | ||||||||||||||||
| import com.example.solidconnection.report.repository.ReportRepository; | ||||||||||||||||
| import com.example.solidconnection.s3.service.S3Service; | ||||||||||||||||
| import com.example.solidconnection.score.repository.GpaScoreRepository; | ||||||||||||||||
| import com.example.solidconnection.score.repository.LanguageTestScoreRepository; | ||||||||||||||||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||||||||||||||||
| import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||||||||||||||||
| import com.example.solidconnection.siteuser.repository.UserBlockRepository; | ||||||||||||||||
| import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; | ||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||
| import java.util.List; | ||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||
|
|
||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||
| @Component | ||||||||||||||||
|
|
@@ -16,14 +36,67 @@ public class UserRemovalScheduler { | |||||||||||||||
| public static final int ACCOUNT_RECOVER_DURATION = 30; | ||||||||||||||||
|
|
||||||||||||||||
| private final SiteUserRepository siteUserRepository; | ||||||||||||||||
| private final InterestedCountryRepository interestedCountryRepository; | ||||||||||||||||
| private final InterestedRegionRepository interestedRegionRepository; | ||||||||||||||||
| private final CommentRepository commentRepository; | ||||||||||||||||
| private final PostRepository postRepository; | ||||||||||||||||
| private final PostLikeRepository postLikeRepository; | ||||||||||||||||
| private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; | ||||||||||||||||
| private final ApplicationRepository applicationRepository; | ||||||||||||||||
| private final GpaScoreRepository gpaScoreRepository; | ||||||||||||||||
| private final LanguageTestScoreRepository languageTestScoreRepository; | ||||||||||||||||
| private final MentorRepository mentorRepository; | ||||||||||||||||
| private final MentoringRepository mentoringRepository; | ||||||||||||||||
| private final NewsRepository newsRepository; | ||||||||||||||||
| private final LikedNewsRepository likedNewsRepository; | ||||||||||||||||
| private final ChatParticipantRepository chatParticipantRepository; | ||||||||||||||||
| private final ChatReadStatusRepository chatReadStatusRepository; | ||||||||||||||||
| private final ReportRepository reportRepository; | ||||||||||||||||
| private final UserBlockRepository userBlockRepository; | ||||||||||||||||
| private final MentorApplicationRepository mentorApplicationRepository; | ||||||||||||||||
| private final S3Service s3Service; | ||||||||||||||||
|
|
||||||||||||||||
| /* | ||||||||||||||||
| * 탈퇴 후 계정 복구 기한까지 방문하지 않은 사용자를 삭제한다. | ||||||||||||||||
| * */ | ||||||||||||||||
| @Scheduled(cron = EVERY_MIDNIGHT) | ||||||||||||||||
| @Transactional | ||||||||||||||||
| public void scheduledUserRemoval() { | ||||||||||||||||
| LocalDate cutoffDate = LocalDate.now().minusDays(ACCOUNT_RECOVER_DURATION); | ||||||||||||||||
| List<SiteUser> usersToRemove = siteUserRepository.findUsersToBeRemoved(cutoffDate); | ||||||||||||||||
| siteUserRepository.deleteAll(usersToRemove); | ||||||||||||||||
|
|
||||||||||||||||
| usersToRemove.forEach(this::deleteUserAndRelatedData); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
62
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. 트랜잭션 경계 문제
🔎 개별 사용자별 트랜잭션 처리로 개선 @Scheduled(cron = EVERY_MIDNIGHT)
- @Transactional
public void scheduledUserRemoval() {
LocalDate cutoffDate = LocalDate.now().minusDays(ACCOUNT_RECOVER_DURATION);
List<SiteUser> usersToRemove = siteUserRepository.findUsersToBeRemoved(cutoffDate);
usersToRemove.forEach(this::deleteUserAndRelatedData);
}
+ @Transactional
private void deleteUserAndRelatedData(SiteUser user) {
+ try {
long siteUserId = user.getId();
likedNewsRepository.deleteAllBySiteUserId(siteUserId);
// ... 나머지 삭제 로직
siteUserRepository.delete(user);
+ } catch (Exception e) {
+ log.error("Failed to delete user and related data for userId: {}", user.getId(), e);
+ // 개별 사용자 삭제 실패 시에도 다음 사용자 처리 계속
+ }
}
🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| private void deleteUserAndRelatedData(SiteUser user) { | ||||||||||||||||
| long siteUserId = user.getId(); | ||||||||||||||||
|
|
||||||||||||||||
| likedNewsRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| newsRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
|
|
||||||||||||||||
| postLikeRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| commentRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| postRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
|
|
||||||||||||||||
| mentoringRepository.deleteAllByMenteeId(siteUserId); | ||||||||||||||||
| mentorRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| mentorApplicationRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
|
|
||||||||||||||||
| List<Long> chatParticipantIds = chatParticipantRepository.findAllIdsBySiteUserId(siteUserId); | ||||||||||||||||
| chatReadStatusRepository.deleteAllByChatParticipantIdIn(chatParticipantIds); | ||||||||||||||||
| chatParticipantRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| reportRepository.deleteAllByReporterId(siteUserId); | ||||||||||||||||
| userBlockRepository.deleteAllByBlockerIdOrBlockedId(siteUserId, siteUserId); | ||||||||||||||||
|
|
||||||||||||||||
| applicationRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| gpaScoreRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| languageTestScoreRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| likedUnivApplyInfoRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| interestedCountryRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
| interestedRegionRepository.deleteAllBySiteUserId(siteUserId); | ||||||||||||||||
|
|
||||||||||||||||
| s3Service.deleteExProfile(siteUserId); | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. S3 삭제 실패 처리 Line 94에서 S3 프로필 삭제 중 오류가 발생하면 현재 트랜잭션이 롤백되어 DB 삭제도 취소됩니다. S3 삭제는 외부 서비스 호출이므로:
🔎 S3 삭제 오류 격리 처리+ try {
s3Service.deleteExProfile(siteUserId);
+ } catch (Exception e) {
+ log.warn("Failed to delete S3 profile for userId: {}, continuing with user deletion", siteUserId, e);
+ // S3 삭제 실패해도 사용자 삭제는 진행
+ }
siteUserRepository.delete(user);또는 S3 삭제를 별도의 보상 트랜잭션으로 처리하는 방안을 고려하세요. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| siteUserRepository.delete(user); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.