Skip to content

Commit ea73d98

Browse files
authored
Merge pull request #274 from prgrms-web-devcourse-final-project/fix/async-report-bug(WR9-157)
Fix/async report bug(wr9 157)
2 parents f8fbbf0 + 7e2a00d commit ea73d98

File tree

7 files changed

+183
-65
lines changed

7 files changed

+183
-65
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.crops.warmletter.domain.report.event;
2+
3+
import java.util.Map;
4+
5+
public record AIResultReadyEvent(Long reportId, Map<String, String> result) {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.crops.warmletter.domain.report.event;
2+
3+
import io.crops.warmletter.domain.report.service.ReportService;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.context.event.EventListener;
6+
import org.springframework.scheduling.annotation.Async;
7+
import org.springframework.stereotype.Component;
8+
import org.springframework.transaction.event.TransactionPhase;
9+
import org.springframework.transaction.event.TransactionalEventListener;
10+
11+
@Component
12+
@RequiredArgsConstructor
13+
public class ReportEventHandler {
14+
15+
private final ReportService reportService;
16+
17+
@Async
18+
@EventListener
19+
// @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
20+
public void handleAIResultReady(AIResultReadyEvent event) {
21+
reportService.updateReportWithAIResult(event.reportId(), event.result());
22+
}
23+
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.crops.warmletter.domain.report.service;
2+
3+
import io.crops.warmletter.domain.report.enums.ReasonType;
4+
import io.crops.warmletter.domain.report.event.AIResultReadyEvent;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.context.ApplicationEventPublisher;
7+
import org.springframework.scheduling.annotation.Async;
8+
import org.springframework.stereotype.Service;
9+
10+
import java.util.Map;
11+
12+
@Service
13+
@RequiredArgsConstructor
14+
public class ReportAsyncProcessor {
15+
16+
private final ReportModerationService reportModerationService;
17+
private final ApplicationEventPublisher eventPublisher;
18+
19+
@Async
20+
public void processReportInBackground(Long reportId, String content, ReasonType reasonType, String reason) {
21+
Map<String, String> result = reportModerationService.moderateText(content, reasonType, reason);
22+
eventPublisher.publishEvent(new AIResultReadyEvent(reportId, result));
23+
}
24+
}

src/main/java/io/crops/warmletter/domain/report/service/ReportService.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import io.crops.warmletter.domain.report.dto.response.ReportsResponse;
1818
import io.crops.warmletter.domain.report.dto.response.UpdateReportResponse;
1919
import io.crops.warmletter.domain.report.entity.Report;
20-
import io.crops.warmletter.domain.report.enums.ReasonType;
2120
import io.crops.warmletter.domain.report.enums.ReportStatus;
2221
import io.crops.warmletter.domain.report.enums.ReportType;
2322
import io.crops.warmletter.domain.report.exception.DuplicateReportException;
@@ -33,10 +32,11 @@
3332
import io.crops.warmletter.domain.timeline.enums.AlarmType;
3433
import jakarta.transaction.Transactional;
3534
import lombok.RequiredArgsConstructor;
35+
import lombok.extern.slf4j.Slf4j;
3636
import org.springframework.context.ApplicationEventPublisher;
3737
import org.springframework.data.domain.Page;
3838
import org.springframework.data.domain.Pageable;
39-
import org.springframework.scheduling.annotation.Async;
39+
4040
import org.springframework.stereotype.Service;
4141

4242
import java.time.LocalDateTime;
@@ -45,7 +45,9 @@
4545
import java.util.Map;
4646
import java.util.Optional;
4747

48+
4849
@Service
50+
@Slf4j
4951
@RequiredArgsConstructor
5052
public class ReportService {
5153

@@ -55,7 +57,7 @@ public class ReportService {
5557
private final EventCommentRepository eventCommentRepository;
5658
private final MemberRepository memberRepository;
5759
private final ShareProposalRepository shareProposalRepository;
58-
private final ReportModerationService reportModerationService;
60+
private final ReportAsyncProcessor reportAsyncProcessor;
5961

6062
private final AuthFacade authFacde;
6163

@@ -101,7 +103,6 @@ public Page<ReportsResponse> getAllReports(String reportType, String status, Pag
101103
@Transactional
102104
public ReportResponse createReport(CreateReportRequest request) {
103105
Long memberId = authFacde.getCurrentUserId();
104-
//Long memberId = 1L;
105106
Map<String, String> reportedContentMap = new HashMap<>();
106107
validateRequest(request, memberId, reportedContentMap);
107108
String reportedContent = reportedContentMap.get("content");
@@ -125,19 +126,12 @@ public ReportResponse createReport(CreateReportRequest request) {
125126
}
126127
Report report = builder.build();
127128
Report savedReport = reportRepository.save(report);
128-
129-
processReportInBackground(savedReport.getId(), reportedContent, request.getReasonType(), request.getReason());
129+
reportAsyncProcessor.processReportInBackground(savedReport.getId(), reportedContent, request.getReasonType(), request.getReason());
130130

131131
return new ReportResponse(savedReport);
132132
}
133133

134134
// 신고 AI 판별 비동기 처리
135-
@Async
136-
public void processReportInBackground(Long reportId, String reportedContent, ReasonType reasonType, String reason) {
137-
Map<String, String> moderationResult = reportModerationService.moderateText(reportedContent, reasonType, reason);
138-
updateReportWithAIResult(reportId, moderationResult);
139-
}
140-
141135
@Transactional
142136
public void updateReportWithAIResult(Long reportId, Map<String, String> moderationResult) {
143137
Report report = reportRepository.findById(reportId)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.crops.warmletter.domain.report.event;
2+
3+
import io.crops.warmletter.domain.report.service.ReportService;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.ExtendWith;
7+
import org.mockito.InjectMocks;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
import static org.mockito.Mockito.verify;
11+
import java.util.Map;
12+
13+
@ExtendWith(MockitoExtension.class)
14+
public class ReportEventHandlerTest {
15+
@Mock
16+
ReportService reportService;
17+
18+
@InjectMocks
19+
ReportEventHandler reportEventHandler;
20+
21+
@Test
22+
@DisplayName("hanlder 테스트")
23+
void handleAIResultReady_ShouldInvokeReportService() {
24+
// Given
25+
Long reportId = 1L;
26+
Map<String, String> aiResult = Map.of("status", "RESOLVED");
27+
28+
AIResultReadyEvent event = new AIResultReadyEvent(reportId, aiResult);
29+
30+
// When
31+
reportEventHandler.handleAIResultReady(event);
32+
33+
// Then
34+
verify(reportService).updateReportWithAIResult(reportId, aiResult);
35+
}
36+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.crops.warmletter.domain.report.service;
2+
import io.crops.warmletter.domain.report.enums.ReasonType;
3+
import io.crops.warmletter.domain.report.event.AIResultReadyEvent;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.mockito.ArgumentCaptor;
7+
import org.mockito.InjectMocks;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
import org.springframework.context.ApplicationEventPublisher;
11+
12+
import java.util.Map;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.mockito.Mockito.*;
16+
17+
@ExtendWith(MockitoExtension.class)
18+
class ReportAsyncProcessorTest {
19+
20+
@Mock
21+
ReportModerationService reportModerationService;
22+
23+
@Mock
24+
ApplicationEventPublisher eventPublisher;
25+
26+
@InjectMocks
27+
ReportAsyncProcessor reportAsyncProcessor;
28+
29+
@Test
30+
void processReportInBackground_shouldModerateTextAndPublishEvent() {
31+
// Given
32+
Long reportId = 1L;
33+
String content = "신고 대상 콘텐츠";
34+
ReasonType reasonType = ReasonType.ETC;
35+
String reason = "기타 사유";
36+
37+
Map<String, String> mockResult = Map.of("status", "RESOLVED");
38+
when(reportModerationService.moderateText(content, reasonType, reason)).thenReturn(mockResult);
39+
40+
// When
41+
reportAsyncProcessor.processReportInBackground(reportId, content, reasonType, reason);
42+
43+
// Then
44+
verify(reportModerationService).moderateText(content, reasonType, reason);
45+
46+
// 이벤트 검증 (캡처해서 값까지 확인)
47+
ArgumentCaptor<AIResultReadyEvent> captor = ArgumentCaptor.forClass(AIResultReadyEvent.class);
48+
verify(eventPublisher).publishEvent(captor.capture());
49+
50+
AIResultReadyEvent event = captor.getValue();
51+
assertThat(event.reportId()).isEqualTo(reportId);
52+
assertThat(event.result()).isEqualTo(mockResult);
53+
}
54+
}

0 commit comments

Comments
 (0)