-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 인증 관련 경고 누적 시 퇴출 처리 #301
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
Changes from all commits
1e57fb5
75e4d97
d8ddd83
0225aa7
c565af1
bafa384
5d0f7ce
c857105
6cd102a
a58add1
d25d6b7
60dc1bc
77c396b
fb4ea71
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 |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.hrr.backend.domain.report.entity; | ||
|
|
||
| import com.hrr.backend.domain.round.entity.RoundRecord; | ||
| import com.hrr.backend.domain.user.entity.User; | ||
| import com.hrr.backend.domain.verification.entity.Verification; | ||
| import com.hrr.backend.global.common.BaseEntity; | ||
| import jakarta.persistence.*; | ||
| import lombok.AccessLevel; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Entity | ||
| @Builder | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Table( | ||
| name = "weak_verification_report", | ||
| uniqueConstraints = { | ||
| @UniqueConstraint( | ||
| name = "uk_reporter_verification", | ||
| columnNames = {"reporter_id", "verification_id"} | ||
| ) | ||
| } | ||
| ) | ||
| // 부실인증 신고를 위한 엔티티 | ||
| public class WeakVerificationReport extends BaseEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "reporter_id", nullable = false) | ||
| private User reporter; // 신고자 | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "verification_id", nullable = false) | ||
| private Verification verification; // 신고 대상 인증 | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "round_record_id", nullable = false) | ||
| private RoundRecord roundRecord; // 페널티 대상자의 라운드 기록 | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.hrr.backend.domain.report.repository; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import com.hrr.backend.domain.report.entity.WeakVerificationReport; | ||
| import com.hrr.backend.domain.user.entity.User; | ||
| import com.hrr.backend.domain.verification.entity.Verification; | ||
|
|
||
| public interface WeakVerificationReportRepository extends JpaRepository<WeakVerificationReport, Long> { | ||
|
|
||
| // 특정 라운드의 특정 챌린저의 부실인증 개수 조회 | ||
| long countByRoundRecordId(Long roundRecordId); | ||
|
|
||
| // 중복 체크 - 특정 사용자가 특정 인증을 신고한 내역이 있는지 조회 | ||
| boolean existsByReporterAndVerification(User reporter, Verification targetVerification); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,19 @@ | |
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.hrr.backend.domain.challenge.entity.Challenge; | ||
| import com.hrr.backend.domain.report.dto.ReportRequestDto; | ||
| import com.hrr.backend.domain.report.entity.UserReport; | ||
| import com.hrr.backend.domain.report.entity.VerificationPostReport; | ||
| import com.hrr.backend.domain.report.entity.WeakVerificationReport; | ||
| import com.hrr.backend.domain.report.repository.UserReportRepository; | ||
| import com.hrr.backend.domain.report.repository.VerificationPostReportRepository; | ||
| import com.hrr.backend.domain.report.repository.WeakVerificationReportRepository; | ||
| import com.hrr.backend.domain.round.entity.RoundRecord; | ||
| import com.hrr.backend.domain.round.service.RoundRecordService; | ||
| import com.hrr.backend.domain.user.entity.User; | ||
| import com.hrr.backend.domain.user.entity.enums.ChallengeJoinStatus; | ||
| import com.hrr.backend.domain.user.repository.UserChallengeRepository; | ||
| import com.hrr.backend.domain.user.repository.UserRepository; | ||
| import com.hrr.backend.domain.verification.entity.Verification; | ||
| import com.hrr.backend.domain.verification.entity.enums.VerificationStatus; | ||
|
|
@@ -30,6 +37,64 @@ public class ReportServiceImpl implements ReportService { | |
| private final UserRepository userRepository; | ||
| private final UserReportRepository userReportRepository; | ||
|
|
||
| private final UserChallengeRepository userChallengeRepository; | ||
|
|
||
| private final WeakVerificationReportRepository weakVerificationReportRepository; | ||
|
|
||
| private final RoundRecordService roundRecordService; | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void reportWeakVerification(User reporter,Long verificationId) { | ||
| // 신고 대상 조회(Verification) | ||
| Verification targetVerification = verificationRepository.findByIdWithPessimisticLock(verificationId) | ||
| .orElseThrow(() -> new GlobalException(ErrorCode.VERIFICATION_NOT_FOUND)); | ||
|
|
||
| // 차단 확인 (가장 먼저!) | ||
| // 이미 게시글 신고 5회 누적으로 차단된 글이라면 다른 검증을 할 필요도 없이 바로 예외를 던짐 | ||
| if (VerificationStatus.BLOCKED.equals(targetVerification.getStatus())) { | ||
| throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); | ||
| } | ||
|
|
||
| // 피신고자 정보 조회(RoundRecord) | ||
| RoundRecord targetRecord = targetVerification.getRoundRecord(); | ||
|
|
||
| // 자기 신고 방지 | ||
| if (targetVerification.getUserChallenge().getUser().getId().equals(reporter.getId())) { | ||
| throw new GlobalException(ErrorCode.CANNOT_REPORT_OWN_POST); | ||
| } | ||
|
|
||
| // 중복 신고 방지 | ||
| if (weakVerificationReportRepository.existsByReporterAndVerification(reporter, targetVerification)) { | ||
| throw new GlobalException(ErrorCode.ALREADY_REPORTED); | ||
| } | ||
|
|
||
| // 권한 검증: 신고자와 피신고자가 동일 챌린지에 참여 중인지 확인 - 검증 완료 시 다음 단계로 이동 | ||
| validateChallengeParticipation(reporter, targetVerification.getUserChallenge().getChallenge()); | ||
|
|
||
| // 신고 내역 저장 | ||
| WeakVerificationReport report = WeakVerificationReport.builder() | ||
| .reporter(reporter) | ||
| .verification(targetVerification) | ||
| .roundRecord(targetRecord) | ||
| .build(); | ||
| weakVerificationReportRepository.save(report); | ||
|
|
||
| // 경고 횟수를 업데이트 후 퇴출 여부를 결정하는 메소드 호출 | ||
| roundRecordService.synchronizeWarnCount(targetRecord.getId()); | ||
| } | ||
|
Comment on lines
+46
to
+85
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. 이미 퇴출(KICKED)된 사용자의 인증에 대한 부실 인증 신고 처리가 누락되어 있습니다. 현재 로직은 인증 글이 BLOCKED인지만 확인하지만, 해당 인증의 소유자가 이미 챌린지에서 퇴출된 상태인지는 검증하지 않습니다. 퇴출된 사용자의 인증에 대해 신고가 접수되면 불필요한 경고가 누적되고,
🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * 신고자와 피신고자가 같은 챌린지에 참여하고 있는지를 검증 | ||
| * @param reporter 신고자 User 객체 | ||
| * @param challenge 확인하려는 챌린지 | ||
| */ | ||
| private void validateChallengeParticipation(User reporter, Challenge challenge) { | ||
| if (!userChallengeRepository.existsByUserAndChallengeAndStatus(reporter, challenge, ChallengeJoinStatus.JOINED)) { | ||
| throw new GlobalException(ErrorCode.CANNOT_REPORT_OTHER_CHALLENGE_VERIFICATION); | ||
| } | ||
| } | ||
yc3697 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| @Transactional | ||
| public void reportVerificationPost(User reporter, ReportRequestDto request) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.hrr.backend.domain.round.service; | ||
|
|
||
| public interface RoundRecordService { | ||
|
|
||
| // 경고 횟수의 데이터 정합성을 보장하기 위한 계산 확인 메서드 | ||
| void synchronizeWarnCount(Long roundRecordId); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package com.hrr.backend.domain.round.service; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.hrr.backend.domain.challenge.entity.Challenge; | ||
| import com.hrr.backend.domain.challenge.repository.ChallengeRepository; | ||
| import com.hrr.backend.domain.report.repository.WeakVerificationReportRepository; | ||
| import com.hrr.backend.domain.round.entity.RoundRecord; | ||
| import com.hrr.backend.domain.round.repository.RoundRecordRepository; | ||
| import com.hrr.backend.domain.user.entity.enums.ChallengeJoinStatus; | ||
| import com.hrr.backend.domain.user.repository.UserChallengeRepository; | ||
| import com.hrr.backend.domain.verification.repository.VerificationAbsenceLogRepository; | ||
| import com.hrr.backend.global.exception.GlobalException; | ||
| import com.hrr.backend.global.response.ErrorCode; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
| public class RoundRecordServiceImpl implements RoundRecordService { | ||
|
|
||
| private final RoundRecordRepository roundRecordRepository; | ||
|
|
||
| private final WeakVerificationReportRepository weakVerificationReportRepository; | ||
|
|
||
| private final VerificationAbsenceLogRepository verificationAbsenceLogRepository; | ||
|
|
||
| private final UserChallengeRepository userChallengeRepository; | ||
|
|
||
| private final ChallengeRepository challengeRepository; | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void synchronizeWarnCount(Long roundRecordId) { | ||
| // 라운드 기록 조회 | ||
| RoundRecord roundRecord = roundRecordRepository.findByIdWithPessimisticLock(roundRecordId) | ||
| .orElseThrow(() -> new GlobalException(ErrorCode.ROUND_RECORD_NOT_FOUND)); | ||
|
|
||
| // 부실 인증 신고 수와 미인증 로그 수 조회 | ||
| long weakReportCount = weakVerificationReportRepository.countByRoundRecordId(roundRecordId); | ||
| long absenceCount = verificationAbsenceLogRepository.countByRoundRecordId(roundRecordId); | ||
|
|
||
| // 경고 횟수 계산: (부실 신고 / 3) + 미인증 횟수 | ||
| int calculatedWarnCount = (int) (weakReportCount / 3) + (int) absenceCount; | ||
|
|
||
| // 경고 횟수 동기화 및 챌린지 퇴출 여부 판단 - warnCound==3이면 KICKED로 변경되며 퇴출 | ||
| roundRecord.synchronizeWarnCount(calculatedWarnCount); | ||
yc3697 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 필요 시, 추가적인 퇴출 처리 진행 | ||
| if (roundRecord.getUserChallenge().getStatus() == ChallengeJoinStatus.KICKED) { | ||
| processKickOutSideEffects(roundRecord.getUserChallenge().getChallenge().getId()); | ||
| } | ||
| } | ||
|
|
||
| // 퇴출 처리 진행 | ||
| private void processKickOutSideEffects(Long challengeId) { | ||
| Challenge targetChallenge = challengeRepository.findById(challengeId) | ||
| .orElseThrow(() -> new GlobalException(ErrorCode.CHALLENGE_NOT_FOUND)); | ||
|
|
||
| // -- 챌린지 인원 재계산 | ||
| // 실제 JOINED 상태인 인원만 다시 카운트 | ||
| int actualCount = (int) userChallengeRepository.countByChallengeIdAndStatus( | ||
| targetChallenge.getId(), ChallengeJoinStatus.JOINED); | ||
|
|
||
| // 챌린지 엔티티의 인원수 필드를 실제 값으로 덮어쓰기 | ||
| targetChallenge.updateCurrentParticipants(actualCount); | ||
|
|
||
| // --필요 시 추가 작업 진행(알림 발송 등) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.