33import io .crops .warmletter .domain .letter .entity .Letter ;
44import io .crops .warmletter .domain .letter .enums .Status ;
55import io .crops .warmletter .domain .letter .repository .LetterRepository ;
6- import io .crops .warmletter .domain .member .repository .MemberRepository ;
76import io .crops .warmletter .domain .timeline .dto .request .NotificationRequest ;
87import io .crops .warmletter .domain .timeline .dto .response .LetterAlarmResponse ;
98import io .crops .warmletter .domain .timeline .enums .AlarmType ;
10- import io .crops .warmletter .domain .timeline .facade .NotificationFacade ;
11- import jakarta .transaction .Transactional ;
129import lombok .RequiredArgsConstructor ;
1310import lombok .extern .slf4j .Slf4j ;
11+ import org .springframework .beans .factory .annotation .Qualifier ;
1412import org .springframework .context .ApplicationEventPublisher ;
1513import org .springframework .context .annotation .Configuration ;
14+ import org .springframework .core .task .AsyncTaskExecutor ;
1615import org .springframework .scheduling .annotation .Scheduled ;
16+ import org .springframework .transaction .annotation .Propagation ;
17+ import org .springframework .transaction .annotation .Transactional ;
1718
1819import java .time .LocalDateTime ;
1920import java .time .format .DateTimeFormatter ;
21+ import java .util .ArrayList ;
2022import java .util .List ;
2123import java .util .Map ;
24+ import java .util .concurrent .CompletableFuture ;
2225import java .util .stream .Collectors ;
2326
2427@ Slf4j
2730public class DeliverySchedule {
2831
2932 private final LetterRepository letterRepository ;
30-
3133 private final ApplicationEventPublisher notificationPublisher ;
34+ @ Qualifier ("deliveryTaskExecutor" )
35+ private final AsyncTaskExecutor taskExecutor ;
3236
33- @ Transactional
3437 @ Scheduled (cron = "0 */1 * * * *" , zone = "Asia/Seoul" )
3538 public void processDeliveryCompletion () {
3639 String currentTime = LocalDateTime .now ().format (DateTimeFormatter .ofPattern ("yyyy-MM-dd HH:mm:ss" ));
3740 log .info ("--------- 배송 완료 처리 시작: {} ---------" , currentTime );
3841
3942 LocalDateTime now = LocalDateTime .now ();
4043
41- // 배송 완료 조건을 만족하는 편지 목록 조회 (배송 중이면서 배송 완료 시간이 현재보다 이전인 편지)
44+ // 배송 완료 조건을 만족하는 편지 목록 조회
4245 List <Letter > lettersToComplete = letterRepository .findByStatusAndDeliveryCompletedAtLessThanEqual (
4346 Status .IN_DELIVERY , now );
44- // lettersToComplete 조건을 만족하는 편지를 보낸 사람의 zipCode 조회
47+
48+ // zipCode 조회
4549 List <LetterAlarmResponse > zipCodeData = letterRepository .findZipCodeByLettersToComplete (now );
4650 Map <Long , String > senderZipCodes = zipCodeData .stream ()
4751 .collect (Collectors .toMap (
4852 LetterAlarmResponse ::getWriterId ,
4953 LetterAlarmResponse ::getZipCode ,
50- (existingZipCode , newZipCode ) -> existingZipCode // 중복 키 발생 시 기존 값 사용
54+ (existingZipCode , newZipCode ) -> existingZipCode
5155 ));
5256
5357 if (!lettersToComplete .isEmpty ()) {
5458 log .info ("배송 완료 처리할 편지 수: {}" , lettersToComplete .size ());
5559
56- // 각 편지의 상태를 DELIVERED로 변경
60+ // 결과 추적을 위한 CompletableFuture 목록
61+ List <CompletableFuture <Boolean >> futures = new ArrayList <>();
62+
63+ // 각 편지를 비동기적으로 처리
5764 for (Letter letter : lettersToComplete ) {
58- letter .updateStatus (Status .DELIVERED );
59- log .info ("편지 ID: {} 배송 완료 처리됨" , letter .getId ());
60- // 도착 알림 전송
61- notificationPublisher .publishEvent (NotificationRequest .builder ()
62- .senderZipCode (senderZipCodes .get (letter .getWriterId ()))
63- .receiverId (letter .getReceiverId ())
64- .alarmType (AlarmType .LETTER )
65- .data (letter .getId ().toString ())
66- .build ());
65+ CompletableFuture <Boolean > future = CompletableFuture .supplyAsync (() -> {
66+ try {
67+ processLetter (letter , senderZipCodes .get (letter .getWriterId ()));
68+ log .info ("편지 ID: {} 배송 완료 처리됨" , letter .getId ());
69+ return true ;
70+ } catch (Exception e ) {
71+ log .error ("편지 ID: {} 배송 완료 처리 실패: {}" , letter .getId (), e .getMessage (), e );
72+ return false ;
73+ }
74+ }, taskExecutor );
75+
76+ futures .add (future );
6777 }
6878
69- // 변경사항 저장
70- letterRepository .saveAll (lettersToComplete );
71- log .info ("총 {}개의 편지 배송 완료 처리됨" , lettersToComplete .size ());
79+ // 모든 비동기 작업 완료 대기 (옵션)
80+ CompletableFuture .allOf (futures .toArray (new CompletableFuture [0 ])).join ();
81+
82+ // 성공/실패 편지 수 계산
83+ long successCount = futures .stream ().filter (f -> {
84+ try {
85+ return f .get ();
86+ } catch (Exception e ) {
87+ return false ;
88+ }
89+ }).count ();
90+
91+ log .info ("총 {}개 중 {}개의 편지 배송 완료 처리 성공" , lettersToComplete .size (), successCount );
7292 } else {
7393 log .info ("배송 완료 처리할 편지가 없습니다." );
7494 }
7595 log .info ("--------- 배송 완료 처리 완료 ---------" );
7696 }
97+
98+ @ Transactional (propagation = Propagation .REQUIRES_NEW )
99+ public void processLetter (Letter letter , String senderZipCode ) {
100+ // 편지 상태 업데이트
101+ letter .updateStatus (Status .DELIVERED );
102+ letterRepository .save (letter );
103+
104+ // 알림 전송
105+ if (letter .getReceiverId () != null && senderZipCode != null ) {
106+ notificationPublisher .publishEvent (NotificationRequest .builder ()
107+ .senderZipCode (senderZipCode )
108+ .receiverId (letter .getReceiverId ())
109+ .alarmType (AlarmType .LETTER )
110+ .data (letter .getId ().toString ())
111+ .build ());
112+ }
113+ }
77114}
0 commit comments