1010import com .back .web7_9_codecrete_be .domain .users .entity .User ;
1111import com .back .web7_9_codecrete_be .domain .users .repository .UserRepository ;
1212import lombok .RequiredArgsConstructor ;
13+ import lombok .extern .slf4j .Slf4j ;
1314import org .springframework .scheduling .annotation .EnableScheduling ;
1415import org .springframework .scheduling .annotation .Scheduled ;
1516import org .springframework .stereotype .Service ;
2324import java .util .List ;
2425import java .util .Map ;
2526
27+ @ Slf4j
2628@ Service
2729@ EnableScheduling
2830@ RequiredArgsConstructor
@@ -45,13 +47,26 @@ private List<Concert> getTodayTicketingConcerts() {
4547
4648
4749 private Map <Long , List <TicketOffice >> getAllTicketOfficesMapFromConcerts (List <Concert > concerts ) {
50+ // 예매처 정보를 빠르게 가져오기 위한 Map 지정
4851 Map <Long , List <TicketOffice >> ticketOfficeMap = new HashMap <>();
49- List <TicketOffice > ticketOffices ;
52+ //공연에 대한 모든 예매처 정보를 가져와서 저장
53+ List <TicketOffice > ticketOffices = ticketOfficeRepository .findAllByConcerts (concerts );
54+
55+ for (TicketOffice ticketOffice : ticketOffices ) {
56+ log .info ("예매처 조회" );
57+ Long concertId = ticketOffice .getConcert ().getConcertId ();
58+ List <TicketOffice > ticketOfficeList = ticketOfficeMap .getOrDefault (concertId , new ArrayList <>());
59+ ticketOfficeList .add (ticketOffice );
60+ ticketOfficeMap .put (concertId , ticketOfficeList );
61+ }
62+
5063 // 줄일 수 없긴 개뿔, 가능하네.
64+ /*
5165 for (Concert concert : concerts) {
5266 ticketOffices = ticketOfficeRepository.getTicketOfficesByConcert(concert);
5367 ticketOfficeMap.put(concert.getConcertId(), ticketOffices);
5468 }
69+ */
5570 // 예매처 맵 반환
5671 return ticketOfficeMap ;
5772 }
@@ -65,6 +80,7 @@ private Map<String, List<Long>> getSendingEmailFromLikeUser(List<Concert> concer
6580
6681 List <ConcertLike > concertLikes = concertLikeRepository .getTodayConcertTicketingLikes (startOfToday , endOfToday );
6782 for (ConcertLike concertLike : concertLikes ) {
83+ log .info ("사용자 email 조회" );
6884 // map에 해당 사용자 email의 ConcertId list 가져오기, 없다면 새로은 arraylist 사용
6985 List <Long > tempList = emailMap .getOrDefault (concertLike .getUser ().getEmail (), new ArrayList <>());
7086 // 임시 리스트에 concertId 추가
@@ -110,30 +126,46 @@ public String sendTodayTicketingConcertsNotifyingEmail() {
110126
111127 for (String targetEmail : emailMap .keySet ()) {
112128
113- StringBuilder sb = new StringBuilder ();
114-
115- //위 타이틀 부분
116- sb .append ("""
117- <!doctype html>
118- <html lang="ko">
119- <body style="margin:0;padding:0;background-color:#fafafa;
120- font-family:-apple-system,BlinkMacSystemFont,system-ui,Roboto,Helvetica Neue,Segoe UI,Apple SD Gothic Neo,Noto Sans KR,Malgun Gothic,sans-serif;">
121-
122- <div style="max-width:680px;margin:40px auto;background:#ffffff;
123- border-radius:16px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">
124-
125- <!-- Header -->
126- <div style="padding:32px;background:#1a1a1a;color:#ffffff;">
127- <h1 style="margin:0 0 8px 0;font-size:26px;font-weight:700;">
128- 🎟 %s 오늘의 공연 예매 알림
129- </h1>
130- <div style="font-size:14px;opacity:0.85;">
131- 예매 시작 공연을 알려드립니다.
132- </div>
133- </div>
134-
135- <div style="padding:32px;">
129+ StringBuilder htmlStringBuilder = new StringBuilder ();
130+ StringBuilder textStringBuilder = new StringBuilder ();
131+
132+ // 전체 타이틀 부분(html)
133+ htmlStringBuilder .append ("""
134+ <!doctype html>
135+ <html lang="ko">
136+ <head>
137+ <meta charset="UTF-8" />
138+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
139+ <title>공연 예매 알림</title>
140+ </head>
141+ <body style="margin:0;padding:0;background-color:#fafafa;
142+ font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Malgun Gothic',
143+ 'Apple SD Gothic Neo','Noto Sans KR',sans-serif;line-height:1.5;">
144+
145+ <div style="max-width:680px;margin:40px auto;background:#ffffff;
146+ border-radius:16px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">
147+
148+ <!-- Header -->
149+ <div style="padding:32px;background:linear-gradient(135deg,#1a1a1a 0%%,#2d2d2d 100%%);
150+ color:#ffffff;">
151+ <h1 style="margin:0 0 8px 0;font-size:26px;font-weight:700;letter-spacing:-0.5px;">
152+ 🎟 %s 오늘의 공연 예매 알림
153+ </h1>
154+ <div style="font-size:14px;opacity:0.85;">
155+ 예매 시작 공연을 알려드립니다.
156+ </div>
157+ </div>
158+
159+ <div style="padding:32px;">
160+ """ .formatted (today ));
161+ //전체 타이틀 부분(text);
162+ textStringBuilder .append ("""
163+ [NCB] 공연 예매 알림입니다.
164+ %s 오늘의 공연 예매 알림
165+ 예매 시작 공연을 알려드립니다.
166+ ---------------------------------------------------------
136167 """ .formatted (today ));
168+
137169 // 개별 공연 내용 작성
138170 for (Long concertId : emailMap .get (targetEmail )) {
139171
@@ -144,78 +176,142 @@ public String sendTodayTicketingConcertsNotifyingEmail() {
144176 posterImage = "https://via.placeholder.com/640x360?text=No+Image" ;
145177 }
146178
147- sb .append ("""
148- <div style="border:1px solid #e8e8e8;border-radius:12px;
149- padding:24px;margin-bottom:20px;">
150- <img
151- src="%s"
152- alt="공연 포스터"
153- style="width:100%%;height:200px;
154- object-fit:cover;border-radius:8px;
155- margin-bottom:16px;"
156- />
179+ // 날짜 정보 파싱 (예: 2025년 12월 17일 → 17, DEC)
180+ String day = concert .getTicketTime ().format (DateTimeFormatter .ofPattern ("dd" ));
181+ String month = concert .getTicketTime ().format (DateTimeFormatter .ofPattern ("MMM" , java .util .Locale .ENGLISH )).toUpperCase ();
182+
183+ // 개별 공연 HTML 시작부
184+ htmlStringBuilder .append ("""
185+ <!-- 공연 카드 시작 -->
186+ <div style="border:1px solid #e8e8e8;border-radius:12px;
187+ padding:24px;margin-bottom:20px;background:#ffffff;">
188+
189+ <!-- 포스터 이미지 -->
190+ <img src="%s" alt="공연 포스터"
191+ style="display:block;width:100%%;height:200px;
192+ object-fit:cover;border-radius:8px;margin-bottom:16px;" />
193+
194+ <!-- 공연 헤더 -->
195+ <div style="display:flex;align-items:flex-start;gap:20px;margin-bottom:16px;">
196+
197+ <!-- 날짜 박스 -->
198+ <div style="width:64px;text-align:center;padding:12px 0;
199+ background:#f5f5f5;border-radius:8px;flex-shrink:0;">
200+ <div style="font-size:28px;font-weight:700;color:#1a1a1a;line-height:1;">
201+ %s
202+ </div>
203+ <div style="font-size:13px;color:#666;margin-top:4px;font-weight:500;">
204+ %s
205+ </div>
206+ </div>
207+
208+ <!-- 공연 정보 -->
209+ <div style="flex:1;">
157210 <div style="font-size:16px;font-weight:600;color:#1a1a1a;margin-bottom:8px;">
158211 %s
159212 </div>
160- <div style="font-size:13px;color:#666;margin-bottom:12px;">
161- ⏰ 예매 시간 :
162- <strong>%s</strong>
213+ <div style="font-size:12px;color:#666;">
214+ ⏰ %s
163215 </div>
164- """ .formatted (
216+ </div>
217+ </div>
218+ """ .formatted (
165219 posterImage ,
220+ day ,
221+ month ,
166222 concert .getName (),
167223 concert .getTicketTime ()
168224 .format (DateTimeFormatter .ofPattern ("yyyy년 MM월 dd일 HH시 mm분" ))
169225 ));
226+ // 개별 공연 text 시작부
227+ textStringBuilder .append ("""
228+ 공연명: %s
229+ 티켓팅 시간: %s
230+
231+ """ .formatted (concert .getName (), concert .getTicketTime ().format (DateTimeFormatter .ofPattern ("yyyy년 MM월 dd일 HH시 mm분" ))));
232+
233+ // 공연 예매처 반복 처리
170234 for (TicketOffice ticketOffice : ticketOfficesMap .get (concertId )) {
171- sb .append ("""
172- <div style="background:#f8f8f8;padding:12px 16px;
173- border-radius:8px;margin-bottom:8px;font-size:13px;">
174-
175- <div style="font-weight:600;color:#1a1a1a;">
235+ // 개별 예매처 html
236+ htmlStringBuilder .append ("""
237+ <!-- 예매처 -->
238+ <div style="background:#f8f8f8;padding:16px;border-radius:8px;margin-bottom:8px;">
239+ <div style="display:flex;justify-content:space-between;
240+ align-items:center;flex-wrap:wrap;gap:12px;">
241+
242+ <div style="flex:1;min-width:150px;">
243+ <div style="font-size:12px;color:#888;margin-bottom:4px;">
244+ 예매처
245+ </div>
246+ <div style="font-size:14px;font-weight:600;color:#1a1a1a;">
176247 %s
177248 </div>
178-
179- <a href="%s" target="_blank"
180- style="color:#1a1a1a;text-decoration:underline;font-size:12px;">
181- 예매 페이지 바로가기
182- </a>
183249 </div>
184- """ .formatted (
250+
251+ <a href="%s" target="_blank"
252+ style="display:inline-block;padding:8px 24px;
253+ background:#1a1a1a;color:#ffffff;text-decoration:none;
254+ border-radius:6px;font-size:14px;font-weight:600;">
255+ 예매하기
256+ </a>
257+ </div>
258+ </div>
259+ """ .formatted (
185260 ticketOffice .getTicketOfficeName (),
186261 ticketOffice .getTicketOfficeUrl ()
187262 ));
263+ // 개별 예매처 text
264+ textStringBuilder .append ("""
265+ 예매처: %s
266+ 예매링크: %s
267+ """ .formatted (ticketOffice .getTicketOfficeName (), ticketOffice .getTicketOfficeUrl ()));
188268 }
189269
190- sb .append ("</div>" );
270+ htmlStringBuilder .append ("</div>" ); // 공연 카드 종료
271+ textStringBuilder .append ("""
272+ ---------------------------------------------------------
273+ """ ); // text 자름
191274 }
192- sb .append ("""
193- <div style="background:#f8f8f8;padding:20px;border-radius:8px;margin-top:24px;">
194- <div style="font-size:14px;font-weight:bold;margin-bottom:6px;">
195- ℹ️ 유의사항
196- </div>
197- <div style="font-size:12px;color:#666;">
198- 공연 정보는 각 공연의 상황에 따라 변경될 수 있으니
199- 예매 전 반드시 확인해주세요.
200- </div>
201- </div>
202-
203- </div>
204-
205- <div style="text-align:center;padding:32px;background:#fafafa;
206- color:#999;font-size:12px;line-height:1.6;">
207- 이 메일은 자동으로 발송되었습니다.<br/>
208- © 2025 Concert Notification Service
275+
276+ // 공연 마지막 바닥 부분 처리
277+ htmlStringBuilder .append ("""
278+ <!-- 유의사항 -->
279+ <div style="background:#f8f8f8;padding:20px 24px;margin-top:24px;border-radius:8px;">
280+ <div style="display:flex;align-items:center;gap:6px;
281+ font-size:14px;font-weight:600;color:#1a1a1a;margin-bottom:8px;">
282+ ⚠️ 유의사항
209283 </div>
210-
284+ <div style="font-size:12px;color:#666;line-height:1.6;">
285+ 공연 정보는 각 공연의 상황에 따라 변경될 수 있으니
286+ 예매 전 반드시 확인해주세요.
211287 </div>
212- </body>
213- </html>
288+ </div>
289+
290+ </div>
291+
292+ <!-- Footer -->
293+ <div style="text-align:center;padding:32px;background:#fafafa;
294+ color:#999;font-size:12px;line-height:1.6;">
295+ 이 메일은 자동으로 발송되었습니다.<br/>
296+ © 2025 Concert Notification Service
297+ </div>
298+
299+ </div>
300+ </body>
301+ </html>
302+ """ );
303+ textStringBuilder .append ("""
304+ 유의사항 : 공연 정보는 각 공연의 상황에 따라 변경될 수 있으니 예매 전 반드시 확인해주세요.
214305 """ );
215306
216- String contents = sb .toString ();
217- emailService .sendNotifyEmail (targetEmail , contents );
307+
308+ String htmlContent = htmlStringBuilder .toString ();
309+ String textContent = textStringBuilder .toString ();
310+ emailService .sendNotifyEmail (targetEmail , htmlContent ,textContent );
218311 }
219- return totalConcertsCount + "건의 공연을" + totalEmailCount + "명의 사용자에게 전송했습니다." ;
312+ log .info ("일일 공연 예매 오픈 알림 : " + totalConcertsCount + "건의 공연을 " + totalEmailCount + "명의 사용자에게 전송했습니다." );
313+ return totalConcertsCount + "건의 공연을 " + totalEmailCount + "명의 사용자에게 전송했습니다." ;
220314 }
221315}
316+
317+
0 commit comments