From 6f0b5d192cd1c7a1201e4ae69e80f1db423b7c81 Mon Sep 17 00:00:00 2001 From: UNGGU0704 Date: Wed, 18 Jun 2025 15:17:50 +0900 Subject: [PATCH] =?UTF-8?q?Update:=20review=20eventhub=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExternalReviewEventHubAdapter.java | 64 +++++-------------- .../repository/ReviewJpaRepository.java | 18 +----- .../ExternalIntegrationInteractor.java | 4 ++ 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/review/src/main/java/com/ktds/hi/review/infra/gateway/ExternalReviewEventHubAdapter.java b/review/src/main/java/com/ktds/hi/review/infra/gateway/ExternalReviewEventHubAdapter.java index 9469637..93e062c 100644 --- a/review/src/main/java/com/ktds/hi/review/infra/gateway/ExternalReviewEventHubAdapter.java +++ b/review/src/main/java/com/ktds/hi/review/infra/gateway/ExternalReviewEventHubAdapter.java @@ -64,38 +64,25 @@ public class ExternalReviewEventHubAdapter { } /** - * 외부 리뷰 이벤트 수신 처리 (모든 파티션 검사 추가) + * 외부 리뷰 이벤트 수신 처리 (기존 로직 유지) */ private void listenToExternalReviewEvents() { log.info("외부 리뷰 이벤트 수신 시작"); try { - // 🔥 모든 파티션 ID 설정 (기존 "4"번 포함) - String[] partitionIds = {"0", "1", "2", "3", "4"}; - log.info("처리할 파티션: {}", String.join(", ", partitionIds)); - while (isRunning) { - // 🔥 모든 파티션 순회 처리 - for (String partitionId : partitionIds) { - try { - Iterable events = externalReviewEventConsumer.receiveFromPartition( - partitionId, // 🔥 동적 파티션 ID - 100, // 최대 이벤트 수 (기존과 동일) - EventPosition.earliest(), // 시작 위치 (기존과 동일) - Duration.ofSeconds(30) // 타임아웃 (기존과 동일) - ); + Iterable events = externalReviewEventConsumer.receiveFromPartition( + "4", // 파티션 ID (기존과 동일) + 100, // 최대 이벤트 수 (기존과 동일) + EventPosition.earliest(), // 시작 위치 (기존과 동일) + Duration.ofSeconds(10) // 타임아웃 (기존과 동일) + ); - for (PartitionEvent partitionEvent : events) { - handleExternalReviewEvent(partitionEvent); - } - - } catch (Exception e) { - log.error("파티션 {} 처리 중 오류 (계속 진행): {}", partitionId, e.getMessage()); - // 개별 파티션 오류는 무시하고 다음 파티션 계속 처리 - } + for (PartitionEvent partitionEvent : events) { + handleExternalReviewEvent(partitionEvent); } - Thread.sleep(1000); // 기존과 동일 + Thread.sleep(1000); } } catch (InterruptedException e) { log.info("외부 리뷰 이벤트 수신 중단됨"); @@ -199,32 +186,11 @@ public class ExternalReviewEventHubAdapter { String content = extractContent(reviewData); String memberNickname = extractMemberNickname(reviewData); - // 1차 중복 체크: storeId + content 기반 - if (reviewJpaRepository.existsByStoreIdAndContent(storeId, content)) { - log.debug("중복 리뷰 스킵 (content 기준): storeId={}, content={}", - storeId, content.substring(0, Math.min(50, content.length()))); - return null; - } - // 2차 중복 체크: storeId + memberNickname + content 기반 (더 정확) - if (reviewJpaRepository.existsByStoreIdAndMemberNicknameAndContent(storeId, memberNickname, content)) { - log.debug("중복 리뷰 스킵 (nickname+content 기준): storeId={}, nickname={}", - storeId, memberNickname); - return null; - } - - // 3차 중복 체크: 스팸 방지 (동일 닉네임이 5개 이상 리뷰 작성 시 차단) - Long reviewCount = reviewJpaRepository.countByStoreIdAndMemberNickname(storeId, memberNickname); - if (reviewCount >= 5) { - log.debug("스팸 가능성으로 리뷰 스킵: storeId={}, nickname={}, count={}", - storeId, memberNickname, reviewCount); - return null; - } - - // 4차 중복 체크: 최근 1시간 내 동일 닉네임 리뷰 체크 - LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1); - if (reviewJpaRepository.existsByStoreIdAndMemberNicknameAfterTime(storeId, memberNickname, oneHourAgo)) { - log.debug("1시간 내 중복 리뷰 스킵: storeId={}, nickname={}", storeId, memberNickname); + // 1차 중복 체크: content 기반 (전체 시스템) + if (reviewJpaRepository.existsByContent(content)) { + log.debug("중복 리뷰 스킵 (content 기준): content={}", + content.substring(0, Math.min(50, content.length()))); return null; } @@ -346,4 +312,6 @@ public class ExternalReviewEventHubAdapter { log.info("처리된 이벤트 ID 캐시 정리 완료: 현재 크기={}", processedEventIds.size()); } } + + } \ No newline at end of file diff --git a/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java b/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java index 4d16598..a0ffece 100644 --- a/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java +++ b/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java @@ -39,24 +39,10 @@ public interface ReviewJpaRepository extends JpaRepository { @Param("cutoffDate") LocalDateTime cutoffDate, Pageable pageable ); - /** - * 특정 매장의 닉네임별 리뷰 개수 조회 (스팸 체크용) + * 전체 시스템에서 동일한 Content 중복 체크 (매장 무관) */ - @Query("SELECT COUNT(r) FROM ReviewEntity r WHERE r.storeId = :storeId AND r.memberNickname = :memberNickname") - Long countByStoreIdAndMemberNickname(@Param("storeId") Long storeId, @Param("memberNickname") String memberNickname); - - /** - * 강화된 중복 체크: 매장ID + 닉네임 + 내용으로 중복 체크 - * 외부 리뷰에서 동일한 닉네임이 같은 내용의 리뷰를 작성했는지 확인 - */ - boolean existsByStoreIdAndMemberNicknameAndContent(Long storeId, String memberNickname, String content); - - /** - * 닉네임 기반 중복 체크: 매장ID + 닉네임으로 기존 리뷰 존재 확인 - * 동일한 닉네임이 이미 리뷰를 작성했는지 체크 - */ - boolean existsByStoreIdAndMemberNickname(Long storeId, String memberNickname); + boolean existsByContent(String content); /** * 시간 기반 중복 체크: 특정 시간 이후 동일한 닉네임의 리뷰 존재 확인 diff --git a/store/src/main/java/com/ktds/hi/store/biz/service/ExternalIntegrationInteractor.java b/store/src/main/java/com/ktds/hi/store/biz/service/ExternalIntegrationInteractor.java index 7ed8caa..4568bd9 100644 --- a/store/src/main/java/com/ktds/hi/store/biz/service/ExternalIntegrationInteractor.java +++ b/store/src/main/java/com/ktds/hi/store/biz/service/ExternalIntegrationInteractor.java @@ -4,6 +4,7 @@ import com.azure.messaging.eventhubs.EventData; import com.azure.messaging.eventhubs.EventDataBatch; import com.azure.messaging.eventhubs.EventHubClientBuilder; import com.azure.messaging.eventhubs.EventHubProducerClient; +import com.azure.messaging.eventhubs.models.SendOptions; import com.fasterxml.jackson.databind.ObjectMapper; import com.ktds.hi.store.biz.usecase.in.ExternalIntegrationUseCase; import com.ktds.hi.store.biz.usecase.out.ExternalPlatformPort; @@ -249,6 +250,9 @@ public class ExternalIntegrationInteractor implements ExternalIntegrationUseCase EventData eventData = new EventData(payloadJson); + // 🔥 파티션 4번에 보내기 위한 SendOptions 설정 + SendOptions sendOptions = new SendOptions(); + sendOptions.setPartitionId("4"); // EventDataBatch 사용 EventDataBatch batch = producer.createBatch(); if (batch.tryAdd(eventData)) {