Skip to content

Commit 4c0021f

Browse files
authored
Merge pull request #248 from DevKor-github/fix/cafeteria-menu
Fix: 학생식당 메뉴 크롤링 및 응답 수정
2 parents de5dafa + d1e47b6 commit 4c0021f

4 files changed

Lines changed: 100 additions & 21 deletions

File tree

src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/HolidayScheduler.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@
66
import java.time.LocalDateTime;
77
import lombok.RequiredArgsConstructor;
88
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.beans.factory.annotation.Value;
910
import org.springframework.scheduling.annotation.Scheduled;
1011
import org.springframework.stereotype.Component;
1112

1213
@Slf4j(topic = "Holiday Scheduler")
1314
@Component
1415
@RequiredArgsConstructor
1516
public class HolidayScheduler {
17+
18+
@Value("${metrics.environment}")
19+
private String env;
20+
1621
private final HolidayService holidayService;
1722
private final RedisLockUtil redisLockUtil;
1823

1924
// @Scheduled(cron = "0 * * * * *") // 테스트용
2025
@Scheduled(cron = "0 0 0 1 * ?") // 매달 1일 자정마다
2126
public void updateHoliday() {
27+
// 배포 서버에서만 실행
28+
if(!env.equals("prod")) return;
29+
2230
redisLockUtil.executeWithLock("lock", 1, 300, () -> {
2331
LocalDateTime nowTime = LocalDateTime.now();
2432
try {

src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.time.temporal.TemporalAdjusters;
1111
import lombok.RequiredArgsConstructor;
1212
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.beans.factory.annotation.Value;
1314
import org.springframework.boot.context.event.ApplicationReadyEvent;
1415
import org.springframework.context.event.EventListener;
1516
import org.springframework.scheduling.annotation.Scheduled;
@@ -19,6 +20,10 @@
1920
@Component
2021
@RequiredArgsConstructor
2122
public class OperatingScheduler {
23+
24+
@Value("${metrics.environment}")
25+
private String env;
26+
2227
private final OperatingService operatingService;
2328
private final HolidayService holidayService;
2429
private final RedisLockUtil redisLockUtil;
@@ -33,6 +38,9 @@ public class OperatingScheduler {
3338
@EventListener(ApplicationReadyEvent.class)
3439
public void updateOperatingTime() {
3540

41+
// 배포 서버에서만 실행
42+
if(!env.equals("prod")) return;
43+
3644
try{
3745
setState();
3846
log.info("운영 시간 업데이트");
@@ -50,6 +58,10 @@ public void updateOperatingTime() {
5058
// @EventListener(ApplicationReadyEvent.class) // 테스트용
5159
@Scheduled(cron = "0 */10 9-18 * * *") // 10분마다
5260
public void updateOperatingDuringPeakHour() {
61+
62+
// 배포 서버에서만 실행
63+
if(!env.equals("prod")) return;
64+
5365
try{
5466
log.info("운영 여부 업데이트");
5567
redisLockUtil.executeWithLock("lock", 1, 300, () -> {
@@ -63,6 +75,10 @@ public void updateOperatingDuringPeakHour() {
6375

6476
@Scheduled(cron = "0 0,30 0-8,19-23 * * *") // 30분마다
6577
public void updateOperating() {
78+
79+
// 배포 서버에서만 실행
80+
if(!env.equals("prod")) return;
81+
6682
try{
6783
log.info("운영 여부 업데이트");
6884
redisLockUtil.executeWithLock("lock", 1, 300, () -> {
@@ -77,6 +93,10 @@ public void updateOperating() {
7793
// 장소 운영 시간 저장 - 건물의 운영 시간에 변동이 있을 경우 1회 작동
7894
@EventListener(ApplicationReadyEvent.class)
7995
public void updatePlaceOperatingTime() {
96+
97+
// 배포 서버에서만 실행
98+
if(!env.equals("prod")) return;
99+
80100
try{
81101
log.info("장소 운영 시간 업데이트");
82102
redisLockUtil.executeWithLock("lock", 1, 300, () -> {

src/main/java/devkor/com/teamcback/domain/place/scheduler/CafeteriaMenuScheduler.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import devkor.com.teamcback.global.redis.RedisLockUtil;
55
import lombok.RequiredArgsConstructor;
66
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.beans.factory.annotation.Value;
78
import org.springframework.boot.context.event.ApplicationReadyEvent;
89
import org.springframework.context.event.EventListener;
910
import org.springframework.scheduling.annotation.Scheduled;
@@ -14,18 +15,28 @@
1415
@RequiredArgsConstructor
1516
public class CafeteriaMenuScheduler {
1617

18+
@Value("${metrics.environment}")
19+
private String env;
20+
1721
private final CafeteriaMenuService cafeteriaMenuService;
1822
private final RedisLockUtil redisLockUtil;
1923

2024
// @EventListener(ApplicationReadyEvent.class)
2125
@Scheduled(cron = "0 10 0 * * *") // 매일 자정 10분마다
2226
public void updateMenus() {
23-
redisLockUtil.executeWithLock("menu_lock", 1, 300, () -> {
2427

25-
System.out.println("--- 고려대학교 학식메뉴 스크래핑 시작 ---");
28+
System.out.println("--- 고려대학교 학식메뉴 스크래핑 시작 ---");
29+
30+
// 배포 서버에서만 실행
31+
if(!env.equals("prod")) {
32+
System.out.println("--- 현재 서버: " + env + " ---");
33+
return;
34+
}
35+
36+
redisLockUtil.executeWithLock("menu_lock", 1, 300, () -> {
2637

2738
// 수당삼양패컬티하우스 송림
28-
cafeteriaMenuService.scrapeMenu(503, 9757L);
39+
cafeteriaMenuService.scrapeMenu(503, 9758L);
2940
// 자연계 학생식당
3041
cafeteriaMenuService.scrapeMenu(504, 3103L);
3142
// 자연계 교직원 식당
@@ -37,12 +48,13 @@ public void updateMenus() {
3748
// 교우회관 학생식당
3849
cafeteriaMenuService.scrapeMenu(507, 7705L);
3950
// 학생회관 학생식당
40-
cafeteriaMenuService.scrapeMenu(508, 9758L);
51+
cafeteriaMenuService.scrapeMenu(508, 9757L);
4152

42-
System.out.println("------------------종료-------------------");
4353
return null;
4454
});
4555

56+
System.out.println("------------------종료-------------------");
57+
4658
}
4759
}
4860

src/main/java/devkor/com/teamcback/domain/place/service/CafeteriaMenuService.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import java.io.IOException;
2020
import java.time.LocalDate;
2121
import java.time.format.DateTimeFormatter;
22-
import java.util.ArrayList;
23-
import java.util.HashMap;
24-
import java.util.List;
25-
import java.util.Map;
22+
import java.util.*;
2623
import java.util.regex.Matcher;
2724
import java.util.regex.Pattern;
2825

@@ -37,6 +34,8 @@ public class CafeteriaMenuService {
3734
private static final String TABLE_SELECTOR = ".table_1 table";
3835
// 식단 메뉴가 없을 때 문구
3936
private static final String NO_MENU_INFO = "등록된 식단내용이(가) 없습니다.";
37+
// 학생식당 식단 종류 순서 지정
38+
private static final List<String> customOrder = Arrays.asList("천원의아침", "천원의아침(테이크아웃)", "중식(한식반상)", "중식(일품반상)");
4039

4140
private final CafeteriaMenuRepository cafeteriaMenuRepository;
4241
private final PlaceRepository placeRepository;
@@ -64,7 +63,35 @@ public GetCafeteriaMenuListRes getCafeteriaMenu(Long placeId, LocalDate startDat
6463
menuByKind.put(cafeteriaMenu.getKind(), cafeteriaMenu.getMenu());
6564
}
6665

67-
menuMap.put(date, menuByKind);
66+
if(placeId == 9757) {
67+
// 식단 종류에 따라 정렬하기
68+
Comparator<String> customComparator = (key1, key2) -> {
69+
int index1 = customOrder.indexOf(key1);
70+
int index2 = customOrder.indexOf(key2);
71+
72+
// 맵에 정의되지 않은 키는 맨 뒤로 보냅니다 (큰 값 부여)
73+
if (index1 == -1) index1 = customOrder.size();
74+
if (index2 == -1) index2 = customOrder.size();
75+
76+
// 순서 비교: index1이 작으면 먼저 옵니다.
77+
int comparison = Integer.compare(index1, index2);
78+
79+
// customOrder에 없는 키끼리의 순서는 원래 문자열 비교(알파벳순)를 따릅니다.
80+
if (comparison == 0 && index1 >= customOrder.size()) {
81+
return key1.compareTo(key2);
82+
}
83+
return comparison;
84+
};
85+
86+
// 정렬된 맵 생성
87+
Map<String, String> sortedMap = new TreeMap<>(customComparator);
88+
sortedMap.putAll(menuByKind);
89+
90+
menuMap.put(date, sortedMap);
91+
}
92+
else {
93+
menuMap.put(date, menuByKind);
94+
}
6895
});
6996

7097
GetCafeteriaMenuListRes res = new GetCafeteriaMenuListRes(placeId, place.getName(), address, place.getDetail(), place.getContact(), menuMap);
@@ -140,7 +167,7 @@ public void scrapeMenu(int page, Long placeId) {
140167
// 6. 분리된 블록을 순회하며 식단 데이터 추출
141168
// 패턴: (조식|중식|석식) (.+?) - 내용물은 하이픈(-) 기준으로 분리됩니다.
142169
Pattern menuItemsPattern = Pattern.compile(
143-
"(조식|중식|석식|식사|요리|파스타/스테이크코스|천원의밥상)\\s*(.*?)(?=\\s*(조식|중식|석식|식사|요리|파스타/스테이크코스|천원의밥상)\\s*|$)",
170+
"(조식|석식|식사|요리|파스타/스테이크 코스|천원의밥상|천원의아침\\(테이크아웃\\)|천원의아침|중식\\(한식반상\\)|중식\\(일품반상\\)|중식 A|중식 B|중식)\\s*(.*?)(?=\\s*(조식|석식|식사|요리|파스타/스테이크 코스|천원의밥상|천원의아침\\(테이크아웃\\)|천원의아침|중식\\(한식반상\\)|중식\\(일품반상\\)|중식 A|중식 B|중식)\\s*|$)",
144171
Pattern.DOTALL
145172
);
146173

@@ -174,6 +201,27 @@ public void scrapeMenu(int page, Long placeId) {
174201

175202
if (!content.isEmpty()) {
176203

204+
// 애기능 메뉴 정리
205+
if(placeId == 3103 || placeId == 2490) {
206+
content = content.replaceAll("\\s*/\\s*", "/");
207+
content = content.replaceAll("(\\s+)의(\\s+)", "의");
208+
content = content.replaceAll("사이드메뉴: ", "사이드메뉴:");
209+
}
210+
211+
// 안암학사 메뉴 정리
212+
if(placeId == 3654) {
213+
if(content.contains("또는")) {
214+
content = content.replaceAll(" ", "");
215+
content = content.replaceAll("또는", "/");
216+
}
217+
content = content.replaceAll("/", " ").trim();
218+
}
219+
220+
// 학생회관 메뉴 정리
221+
if(placeId == 9757) {
222+
content = content.split("-제공되는 메뉴")[0].trim();
223+
}
224+
177225
// 애기능 - 학생식당
178226
if(placeId == 3103) {
179227
if(!content.contains("[학생식당]")) content = NO_MENU_INFO;
@@ -182,7 +230,7 @@ public void scrapeMenu(int page, Long placeId) {
182230
// 애기능 - 교직원식당
183231
else if(placeId == 2490) {
184232
if(!content.contains("[교직원식당]")) content = NO_MENU_INFO;
185-
else content = content.substring(content.lastIndexOf("[교직원식당]") + "[교직원식당]".length()).trim();
233+
else content = content.substring(content.lastIndexOf("[교직원식당]") + "[교직원식당]".length(), content.contains("[학생식당]") && content.lastIndexOf("[학생식당]") > content.lastIndexOf("[교직원식당]")? content.lastIndexOf("[학생식당]") : content.length()).trim();
186234
}
187235

188236
CafeteriaMenu savedMenu = cafeteriaMenuRepository.findByDateAndKindAndPlaceId(date, kind, placeId);
@@ -201,15 +249,6 @@ else if(!savedMenu.getMenu().equals(content)) {
201249
savedMenu.setMenu(content);
202250
}
203251

204-
// 당일에 해당하는 경우 식당 설명 수정
205-
if(date.equals(LocalDate.now())) {
206-
if(!updated) {
207-
place.setDescription(kind + " - " + content);
208-
updated = true;
209-
}
210-
else place.setDescription(place.getDescription() + "\n" + kind + " - " + content);
211-
}
212-
213252
}
214253
}
215254
}

0 commit comments

Comments
 (0)