1+ package com .workingdead .meet .scheduler ;
2+
3+ import com .workingdead .meet .entity .TimePoll ;
4+ import com .workingdead .meet .entity .TimePollStatus ;
5+ import com .workingdead .meet .repository .TimePollRepository ;
6+ import com .workingdead .meet .service .TimePollService ;
7+ import lombok .RequiredArgsConstructor ;
8+ import lombok .extern .slf4j .Slf4j ;
9+ import org .springframework .scheduling .annotation .Scheduled ;
10+ import org .springframework .stereotype .Component ;
11+
12+ import java .time .Duration ;
13+ import java .time .Instant ;
14+ import java .util .List ;
15+
16+ @ Slf4j
17+ @ Component
18+ @ RequiredArgsConstructor
19+ public class TimePollScheduler {
20+
21+ private final TimePollRepository timePollRepository ;
22+ private final TimePollService timePollService ;
23+
24+ // 독촉 단계별 경과 시간 (분)
25+ private static final long AGGREGATE_MINUTES = 3 ;
26+ private static final long REMIND_1_MINUTES = 30 ;
27+ private static final long REMIND_2_MINUTES = 120 ; // 2시간
28+ private static final long REMIND_3_MINUTES = 360 ; // 6시간
29+ private static final long REMIND_4_MINUTES = 720 ; // 12시간
30+ private static final long ULTIMATUM_MINUTES = 1440 ; // 24시간
31+ private static final long AUTO_FINALIZE_MINUTES = 60 ; // 최후통첩 후 60분
32+
33+ /**
34+ * 1분마다 실행 - 모든 진행 중인 투표를 체크
35+ */
36+ @ Scheduled (fixedRate = 60000 )
37+ public void checkTimePoll () {
38+ List <TimePoll > ongoingPolls = timePollRepository .findByStatusAndCreatedAtBefore (
39+ TimePollStatus .ONGOING , Instant .now ());
40+
41+ for (TimePoll poll : ongoingPolls ) {
42+ try {
43+ processOngoingPoll (poll );
44+ } catch (Exception e ) {
45+ log .error ("[TimePollScheduler] Failed to process pollId={}: {}" , poll .getId (), e .getMessage ());
46+ }
47+ }
48+
49+ // 최후통첩 상태 - 자동 확정 체크
50+ List <TimePoll > ultimatumPolls = timePollRepository .findByStatusAndUltimatumSentAtBefore (
51+ TimePollStatus .ULTIMATUM ,
52+ Instant .now ().minus (Duration .ofMinutes (AUTO_FINALIZE_MINUTES )));
53+
54+ for (TimePoll poll : ultimatumPolls ) {
55+ try {
56+ processAutoFinalize (poll );
57+ } catch (Exception e ) {
58+ log .error ("[TimePollScheduler] Auto-finalize failed pollId={}: {}" , poll .getId (), e .getMessage ());
59+ }
60+ }
61+ }
62+
63+ /**
64+ * ONGOING 상태 투표 처리
65+ * - 3분 집계
66+ * - 30분 / 2시간 / 6시간 / 12시간 독촉
67+ * - 24시간 최후통첩
68+ */
69+ private void processOngoingPoll (TimePoll poll ) {
70+ long minutesElapsed = Duration .between (poll .getCreatedAt (), Instant .now ()).toMinutes ();
71+ List <String > pendingNames = timePollService .getPendingNames (poll .getId ());
72+
73+ // 미투표자가 없으면 스킵 (전원 투표 완료는 submit에서 처리)
74+ if (pendingNames .isEmpty ()) {
75+ return ;
76+ }
77+
78+ // --- 3분 집계 ---
79+ if (minutesElapsed >= AGGREGATE_MINUTES && poll .getLastReminderStep () < 1 ) {
80+ sendAggregateMessage (poll );
81+ poll .setLastReminderStep (1 );
82+ timePollRepository .save (poll );
83+ return ;
84+ }
85+
86+ // 3분 전인데 과반 이상 투표 시 집계
87+ if (minutesElapsed < AGGREGATE_MINUTES && poll .getLastReminderStep () < 1 ) {
88+ if (timePollService .isMajoritySubmitted (poll .getId ())) {
89+ sendAggregateMessage (poll );
90+ poll .setLastReminderStep (1 );
91+ timePollRepository .save (poll );
92+ }
93+ return ;
94+ }
95+
96+ // --- 24시간 최후통첩 ---
97+ if (minutesElapsed >= ULTIMATUM_MINUTES && poll .getLastReminderStep () < 6 ) {
98+ sendUltimatumMessage (poll , pendingNames );
99+ poll .setLastReminderStep (6 );
100+ poll .setStatus (TimePollStatus .ULTIMATUM );
101+ poll .setUltimatumSentAt (Instant .now ());
102+ timePollRepository .save (poll );
103+ return ;
104+ }
105+
106+ // --- 독촉 메시지 ---
107+ if (minutesElapsed >= REMIND_4_MINUTES && poll .getLastReminderStep () < 5 ) {
108+ sendReminderMessage (poll , pendingNames , 5 );
109+ } else if (minutesElapsed >= REMIND_3_MINUTES && poll .getLastReminderStep () < 4 ) {
110+ sendReminderMessage (poll , pendingNames , 4 );
111+ } else if (minutesElapsed >= REMIND_2_MINUTES && poll .getLastReminderStep () < 3 ) {
112+ sendReminderMessage (poll , pendingNames , 3 );
113+ } else if (minutesElapsed >= REMIND_1_MINUTES && poll .getLastReminderStep () < 2 ) {
114+ sendReminderMessage (poll , pendingNames , 2 );
115+ }
116+ }
117+
118+ /**
119+ * 최후통첩 60분 경과 - 자동 확정
120+ */
121+ private void processAutoFinalize (TimePoll poll ) {
122+ List <String > pendingNames = timePollService .getPendingNames (poll .getId ());
123+
124+ log .info ("[TimePollScheduler] Auto-finalize pollId={}, pending={}" , poll .getId (), pendingNames );
125+
126+ timePollService .finalize (poll .getId ());
127+
128+ // TODO: "00시로 확정됨" 통보 메시지 발송
129+ }
130+
131+ /**
132+ * 집계 메시지 발송
133+ */
134+ private void sendAggregateMessage (TimePoll poll ) {
135+ var status = timePollService .getStatus (poll .getId ());
136+
137+ if (status .getSubmittedCount () == 0 ) {
138+ log .info ("[TimePollScheduler] Aggregate - no votes yet. pollId={}" , poll .getId ());
139+ } else {
140+ log .info ("[TimePollScheduler] Aggregate - sharing status. pollId={}, voteCount={}" ,
141+ poll .getId (), status .getSubmittedCount ());
142+ }
143+
144+ // TODO: send chatbot message
145+ }
146+
147+ /**
148+ * 독촉 메시지 발송
149+ */
150+ private void sendReminderMessage (TimePoll poll , List <String > pendingNames , int step ) {
151+ String mentions = String .join (", " , pendingNames );
152+
153+ switch (step ) {
154+ case 2 -> // 30min
155+ log .info ("[TimePollScheduler] Reminder 30min. pollId={}, pending={}" ,
156+ poll .getId (), mentions );
157+ case 3 -> // 2h
158+ log .info ("[TimePollScheduler] Reminder 2h. pollId={}, pending={}" ,
159+ poll .getId (), mentions );
160+ case 4 -> // 6h
161+ log .info ("[TimePollScheduler] Reminder 6h. pollId={}, pending={}" ,
162+ poll .getId (), mentions );
163+ case 5 -> // 12h
164+ log .info ("[TimePollScheduler] Reminder 12h. pollId={}, pending={}" ,
165+ poll .getId (), mentions );
166+ }
167+
168+ poll .setLastReminderStep (step );
169+ timePollRepository .save (poll );
170+
171+ // TODO: send chatbot message
172+ }
173+
174+ /**
175+ * 최후통첩 메시지 발송
176+ */
177+ private void sendUltimatumMessage (TimePoll poll , List <String > pendingNames ) {
178+ String mentions = String .join (", " , pendingNames );
179+
180+ log .info ("[TimePollScheduler] Ultimatum sent. pollId={}, pending={}, autoFinalize in 60min" ,
181+ poll .getId (), mentions );
182+
183+ // TODO: send chatbot message with buttons
184+ }
185+ }
0 commit comments