diff --git a/src/main/java/backend/knowhow/domain/alert/client/KoroadApiClient.java b/src/main/java/backend/knowhow/domain/alert/client/KoroadApiClient.java index 21fdb9e..dec4d26 100644 --- a/src/main/java/backend/knowhow/domain/alert/client/KoroadApiClient.java +++ b/src/main/java/backend/knowhow/domain/alert/client/KoroadApiClient.java @@ -25,7 +25,7 @@ public class KoroadApiClient { @Value("${koroad.base-url}") private String baseUrl; - private static final int searchYear = 2015; // 최근 3년 기준 연도 + private static final int searchYear = 2024; // 최근 3년 기준 연도 private static final String PATH_OLDMAN = "/frequentzoneOldman/getRestFrequentzoneOldman"; private static final String PATH_CHILD = "/frequentzoneChild/getRestFrequentzoneChild"; private static final String PATH_SCHOOL = "/frequentzoneChildSchool/getRestFrequentzoneChildSchool"; @@ -49,7 +49,7 @@ public List fetchHotspots( String path = resolvePath(type); String url = baseUrl + path - + "?serviceKey=" + apiKey + + "?ServiceKey=" + apiKey + "&searchYearCd=" + searchYear + "&siDo=" + siDo + "&guGun=" + guGun diff --git a/src/main/java/backend/knowhow/domain/alert/controller/KoroadHotspotSyncController.java b/src/main/java/backend/knowhow/domain/alert/controller/KoroadHotspotSyncController.java index a0452ae..5f8e684 100644 --- a/src/main/java/backend/knowhow/domain/alert/controller/KoroadHotspotSyncController.java +++ b/src/main/java/backend/knowhow/domain/alert/controller/KoroadHotspotSyncController.java @@ -1,6 +1,7 @@ package backend.knowhow.domain.alert.controller; import backend.knowhow.domain.alert.service.KoroadHotspotSyncService; +import backend.knowhow.domain.alert.util.KoroadRegion; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -15,63 +16,32 @@ public class KoroadHotspotSyncController { private final KoroadHotspotSyncService syncService; - // 서울 시도 코드 - private static final int SEOUL_SIDO = 11; - - // 서울 전체 구/군 코드 목록 (Koroad 기준) - private static final List SEOUL_GUGUN_CODES = List.of( - 110, // 종로구 - 140, // 중구 - 170, // 용산구 - 200, // 성동구 - 215, // 광진구 - 230, // 동대문구 - 260, // 중랑구 - 290, // 성북구 - 305, // 강북구 - 320, // 도봉구 - 350, // 노원구 - 380, // 은평구 - 410, // 서대문구 - 440, // 마포구 - 470, // 양천구 - 500, // 강서구 - 530, // 구로구 - 545, // 금천구 - 560, // 영등포구 - 590, // 동작구 - 620, // 관악구 - 650, // 서초구 - 680, // 강남구 - 710, // 송파구 - 740 // 강동구 - ); - /** * 서울 전체 Koroad 다발지역 동기화 * * 예) - * - 보행노인 사고 다발지역 전체: apiType=oldman&type=OLD_MAN - * - 보행어린이: apiType=child&type=CHILD - * - 어린이보호구역: apiType=school&type=SCHOOL + * - 보행노인 사고 다발지역 전체: type=oldman + * - 보행어린이: type=child + * - 어린이보호구역: type=school */ - @PostMapping("/sync/seoul") + @PostMapping("/sync") public String syncSeoul( - @RequestParam String apiType, // KoroadApiClient 에서 사용하는 구분자 ("oldman", "child", "school" 등) - @RequestParam String type // DB KoroadHotspot.type 에 저장할 값 ("OLD_MAN", "CHILD", "SCHOOL" 등) + @RequestParam String type // KoroadApiClient 에서 사용하는 구분자 ("oldman", "child", "school" 등) ) { - log.info("start"); + log.info("sync start"); int total = 0; - for (Integer guGun : SEOUL_GUGUN_CODES) { - syncService.syncHotspots(apiType, type, SEOUL_SIDO, guGun); - log.info("{} finish", guGun); - total++; + for (var entry : KoroadRegion.SIDO_GUGUN.entrySet()) { + int siDo = entry.getKey(); + for (int guGun : entry.getValue()) { + syncService.syncHotspots(type, siDo, guGun); + total++; + } } - return String.format("Synced Seoul Koroad hotspots for apiType=%s, type=%s, gugunCount=%d", - apiType, type, total); + return String.format("Synced Koroad hotspots for type=%s, gugunCount=%d", + type, total); } } diff --git a/src/main/java/backend/knowhow/domain/alert/domain/KoroadHotspot.java b/src/main/java/backend/knowhow/domain/alert/domain/KoroadHotspot.java index 7b4e89f..038a33c 100644 --- a/src/main/java/backend/knowhow/domain/alert/domain/KoroadHotspot.java +++ b/src/main/java/backend/knowhow/domain/alert/domain/KoroadHotspot.java @@ -23,7 +23,7 @@ public class KoroadHotspot { * - SCHOOL : 어린이보호구역 */ @Column(nullable = false, length = 20) - private String type; + private KoroadType type; @Column(name = "afos_fid", length = 50) private String afosFid; diff --git a/src/main/java/backend/knowhow/domain/alert/domain/KoroadType.java b/src/main/java/backend/knowhow/domain/alert/domain/KoroadType.java new file mode 100644 index 0000000..0ad8dff --- /dev/null +++ b/src/main/java/backend/knowhow/domain/alert/domain/KoroadType.java @@ -0,0 +1,30 @@ +package backend.knowhow.domain.alert.domain; + +import backend.knowhow.global.common.exception.BaseException; +import backend.knowhow.global.common.response.ErrorType; + +import java.util.Arrays; + +public enum KoroadType { + + OLD_MAN("oldman"), + CHILD("child"), + SCHOOL("school"); + + private final String param; + + KoroadType(String param) { + this.param = param; + } + + public String param() { + return param; + } + + public static KoroadType from(String value) { + return Arrays.stream(values()) + .filter(type -> type.param.equalsIgnoreCase(value)) + .findFirst() + .orElseThrow(() -> new BaseException(ErrorType.BAD_REQUEST)); + } +} diff --git a/src/main/java/backend/knowhow/domain/alert/mapper/KoroadHotspotMapper.java b/src/main/java/backend/knowhow/domain/alert/mapper/KoroadHotspotMapper.java index 0e5161f..0ba29d0 100644 --- a/src/main/java/backend/knowhow/domain/alert/mapper/KoroadHotspotMapper.java +++ b/src/main/java/backend/knowhow/domain/alert/mapper/KoroadHotspotMapper.java @@ -25,9 +25,9 @@ public AlertItem toAlert( int m = (int) Math.round(dist); String typeLabel = switch (hotspot.getType()) { - case "OLD_MAN" -> "보행 노인 교통사고 다발지역"; - case "CHILD" -> "보행 어린이 교통사고 다발지역"; - case "SCHOOL" -> "어린이 보호구역 내 교통사고 다발지역"; + case OLD_MAN -> "보행 노인 교통사고 다발지역"; + case CHILD -> "보행 어린이 교통사고 다발지역"; + case SCHOOL -> "어린이 보호구역 내 교통사고 다발지역"; default -> "교통사고 다발지역"; }; diff --git a/src/main/java/backend/knowhow/domain/alert/repository/KoroadHotspotRepository.java b/src/main/java/backend/knowhow/domain/alert/repository/KoroadHotspotRepository.java index f200b7e..9c89086 100644 --- a/src/main/java/backend/knowhow/domain/alert/repository/KoroadHotspotRepository.java +++ b/src/main/java/backend/knowhow/domain/alert/repository/KoroadHotspotRepository.java @@ -1,15 +1,16 @@ package backend.knowhow.domain.alert.repository; import backend.knowhow.domain.alert.domain.KoroadHotspot; +import backend.knowhow.domain.alert.domain.KoroadType; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface KoroadHotspotRepository extends JpaRepository { - List findByType(String type); + List findByType(KoroadType type); - List findByTypeAndSiDoAndGuGun(String type, Integer siDo, Integer guGun); + List findByTypeAndSiDoAndGuGun(KoroadType type, Integer siDo, Integer guGun); - void deleteByTypeAndSiDoAndGuGun(String type, Integer siDo, Integer guGun); + void deleteByTypeAndSiDoAndGuGun(KoroadType type, Integer siDo, Integer guGun); } diff --git a/src/main/java/backend/knowhow/domain/alert/service/KoroadHotspotSyncService.java b/src/main/java/backend/knowhow/domain/alert/service/KoroadHotspotSyncService.java index 7550e98..848d9aa 100644 --- a/src/main/java/backend/knowhow/domain/alert/service/KoroadHotspotSyncService.java +++ b/src/main/java/backend/knowhow/domain/alert/service/KoroadHotspotSyncService.java @@ -2,6 +2,7 @@ import backend.knowhow.domain.alert.client.KoroadApiClient; import backend.knowhow.domain.alert.domain.KoroadHotspot; +import backend.knowhow.domain.alert.domain.KoroadType; import backend.knowhow.domain.alert.dto.external.KoroadBaseResponse; import backend.knowhow.domain.alert.repository.KoroadHotspotRepository; import lombok.RequiredArgsConstructor; @@ -21,17 +22,19 @@ public class KoroadHotspotSyncService { // Koroad API → MySQL 저장 (해당 type + 시/도 + 구/군 기준으로 전체 갱신) @Transactional - public void syncHotspots(String apiType, String type, Integer siDo, Integer guGun) { + public void syncHotspots(String type, Integer siDo, Integer guGun) { // Koroad에서 신규 데이터 가져오기 - List items = koroadApiClient.fetchHotspots(apiType, siDo, guGun); + List items = koroadApiClient.fetchHotspots(type, siDo, guGun); if (items.isEmpty()) { log.warn("[KoroadHotspotSyncService] No items fetched. Skipping sync for type={}, siDo={}, guGun={}", type, siDo, guGun); return; } + KoroadType koroadType = KoroadType.from(type); + // 기존 데이터 삭제 (같은 type + region 기준) - hotspotRepository.deleteByTypeAndSiDoAndGuGun(type, siDo, guGun); + hotspotRepository.deleteByTypeAndSiDoAndGuGun(koroadType, siDo, guGun); // 3) 새 데이터 저장 List entities = items.stream() @@ -45,12 +48,11 @@ public void syncHotspots(String apiType, String type, Integer siDo, Integer guGu if (item.getLoCrd() != null) lon = Double.parseDouble(item.getLoCrd()); } catch (NumberFormatException e) { - log.warn("[KoroadHotspotSyncService] invalid coord la={}, lo={}", - item.getLaCrd(), item.getLoCrd()); + log.warn("[KoroadHotspotSyncService] invalid coord la={}, lo={}", item.getLaCrd(), item.getLoCrd()); } return KoroadHotspot.builder() - .type(type) + .type(koroadType) .afosFid(item.getAfosId()) .spotName(item.getSpotName()) .sidoSggName(item.getSidoSggName()) diff --git a/src/main/java/backend/knowhow/domain/alert/service/KoroadRiskService.java b/src/main/java/backend/knowhow/domain/alert/service/KoroadRiskService.java deleted file mode 100644 index c6c0ea0..0000000 --- a/src/main/java/backend/knowhow/domain/alert/service/KoroadRiskService.java +++ /dev/null @@ -1,38 +0,0 @@ -//package backend.knowhow.domain.alert.service; -// -//import backend.knowhow.domain.alert.domain.KoroadHotspot; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.locationtech.jts.geom.Coordinate; -//import org.locationtech.jts.geom.GeometryFactory; -//import org.locationtech.jts.geom.Point; -//import org.springframework.stereotype.Service; -// -//import java.util.Optional; -// -//@Service -//@Slf4j -//@RequiredArgsConstructor -//public class KoroadRiskService { -// -// private final KoroadHotspotGeometryCache cache; -// private final GeometryFactory geometryFactory = new GeometryFactory(); -// -// // 현재 위치가 Koroad hotspot 폴리곤 안에 들어가는지 여부 -// public Optional findContaining(double lat, double lon) { -// Point point = geometryFactory.createPoint(new Coordinate(lon, lat)); // x=lon, y=lat -// -// for (KoroadHotspotGeometryCache.CachedHotspot ch : cache.getHotspots()) { -// if (ch.getGeometry().contains(point) || ch.getGeometry().covers(point)) { -// log.info("inside risk!!!!"); -// return Optional.of(ch.getEntity()); -// } -// } -// return Optional.empty(); -// } -// -// public boolean isInside(double lat, double lon) { -// return findContaining(lat, lon).isPresent(); -// } -//} -// diff --git a/src/main/java/backend/knowhow/domain/alert/util/KoroadRegion.java b/src/main/java/backend/knowhow/domain/alert/util/KoroadRegion.java new file mode 100644 index 0000000..e8d116d --- /dev/null +++ b/src/main/java/backend/knowhow/domain/alert/util/KoroadRegion.java @@ -0,0 +1,350 @@ +package backend.knowhow.domain.alert.util; + +import java.util.List; +import java.util.Map; + +import static java.util.Map.entry; + +public final class KoroadRegion { + + // KOROAD 기준: 법정동 시군구 코드의 뒤 3자리 + public static final Map> SIDO_GUGUN = Map.ofEntries( + + // ================= 서울특별시 (11) ================= + entry(11, List.of( + 680, // 강남구 + 740, // 강동구 + 305, // 강북구 + 500, // 강서구 + 620, // 관악구 + 215, // 광진구 + 530, // 구로구 + 545, // 금천구 + 350, // 노원구 + 320, // 도봉구 + 230, // 동대문구 + 590, // 동작구 + 440, // 마포구 + 410, // 서대문구 + 650, // 서초구 + 200, // 성동구 + 290, // 성북구 + 710, // 송파구 + 470, // 양천구 + 560, // 영등포구 + 170, // 용산구 + 380, // 은평구 + 110, // 종로구 + 140, // 중구 + 260 // 중랑구 + )), + + // ================= 부산광역시 (26) ================= + entry(26, List.of( + 440, // 강서구 + 410, // 금정구 + 710, // 기장군 + 290, // 남구 + 170, // 동구 + 260, // 동래구 + 230, // 부산진구 + 320, // 북구 + 530, // 사상구 + 380, // 사하구 + 140, // 서구 + 500, // 수영구 + 470, // 연제구 + 200, // 영도구 + 110, // 중구 + 350 // 해운대구 + )), + + // ================= 대구광역시 (27) ================= + entry(27, List.of( + 720, // 군위군 + 200, // 남구 + 290, // 달서구 + 710, // 달성군 + 140, // 동구 + 230, // 북구 + 170, // 서구 + 260, // 수성구 + 110 // 중구 + )), + + // ================= 인천광역시 (28) ================= + entry(28, List.of( + 710, // 강화군 + 245, // 계양구 + 170, // 남구(구) + 200, // 남동구 + 140, // 동구 + 177, // 미추홀구 + 237, // 부평구 + 260, // 서구 + 185, // 연수구 + 720, // 옹진군 + 110 // 중구 + )), + + // ================= 광주광역시 (29) ================= + entry(29, List.of( + 200, // 광산구 + 155, // 남구 + 110, // 동구 + 170, // 북구 + 140 // 서구 + )), + + // ================= 대전광역시 (30) ================= + entry(30, List.of( + 230, // 대덕구 + 110, // 동구 + 170, // 서구 + 200, // 유성구 + 140 // 중구 + )), + + // ================= 울산광역시 (31) ================= + entry(31, List.of( + 140, // 남구 + 170, // 동구 + 200, // 북구 + 710, // 울주군 + 110 // 중구 + )), + + // ================= 세종특별자치시 (36) ================= + entry(36, List.of( + 110 // 세종특별자치시 + )), + + // ================= 경기도 (41) ================= + entry(41, List.of( + 820, // 가평군 + 281, // 고양시덕양구 + 283, // 고양시일산구 + 285, // 고양시일산동구 + 287, // 고양시일산서구 + 290, // 과천시 + 210, // 광명시 + 610, // 광주시 + 310, // 구리시 + 410, // 군포시 + 570, // 김포시 + 360, // 남양주시 + 250, // 동두천시 + 192, // 부천시원미구 + 194, // 부천시소사구 + 196, // 부천시오정구 + 190, // 부천시(구) + 197, // 부천시소사구(구) + 199, // 부천시오정구(구) + 195, // 부천시원미구(구) + 135, // 성남시분당구 + 131, // 성남시수정구 + 133, // 성남시중원구 + 113, // 수원시권선구 + 117, // 수원시영통구 + 111, // 수원시장안구 + 115, // 수원시팔달구 + 390, // 시흥시 + 273, // 안산시단원구 + 271, // 안산시상록구 + 550, // 안성시 + 173, // 안양시동안구 + 171, // 안양시만안구 + 630, // 양주시 + 830, // 양평군 + 730, // 여주군 + 670, // 여주시 + 800, // 연천군 + 370, // 오산시 + 463, // 용인시기흥구 + 465, // 용인시수지구 + 461, // 용인시처인구 + 430, // 의왕시 + 150, // 의정부시 + 500, // 이천시 + 480, // 파주시 + 220, // 평택시 + 810, // 포천군 + 650, // 포천시 + 450, // 하남시 + 590 // 화성시 + )), + + // ================= 강원특별자치도 (42) ================= + entry(42, List.of( + 150, // 강릉시 + 820, // 고성군 + 170, // 동해시 + 230, // 삼척시 + 210, // 속초시 + 800, // 양구군 + 830, // 양양군 + 750, // 영월군 + 130, // 원주시 + 810, // 인제군 + 770, // 정선군 + 780, // 철원군 + 110, // 춘천시 + 190, // 태백시 + 760, // 평창군 + 720, // 홍천군 + 790, // 화천군 + 730 // 횡성군 + )), + + // ================= 충청북도 (43) ================= + entry(43, List.of( + 760, // 괴산군 + 800, // 단양군 + 720, // 보은군 + 740, // 영동군 + 730, // 옥천군 + 770, // 음성군 + 150, // 제천시 + 745, // 증평군 + 750, // 진천군 + 710, // 청원군 + 111, // 청주시상당구 + 112, // 청주시서원구 + 114, // 청주시청원구 + 113, // 청주시흥덕구 + 130 // 충주시 + )), + + // ================= 충청남도 (44) ================= + entry(44, List.of( + 250, // 계룡시 + 150, // 공주시 + 710, // 금산군 + 230, // 논산시 + 830, // 당진군 + 270, // 당진시 + 180, // 보령시 + 760, // 부여군 + 210, // 서산시 + 770, // 서천군 + 200, // 아산시 + 730, // 연기군 + 810, // 예산군 + 130, // 천안시 + 131, // 천안시동남구 + 133, // 천안시서북구 + 790, // 청양군 + 825, // 태안군 + 800 // 홍성군 + )), + + // ================= 전북특별자치도 (45) ================= + entry(45, List.of( + 790, // 고창군 + 130, // 군산시 + 210, // 김제시 + 190, // 남원시 + 730, // 무주군 + 800, // 부안군 + 770, // 순창군 + 710, // 완주군 + 140, // 익산시 + 750, // 임실군 + 740, // 장수군 + 113, // 전주시덕진구 + 111, // 전주시완산구 + 180, // 정읍시 + 720 // 진안군 + )), + + // ================= 전라남도 (46) ================= + entry(46, List.of( + 810, // 강진군 + 770, // 고흥군 + 720, // 곡성군 + 230, // 광양시 + 730, // 구례군 + 170, // 나주시 + 710, // 담양군 + 110, // 목포시 + 840, // 무안군 + 780, // 보성군 + 150, // 순천시 + 910, // 신안군 + 130, // 여수시 + 870, // 영광군 + 830, // 영암군 + 890, // 완도군 + 880, // 장성군 + 800, // 장흥군 + 900, // 진도군 + 860, // 함평군 + 820, // 해남군 + 790 // 화순군 + )), + + // ================= 경상북도 (47) ================= + entry(47, List.of( + 290, // 경산시 + 130, // 경주시 + 830, // 고령군 + 190, // 구미시 + 720, // 군위군(구) + 150, // 김천시 + 280, // 문경시 + 920, // 봉화군 + 250, // 상주시 + 840, // 성주군 + 170, // 안동시 + 770, // 영덕군 + 760, // 영양군 + 210, // 영주시 + 230, // 영천시 + 900, // 예천군 + 940, // 울릉군 + 930, // 울진군 + 730, // 의성군 + 820, // 청도군 + 750, // 청송군 + 850, // 칠곡군 + 111, // 포항시남구 + 113 // 포항시북구 + )), + + // ================= 경상남도 (48) ================= + entry(48, List.of( + 310, // 거제시 + 880, // 거창군 + 820, // 고성군 + 250, // 김해시 + 840, // 남해군 + 160, // 마산시 + 270, // 밀양시 + 240, // 사천시 + 860, // 산청군 + 330, // 양산시 + 720, // 의령군 + 170, // 진주시 + 190, // 진해시 + 740, // 창녕군 + 125, // 창원시마산합포구 + 127, // 창원시마산회원구 + 123, // 창원시성산구 + 121, // 창원시의창구 + 129, // 창원시진해구 + 220, // 통영시 + 850, // 하동군 + 730, // 함안군 + 870, // 함양군 + 890 // 합천군 + )), + + // ================= 제주특별자치도 (50) ================= + entry(50, List.of( + 130, // 서귀포시 + 110 // 제주시 + )) + ); + + private KoroadRegion() {} +}