diff --git a/src/main/java/com/example/egobook_be/domain/letters/repository/PlazaLetterRepository.java b/src/main/java/com/example/egobook_be/domain/letters/repository/PlazaLetterRepository.java index 85016a49..3e578593 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/repository/PlazaLetterRepository.java +++ b/src/main/java/com/example/egobook_be/domain/letters/repository/PlazaLetterRepository.java @@ -28,6 +28,8 @@ public interface PlazaLetterRepository extends JpaRepository List findByLetterIdIn(List letterIds); + long countByStatus(PlazaLetterStatus status); + Slice findByReceiverIdOrderByArrivedAtDesc( Long receiverId, Pageable pageable diff --git a/src/main/java/com/example/egobook_be/domain/letters/service/BadWordBlockLogService.java b/src/main/java/com/example/egobook_be/domain/letters/service/BadWordBlockLogService.java new file mode 100644 index 00000000..46e61e12 --- /dev/null +++ b/src/main/java/com/example/egobook_be/domain/letters/service/BadWordBlockLogService.java @@ -0,0 +1,32 @@ +package com.example.egobook_be.domain.letters.service; + +import com.example.egobook_be.domain.letters.entity.BadWordBlockLog; +import com.example.egobook_be.domain.letters.enums.BlockType; +import com.example.egobook_be.domain.letters.repository.BadWordBlockLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BadWordBlockLogService { + + private final BadWordBlockLogRepository badWordBlockLogRepo; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveBlockLog(Long userId, BlockType type, String text, + List badWords, double score) { + badWordBlockLogRepo.save(BadWordBlockLog.builder() + .userId(userId) + .type(type) + .originalText(text) + .badWords(badWords != null ? badWords : List.of()) + .score(score) + .blockedAt(LocalDateTime.now()) + .build()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterDispatchService.java b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterDispatchService.java index f114c6fa..341a931d 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterDispatchService.java +++ b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterDispatchService.java @@ -1,7 +1,9 @@ package com.example.egobook_be.domain.letters.service; +import com.example.egobook_be.domain.letters.entity.LetterSendFailLog; import com.example.egobook_be.domain.letters.entity.PlazaLetter; import com.example.egobook_be.domain.letters.entity.PlazaLetterStatus; +import com.example.egobook_be.domain.letters.repository.LetterSendFailLogRepository; import com.example.egobook_be.domain.letters.repository.PlazaLetterRepository; import com.example.egobook_be.domain.restriction.enums.RestrictionDomainType; import com.example.egobook_be.domain.restriction.service.RestrictionGuardService; @@ -26,6 +28,7 @@ public class PlazaLetterDispatchService { private final PlazaLetterRepository plazaLetterRepository; private final UserRepository userRepository; private final RestrictionGuardService restrictionGuardService; + private final LetterSendFailLogRepository letterFailLogRepo; private static final int BATCH_SIZE = 20; private static final int RECEIVER_POOL_SIZE = 300; @@ -49,7 +52,16 @@ public void dispatchWaitingLetters() { PageRequest.of(0, RECEIVER_POOL_SIZE) ); - if (receiverPool.isEmpty()) return; + if (receiverPool.isEmpty()) { + for (PlazaLetter letter : waitingLetters) { + letterFailLogRepo.save(LetterSendFailLog.builder() + .letterId(letter.getLetterId()) + .failedAt(LocalDateTime.now()) + .reason("수신 가능한 유저 없음") + .build()); + } + return; + } // LETTER 제재 사용자 수신자 풀에서 제외 Set restrictedIds = restrictionGuardService.getActivelyRestrictedUserIds(RestrictionDomainType.LETTER); @@ -57,15 +69,31 @@ public void dispatchWaitingLetters() { receiverPool = receiverPool.stream() .filter(id -> !restrictedIds.contains(id)) .collect(Collectors.toList()); - if (receiverPool.isEmpty()) return; + if (receiverPool.isEmpty()) { + for (PlazaLetter letter : waitingLetters) { + letterFailLogRepo.save(LetterSendFailLog.builder() + .letterId(letter.getLetterId()) + .failedAt(LocalDateTime.now()) + .reason("수신 가능한 유저 없음") + .build()); + } + return; + } } for (PlazaLetter letter : waitingLetters) { Long senderId = letter.getSenderId(); - Long receiverId = pickRandomExcluding(receiverPool, senderId); - if (receiverId == null) continue; + if (receiverId == null) { + // 기존엔 그냥 continue → 실패 로그 저장 추가 + letterFailLogRepo.save(LetterSendFailLog.builder() + .letterId(letter.getLetterId()) + .failedAt(LocalDateTime.now()) + .reason("수신 가능한 유저 없음") + .build()); + continue; + } letter.assignReceiver(receiverId, now, now.plusHours(24)); } log.info("[PlazaLetterDispatchService] dispatchWaitingLetters End"); @@ -96,4 +124,4 @@ private Long pickRandomExcluding(List pool, Long excluded) { } return null; } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java index 7fd82191..50c9329b 100644 --- a/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java +++ b/src/main/java/com/example/egobook_be/domain/letters/service/PlazaLetterService.java @@ -90,6 +90,8 @@ public class PlazaLetterService { private final RestrictionGuardService restrictionGuardService; + private final BadWordBlockLogService badWordBlockLogService; + @Value("${spring.cloud.aws.cloudfront.domain}") private String cloudfrontDomain; @@ -115,14 +117,8 @@ private void enforceWordAiOrThrow(String text, Long userId, BlockType blockType) try { WordDetectResponse res = wordClient.detect(text); if (wordClient.shouldBlock(res)) { - badWordBlockLogRepo.save(BadWordBlockLog.builder() - .userId(userId) - .type(blockType) - .originalText(text) - .badWords(res.getBadWords() != null ? res.getBadWords() : List.of()) - .score(res.getPercentage() / 100.0) - .blockedAt(LocalDateTime.now()) - .build()); + badWordBlockLogService.saveBlockLog(userId, blockType, text, + res.getBadWords(), res.getPercentage() / 100.0); throw new CustomException(LettersErrorCode.AI_MODERATION_FAILED); } } catch (CustomException e) { @@ -290,14 +286,8 @@ public void handleAiResult(Long letterId, WordDetectResponse result, if (wordClient.shouldBlock(result)) { // 욕설 감지 → 차단 로그 저장 후 편지 삭제 - badWordBlockLogRepo.save(BadWordBlockLog.builder() - .userId(letter.getSenderId()) - .type(BlockType.LETTER) - .originalText(result.getText()) - .badWords(result.getBadWords() != null ? result.getBadWords() : List.of()) - .score(result.getPercentage() / 100.0) - .blockedAt(LocalDateTime.now()) - .build()); + badWordBlockLogService.saveBlockLog(letter.getSenderId(), BlockType.LETTER, + result.getText(), result.getBadWords(), result.getPercentage() / 100.0); plazaLetterRepository.delete(letter); return; } diff --git a/src/main/java/com/example/egobook_be/domain/user/service/AdminContentService.java b/src/main/java/com/example/egobook_be/domain/user/service/AdminContentService.java index 4fce76d6..9b1638f5 100644 --- a/src/main/java/com/example/egobook_be/domain/user/service/AdminContentService.java +++ b/src/main/java/com/example/egobook_be/domain/user/service/AdminContentService.java @@ -244,8 +244,7 @@ public LetterStatusRes getLetterStatus(LocalDate startDate, LocalDate endDate) { long sentCount = plazaLetterRepo.countByCreatedAtBetweenAndStatuses( start, end, List.of(PlazaLetterStatus.REPLIED, PlazaLetterStatus.AI_REPLIED)); - long waitingCount = plazaLetterRepo.countByCreatedAtBetweenAndStatus( - start, end, PlazaLetterStatus.WAITING); + long waitingCount = plazaLetterRepo.countByStatus(PlazaLetterStatus.WAITING); long aiReplyCount = plazaLetterRepo.countByCreatedAtBetweenAndStatus( start, end, PlazaLetterStatus.AI_REPLIED);