From d590aad6e970b3f6d72e1e78db34c9716863c279 Mon Sep 17 00:00:00 2001 From: 99hyuk Date: Tue, 3 Jun 2025 22:53:07 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EC=A7=84=EB=A3=8C=EA=B3=BC=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EC=8B=9C=20=EB=8B=A4=EA=B5=AD=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20=EA=B2=80=EC=83=89=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EC=B9=98=EA=B3=BC/=ED=9D=89=EB=B6=80?= =?UTF-8?q?=EC=99=B8=EA=B3=BC=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20=EB=82=98?= =?UTF-8?q?=EC=98=A4=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hospital/HospitalController.java | 9 ++- ...esponse.java => DepartmentMappingDto.java} | 6 +- .../hospital/HospitalRepository.java | 35 +++++---- .../service/department/DepartmentService.java | 74 +++++++++++++++---- .../hospital/HospitalSearchService.java | 36 +++------ 5 files changed, 105 insertions(+), 55 deletions(-) rename src/main/java/com/onebridge/ouch/dto/hospital/response/{AllDepartmentResponse.java => DepartmentMappingDto.java} (56%) diff --git a/src/main/java/com/onebridge/ouch/controller/hospital/HospitalController.java b/src/main/java/com/onebridge/ouch/controller/hospital/HospitalController.java index 5969bd0..cb194d9 100644 --- a/src/main/java/com/onebridge/ouch/controller/hospital/HospitalController.java +++ b/src/main/java/com/onebridge/ouch/controller/hospital/HospitalController.java @@ -4,7 +4,7 @@ import org.springframework.web.bind.annotation.*; -import com.onebridge.ouch.dto.hospital.response.AllDepartmentResponse; +import com.onebridge.ouch.dto.hospital.response.DepartmentMappingDto; import com.onebridge.ouch.dto.hospital.response.HospitalDetailResponse; import com.onebridge.ouch.dto.hospital.response.HospitalDistanceResponse; import com.onebridge.ouch.dto.hospital.response.RegionMappingDto; @@ -27,10 +27,11 @@ public class HospitalController { private final DepartmentService departmentService; private final RegionService regionService; - @Operation(summary = "거리 순 병원 조회 API", description = "입력된 진료과(department), 종별코드명(type) 위도(lat), 경도(lng)를 기준으로 병원 목록을 거리 순으로 조회합니다. " + @Operation(summary = "거리 순 병원 조회 API", description = "입력된 진료과(department), 종별코드명(type), 위도(lat), 경도(lng)를 기준으로 병원 목록을 거리 순으로 조회합니다. " + "진료과나 종별코드명를 입력하지 않으면 입력된 위도, 경도를 기준으로 모든 병원 목록을 거리 순으로 조회합니다. 위도, 경도는 필수로 입력해야 합니다. " + "진료과나 종별코드명은 선택적으로 입력 가능합니다. 진료과는 한글로만 입력이 가능하며, 종별코드명은 병원(약국 제외 모든 병원), 약국 두 가지의 키워드로만 입력이 됩니다." - + "진료과가 존재하지 않는 경우가 있는데 이는 해당 병원이 진료과를 제공하지 않기 때문이며, 응답 필드 중에 department 뿐만 아니라 type(치과의원, 종합병원 등)을 진료과처럼 사용하면 웬만하면 알아볼 수 있습니다.") + + "진료과가 존재하지 않는 경우가 있는데 이는 해당 병원이 진료과를 제공하지 않기 때문이며, 응답 필드 중에 department 뿐만 아니라 type(치과의원, 종합병원 등)을 진료과처럼 사용하면 웬만하면 알아볼 수 있습니다." + + "종별코드명(type)에 약국을 입력하면 약국이 나오고, 병원을 입력하면 약국을 제외한 모든 의료기관이 나옵니다.") @GetMapping("/search") public List searchHospitals( @RequestParam(required = false) String department, @@ -52,7 +53,7 @@ public HospitalDetailResponse getHospitalDetail(@PathVariable String ykiho) { @Operation(summary = "진료과 목록 조회 API", description = "모든 진료과 목록을 조회합니다.") @GetMapping("/departments") - public List getDepartments() { + public List getDepartments() { return departmentService.getAllDepartments(); } diff --git a/src/main/java/com/onebridge/ouch/dto/hospital/response/AllDepartmentResponse.java b/src/main/java/com/onebridge/ouch/dto/hospital/response/DepartmentMappingDto.java similarity index 56% rename from src/main/java/com/onebridge/ouch/dto/hospital/response/AllDepartmentResponse.java rename to src/main/java/com/onebridge/ouch/dto/hospital/response/DepartmentMappingDto.java index a26a025..848d08e 100644 --- a/src/main/java/com/onebridge/ouch/dto/hospital/response/AllDepartmentResponse.java +++ b/src/main/java/com/onebridge/ouch/dto/hospital/response/DepartmentMappingDto.java @@ -1,9 +1,13 @@ package com.onebridge.ouch.dto.hospital.response; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Getter -public class AllDepartmentResponse { +@Setter +@NoArgsConstructor +public class DepartmentMappingDto { private Long code; private String nameKr; private String nameEn; diff --git a/src/main/java/com/onebridge/ouch/repository/hospital/HospitalRepository.java b/src/main/java/com/onebridge/ouch/repository/hospital/HospitalRepository.java index e071948..3265d96 100644 --- a/src/main/java/com/onebridge/ouch/repository/hospital/HospitalRepository.java +++ b/src/main/java/com/onebridge/ouch/repository/hospital/HospitalRepository.java @@ -42,24 +42,33 @@ List findAllOrderByDistance( "GROUP_CONCAT(DISTINCT hd.department_name) as departments, " + "(6371 * acos(cos(radians(:lat)) * cos(radians(h.lat)) " + "* cos(radians(h.lng) - radians(:lng)) + sin(radians(:lat)) * sin(radians(h.lat)))) as distance " + - "FROM hospital h " + - "LEFT JOIN hospital_department hd ON h.ykiho = hd.ykiho " + - "WHERE (:type IS NULL OR h.type = :type " + - " OR (:type = '병원' AND h.type != '약국')) " + // 병원이면 약국 제외 - "AND h.type != '요양병원' " + - "AND (:department1 IS NULL OR hd.department_name IN (:department1, :department2)) " + // 내과+가정의학과 포함 - "AND h.lat IS NOT NULL AND h.lng IS NOT NULL " + - "AND (:sidoKr IS NULL OR h.sido = :sidoKr) " + - "GROUP BY h.ykiho " + - "ORDER BY distance ASC " + - "LIMIT :limit OFFSET :offset", + "FROM hospital h " + + "LEFT JOIN hospital_department hd ON h.ykiho = hd.ykiho " + + "WHERE (:type IS NULL OR h.type = :type " + + " OR (:type = '병원' AND h.type != '약국')) " + // 병원이면 약국 제외 + "AND h.type != '요양병원' " + + "AND ( " + + " :department IS NULL " + + " OR ( :department IN ('내과', '가정의학과') " + + " AND hd.department_name IN ('내과', '가정의학과') ) " + + " OR ( :department = '치과' " + + " AND hd.department_name LIKE '%치%' ) " + + " OR ( :department = '흉부외과' " + + " AND hd.department_name LIKE '%흉부%' ) " + + " OR ( :department NOT IN ('내과', '가정의학과', '치과') " + + " AND hd.department_name = :department ) " + + " ) " + + "AND h.lat IS NOT NULL AND h.lng IS NOT NULL " + + "AND (:sidoKr IS NULL OR h.sido = :sidoKr) " + + "GROUP BY h.ykiho " + + "ORDER BY distance ASC " + + "LIMIT :limit OFFSET :offset", nativeQuery = true) List findWithConditionsOrderByDistance( @Param("lat") double lat, @Param("lng") double lng, @Param("type") String type, - @Param("department1") String department1, - @Param("department2") String department2, + @Param("department") String department, @Param("sidoKr") String sidoKr, @Param("limit") int limit, @Param("offset") int offset diff --git a/src/main/java/com/onebridge/ouch/service/department/DepartmentService.java b/src/main/java/com/onebridge/ouch/service/department/DepartmentService.java index 14eb2ba..2798bed 100644 --- a/src/main/java/com/onebridge/ouch/service/department/DepartmentService.java +++ b/src/main/java/com/onebridge/ouch/service/department/DepartmentService.java @@ -1,29 +1,77 @@ package com.onebridge.ouch.service.department; -import java.io.InputStream; -import java.util.List; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.stereotype.Service; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onebridge.ouch.dto.hospital.response.AllDepartmentResponse; - +import com.onebridge.ouch.dto.hospital.response.DepartmentMappingDto; import jakarta.annotation.PostConstruct; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import java.io.InputStream; +import java.util.*; + +/** + * 1) data/department.json 을 로딩해서 List 로 저장 + * 2) "한·영·중(any) 진료과명 → 한글(nameKr)" 매핑용 Map을 구축 + * 3) getAllDepartments() 는 기존과 동일하게 전체 리스트를 반환 + * 4) getKrDepartmentName(input) 를 통해 다국어 → 한글 매핑 기능 제공 + */ @Service public class DepartmentService { - private List departmentList; + // JSON에서 읽어들인 전체 진료과 리스트 (DTO 객체) + private List departmentList; + + // key: nameKr, or lower(nameEn), or nameZh → nameKr + private final Map anyToKrDept = new HashMap<>(); @PostConstruct public void init() throws Exception { + // 1) JSON 로드 ObjectMapper mapper = new ObjectMapper(); - InputStream input = new ClassPathResource("data/department.json").getInputStream(); - departmentList = mapper.readValue(input, new TypeReference>() {}); + InputStream is = new ClassPathResource("data/department.json").getInputStream(); + departmentList = mapper.readValue(is, new TypeReference>() {}); + + // 2) anyToKrDept 맵 구축 + for (DepartmentMappingDto dm : departmentList) { + String kr = dm.getNameKr().trim(); + String en = dm.getNameEn().trim().toLowerCase(); + String zh = dm.getNameZh().trim(); + + // 하나의 진료과를 세 언어로 모두 매핑 + anyToKrDept.put(kr, kr); // 한국어 입력 + anyToKrDept.put(en, kr); // 영어(소문자) 입력 + anyToKrDept.put(zh, kr); // 중국어 입력 + } } - public List getAllDepartments() { - return departmentList; + /** + * 전체 진료과 목록(한·영·중 포함) 반환 + * 예) [{"code":1,"nameKr":"내과","nameEn":"Internal Medicine","nameZh":"内科"}, ...] + */ + public List getAllDepartments() { + return Collections.unmodifiableList(departmentList); } + + /** + * 입력된 진료과명(input)을 한글 진료과명(nameKr)으로 변환 + * - 예1) input="Internal Medicine" → return "내과" + * - 예2) input="内科" → return "내과" + * - 예3) input="내과" → return "내과" + * - 매칭 실패 시 null 반환 + */ + public String getKrDepartmentName(String input) { + if (input == null || input.isBlank()) { + return null; + } + String key = input.trim(); + // 1) 한글 키가 있는 경우 + if (anyToKrDept.containsKey(key)) { + return anyToKrDept.get(key); + } + // 2) 소문자 영어로 바꿔서 검색 + String lower = key.toLowerCase(); + return anyToKrDept.getOrDefault(lower, null); + } + + } diff --git a/src/main/java/com/onebridge/ouch/service/hospital/HospitalSearchService.java b/src/main/java/com/onebridge/ouch/service/hospital/HospitalSearchService.java index 31894f1..0b7339a 100644 --- a/src/main/java/com/onebridge/ouch/service/hospital/HospitalSearchService.java +++ b/src/main/java/com/onebridge/ouch/service/hospital/HospitalSearchService.java @@ -7,6 +7,7 @@ import com.onebridge.ouch.dto.hospital.response.HospitalDistanceResponse; import com.onebridge.ouch.repository.hospital.HospitalRepository; +import com.onebridge.ouch.service.department.DepartmentService; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ public class HospitalSearchService { private final HospitalRepository hospitalRepository; private final RegionService regionService; + private final DepartmentService departmentService; public List searchHospitals( String department, @@ -30,31 +32,17 @@ public List searchHospitals( // 1) 전달받은 sido를 한국어로 변환 String sidoKr = regionService.getKrSidoName(sido); - int offset = page * size; - List rawList; - - // 진료과: 내과→내과,가정의학과 모두 포함 - String department1 = null; - String department2 = null; - if (department != null && !department.isBlank()) { - if (department.equals("내과") || department.equals("가정의학과")) { - department1 = "내과"; - department2 = "가정의학과"; - } else { - department1 = department; - department2 = department; - } - } + // 2) 진료과(any) → 한글 진료과명 (신규 로직) + String deptKr = departmentService.getKrDepartmentName(department); - // type(종별코드명)도 null/입력값에 따라 처리 - if ((department1 != null && department2 != null) || (type != null && !type.isBlank()) || (sidoKr != null)) { - rawList = hospitalRepository.findWithConditionsOrderByDistance( - lat, lng, - type, department1, department2, sidoKr, size, offset - ); - } else { - rawList = hospitalRepository.findAllOrderByDistance(lat, lng, size, offset); - } + int offset = page * size; + List rawList = hospitalRepository.findWithConditionsOrderByDistance( + lat, lng, + type, + deptKr, + sidoKr, + size, offset + ); List result = new ArrayList<>(); for (Object[] row : rawList) {