diff --git a/README.md b/README.md new file mode 100644 index 00000000..81f83b46 --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +# WaybleπŸ™οΈ + + +**μ‚¬μš©μžλ₯Ό μœ„ν•œ λ§žμΆ€ν˜• 배리어프리 μ„œλΉ„μŠ€ Wayble** + +> λͺ¨λ‘μ˜ νŽΈλ¦¬ν•œ 이동을 μœ„ν•œ λ§žμΆ€ν˜• 경둜 탐색 및 μž₯μ†Œ μΆ”μ²œ μ„œλΉ„μŠ€
+> μž₯μ•  μœ ν˜•λ³„Β·μ΄λ™ μˆ˜λ‹¨λ³„ μ΅œμ ν™”λœ 경둜 μ•ˆλ‚΄μ™€ μ ‘κ·Όμ„± 정보 곡유 ν”Œλž«νΌ, Waybleμ—μ„œ λ§Œλ‚˜λ³΄μ„Έμš”! +
+ + μ›¨μ΄λΈ”λ°œν‘œ + +### [πŸ› οΈWayble μ„œλΉ„μŠ€ 링크 λ°”λ‘œκ°€κΈ°](https://wayble.site) +### [🎬Wayble λ…Έμ…˜ 링크 λ°”λ‘œκ°€κΈ°](https://www.notion.so/wayble-20475cf0b87b806d9473feb579ab23e0) + +### πŸ“‚ Content +- [πŸ”Ž νŒ€ μ†Œκ°œ](#νŒ€-μ†Œκ°œ) +- [πŸ”Ž 기술 μŠ€νƒ](#기술-μŠ€νƒ) +- [πŸ”Ž μ„œλΉ„μŠ€ κ³ μ•ˆ λ°°κ²½](#μ„œλΉ„μŠ€-κ³ μ•ˆ-λ°°κ²½) +- [πŸ”Ž μ£Όμš” κΈ°λŠ₯](#μ£Όμš”-κΈ°λŠ₯) +- [πŸ”Ž 상세 κΈ°λŠ₯](#상세-κΈ°λŠ₯) +- [πŸ”Ž BE 폴더 ꡬ쑰](#BE-폴더-ꡬ쑰) +- [πŸ”Ž BE μ‹œμŠ€ν…œ ꡬ성도](#BE-μ‹œμŠ€ν…œ-ꡬ성) +- [πŸ”Ž λ°μ΄ν„°λ² μ΄μŠ€ ꡬ쑰](#λ°μ΄ν„°λ² μ΄μŠ€-ꡬ쑰) +- [πŸ”Ž api λͺ…μ„Έ](#api-λͺ…μ„Έ) + + + + + +

+## 😎 νŒ€ μ†Œκ°œ +> Team +> 기승민 μ–‘νš¨μΈ 유승인 이원쀀 μ£Όμ •λΉˆ + + +| 기승민 (Lead) | μ–‘νš¨μΈ | 유승인 | 이원쀀 | μ£Όμ •λΉˆ | +| ---------------------------------------- | ------------------------------------ | -------------------------------------- | ------------------------------------------------ | -------------------------------------- | +| ![profile](https://avatars.githubusercontent.com/u/67568824?v=4) | ![profile](https://avatars.githubusercontent.com/u/144425658?v=4) |![profile](https://avatars.githubusercontent.com/u/144124353?v=4)|![profile](https://avatars.githubusercontent.com/u/202200191?v=4) | ![profile](https://avatars.githubusercontent.com/u/166782961?v=4) +| BE | BE | BE | BE | BE | +| [@KiSeungMin](https://github.com/KiSeungMin) |[@hyoinYang](https://github.com/hyoinYang)|[@seung-in-Yoo](https://github.com/seung-in-Yoo) | [@wonjun-lee-fcwj245](https://github.com/wonjun-lee-fcwj245) |[@zyovn](https://github.com/zyovn) | + +

+ +## πŸ”Ž 기술 μŠ€νƒ + +| Category | Stack | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Framework / Runtime | ![Spring Boot](https://img.shields.io/badge/Spring%20Boot-6DB33F?style=for-the-badge&logo=springboot&logoColor=white) ![Java](https://img.shields.io/badge/Java%2017-007396?style=for-the-badge&logo=java&logoColor=white) | +| Programming Language | ![Java](https://img.shields.io/badge/Java%2017-007396?style=for-the-badge&logo=java&logoColor=white) | +| Database / Search | ![Amazon RDS](https://img.shields.io/badge/Amazon%20RDS-527FFF?style=for-the-badge&logo=amazonrds&logoColor=white) ![MySQL](https://img.shields.io/badge/MySQL-4479A1?style=for-the-badge&logo=mysql&logoColor=white) ![Elasticsearch](https://img.shields.io/badge/Elasticsearch-005571?style=for-the-badge&logo=elasticsearch&logoColor=white) | +| Infrastructure | ![AWS EC2](https://img.shields.io/badge/AWS%20EC2-FF9900?style=for-the-badge&logo=amazonec2&logoColor=white) ![AWS S3](https://img.shields.io/badge/AWS%20S3-569A31?style=for-the-badge&logo=amazons3&logoColor=white) ![AWS CloudWatch](https://img.shields.io/badge/AWS%20CloudWatch-FF4F8B?style=for-the-badge&logo=amazoncloudwatch&logoColor=white) ![AWS Route 53](https://img.shields.io/badge/AWS%20Route%2053-232F3E?style=for-the-badge&logo=amazonroute53&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white) | +| API / Data | ![T map API](https://img.shields.io/badge/T%20map%20API-FF1515?style=for-the-badge&logo=naver&logoColor=white) ![곡곡데이터포털](https://img.shields.io/badge/곡곡데이터포털-005BAC?style=for-the-badge&logoColor=white) | +| Authentication | ![JWT](https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=jsonwebtokens&logoColor=white) | +| CI/CD | ![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-2088FF?style=for-the-badge&logo=githubactions&logoColor=white) | +| Version Control | ![Git](https://img.shields.io/badge/Git-F05032?style=for-the-badge&logo=git&logoColor=white) ![GitHub](https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white) | +
+ + +## πŸ”† μ„œλΉ„μŠ€ κ³ μ•ˆ λ°°κ²½ +### πŸ“– ν”„λ‘œμ νŠΈ κ°œμš” + +WAYBLE은 μž₯μ• μΈΒ·κ΅ν†΅μ•½μž μ‚¬μš©μžλ₯Ό μœ„ν•΄ λ§žμΆ€ν˜• 경둜 탐색, μ ‘κ·Όμ„± ν•„ν„° 기반 μž₯μ†Œ μΆ”μ²œ, μ ‘κ·Όμ„± 리뷰 곡유 κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” 배리어프리 지도 μ„œλΉ„μŠ€μž…λ‹ˆλ‹€.
+μ‚¬μš©μžμ˜ μž₯μ•  μœ ν˜•κ³Ό 이동 μˆ˜λ‹¨ 섀정에 따라 μ΅œμ ν™”λœ 경둜λ₯Ό μ•ˆλ‚΄ν•˜κ³ , λˆ„κ΅¬λ‚˜ μ ‘κ·Όμ„± 정보λ₯Ό λ“±λ‘Β·ν™•μΈν•˜μ—¬ λͺ¨λ‘μ˜ μ΄λ™κΆŒμ„ 보μž₯ν•©λ‹ˆλ‹€. + +### 🚦 기획의 μ‹œμž‘ +μ™œ μž₯애인은 μ£Όλ³€μ—μ„œ 자주 보이지 μ•Šμ„κΉŒ?
+κ΅ν†΅μˆ˜λ‹¨ 이용의 어렀움
+μΆ©λΆ„νžˆ 보μž₯λ˜μ§€ μ•ŠλŠ” μƒν™œ μ ‘κ·Όμ„±
+λΆ€μ‘±ν•œ 이동 편의 μ„œλΉ„μŠ€
+μž₯애인 μΈν„°λ·°μ—μ„œ λ‚˜μ˜¨ λͺ©μ†Œλ¦¬
+ +> "맀일 λ˜‘κ°™μ€ 식당에 κ°€μš”.", +> "ν•˜λ£¨κ°€ κ³„νšλŒ€λ‘œ λ˜μ§€ μ•Šμ•„μš”.", +> "μ—˜λ¦¬λ² μ΄ν„° μœ„μΉ˜ 정보가 λΆ€μ •ν™•ν•΄μ„œ 이동이 νž˜λ“€μ–΄μš”." + +κΈ°μ‘΄ 지도 μ„œλΉ„μŠ€μ—λŠ” μ—˜λ¦¬λ² μ΄ν„°Β·κ²½μ‚¬λ‘œΒ·μž₯애인 ν™”μž₯μ‹€ λ“± μž₯애인듀을 μœ„ν•œ ν™•μ‹€ν•œ 정보가 λΆˆμΆ©λΆ„ν•˜κ³ , +μž₯μ•  μœ ν˜•λ³„ 경둜 μ•ˆλ‚΄, μž₯μ• μΈλ“€λ§Œμ˜ μ ‘κ·Όμ„± 리뷰 곡유 κΈ°λŠ₯λ“± νŽΈμ˜μ„±μ΄ μ œκ³΅λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. + +### πŸ’‘ μ„œλΉ„μŠ€ μ†Œκ°œ + +**WAYBLE은** +- μž₯μ•  μœ ν˜•κ³Ό 이동 μˆ˜λ‹¨ 섀정에 따라 UIΒ·μ•ˆλ‚΄ 정보 μžλ™ μ΅œμ ν™” +- μ ‘κ·Όμ„± ν•„ν„° 기반 μž₯μ†Œ κ²€μƒ‰Β·μΆ”μ²œ +- μž₯애인듀을 기반으둜 ν•œ μ°Έμ—¬ν˜• μ ‘κ·Όμ„± 리뷰 곡유 +- μž₯μ• μΈμ˜ 이동 κ²½ν—˜ κ°œμ„  및 말λͺ»ν•˜λŠ” μ‚¬μ†Œν•œ 뢈편 ν•΄μ†Œ +λ₯Ό λͺ©ν‘œλ‘œ ν•˜λŠ” 배리어프리 지도 ν”Œλž«νΌμž…λ‹ˆλ‹€. + +### 🎯 μ„œλΉ„μŠ€ λͺ©ν‘œ + +**μž₯μ•  μœ ν˜•Β·μ΄λ™ μˆ˜λ‹¨λ³„ μ΅œμ ν™”λœ 경둜 탐색** + +**μ ‘κ·Όμ„± ν•„ν„° 기반 μž₯μ†Œ μΆ”μ²œ** + +**μž₯애인 μ°Έμ—¬ν˜• μ ‘κ·Όμ„± 정보 μˆ˜μ§‘Β·κ³΅μœ ** + +(μ±„μš°κΈ°) + +

+ +## πŸ›  μ£Όμš” κΈ°λŠ₯ +**1. 지도 기반 μ ‘κ·Ό κ°€λŠ₯ μž₯μ†Œ 검색** + +μ—˜λ¦¬λ² μ΄ν„°, κ²½μ‚¬λ‘œ, μž₯애인 ν™”μž₯μ‹€ λ“± μž₯애인듀이 ν•„μˆ˜μ μœΌλ‘œ μ•Œκ³ μ‹Άμ–΄ν•˜λŠ” μ •λ³΄λ“€λ‘œ μž₯μ†Œ 필터링 +μœ„μΉ˜ 기반 μΆ”μ²œ + +**2. λ§žμΆ€ν˜• 경둜 μ•ˆλ‚΄** + +μž₯μ•  μœ ν˜•λ³„ 경둜 μ΅œμ ν™” (νœ μ²΄μ–΄, μ‹œκ°μž₯μ• , 지적μž₯μ•  λ“±) + +웨이블 마컀 (κ²½μ‚¬λ‘œ, νœ μ²΄μ–΄ μΆ©μ „κΈ° λ“±)을 ν™œμš©ν•˜μ—¬ μ»€μŠ€ν…€ μΆ”μ²œ 경둜 제곡 + +λŒ€μ€‘κ΅ν†΅ κ²½λ‘œμ—μ„  μž₯μ• μΈλ“€μ—κ²Œ ν•„μš”ν•œ 정보(μ§€ν•˜μ² μ—­-μ—˜λ¦¬λ² μ΄ν„° μœ„μΉ˜ λ“±, λ²„μŠ€-μ €μƒλ²„μŠ€ μ—¬λΆ€ λ“±) 제곡 + +**3. μ ‘κ·Όμ„± 리뷰 μž‘μ„±Β·μ—΄λžŒ** + +μ΄μš©μžκ°€ 남긴 μ ‘κ·Όμ„± 쀑심 리뷰 확인 + +**4. 마이 ν”Œλ ˆμ΄μŠ€** + +λ‚˜λ§Œμ˜ μž₯μ†Œ 웨이블쑴 리슀트 μ €μž₯ + +μ €μž₯ν•œ μž₯μ†Œλ³„ 웨이블쑴 쑰회 및 μ‚­μ œ + +(μ±„μš°κΈ°) + +

+ +## πŸ—ƒοΈλ°μ΄ν„°λ² μ΄μŠ€ ꡬ쑰 +image + + +

+ + +## πŸŒ΄ν΄λ” ꡬ쑰 +``` +wayble-server/ +β”œβ”€β”€ java/ +β”‚ └── com/ +β”‚ └── wayble/ +β”‚ └── server/ +β”‚ β”œβ”€β”€ admin/ # κ΄€λ¦¬μž κ΄€λ ¨ κΈ°λŠ₯ +β”‚ β”œβ”€β”€ auth/ # 인증 및 인가 κ΄€λ ¨ κΈ°λŠ₯ +β”‚ β”œβ”€β”€ aws/ # AWS 연동 (S3, CloudWatch λ“±) +β”‚ β”œβ”€β”€ common/ # 곡톡 μœ ν‹Έ, μ˜ˆμ™Έ 처리 λ“± +β”‚ β”œβ”€β”€ direction/ # κΈΈμ°ΎκΈ° 및 경둜 μ•ˆλ‚΄ +β”‚ β”œβ”€β”€ explore/ # 탐색 및 μΆ”μ²œ κ΄€λ ¨ +β”‚ β”œβ”€β”€ logging/ # λ‘œκΉ… μ„€μ • +β”‚ β”œβ”€β”€ review/ # 리뷰 μž‘μ„± 및 쑰회 +β”‚ β”œβ”€β”€ user/ # μœ μ € κ΄€λ ¨ κΈ°λŠ₯ +β”‚ β”œβ”€β”€ wayblezone/ # 웨이블쑴 κ΄€λ ¨ κΈ°λŠ₯ +β”‚ └── ServerApplication.java +β”‚ +β”œβ”€β”€ resources/ +β”‚ β”œβ”€β”€ data/ # 데이터 κ΄€λ ¨ λ¦¬μ†ŒμŠ€ +β”‚ β”œβ”€β”€ elasticsearch/ # Elasticsearch κ΄€λ ¨ μ„€μ • +β”‚ β”œβ”€β”€ templates/ # ν…œν”Œλ¦Ώ 파일 +β”‚ β”œβ”€β”€ application.properties # Spring Boot ν™˜κ²½ μ„€μ • +β”‚ β”œβ”€β”€ application_secret.yml # 민감 정보 μ„€μ • +β”‚ β”œβ”€β”€ keystore.p12 # HTTPS μΈμ¦μ„œ +β”‚ β”œβ”€β”€ logback-spring.xml +β”‚ β”œβ”€β”€ seocho_pedestrian.json # μ„œμ΄ˆκ΅¬ λ³΄ν–‰μž 데이터 +β”‚ └── wayble_markers.json # 웨이블 마컀 데이터 +β”‚ +β”œβ”€β”€ test/ # ν…ŒμŠ€νŠΈ κ΄€λ ¨ +β”‚ +β”œβ”€β”€ wrapper/ +β”œβ”€β”€ .gitattributes +β”œβ”€β”€ .gitignore +β”œβ”€β”€ .coderabbit.yml +β”œβ”€β”€ application.yml +β”œβ”€β”€ build.gradle +β”œβ”€β”€ docker-compose.yml +β”œβ”€β”€ docker-els.yml +β”œβ”€β”€ Dockerfile +β”œβ”€β”€ Dockerfile.elasticsearch +└── gradlew +``` +

+ +## πŸŒμ‹œμŠ€ν…œ ꡬ성도 +wayble_infra + +

+ +## ⛓️API λͺ…μ„Έ + +#### [πŸ› οΈWayble κΈ°λŠ₯λͺ…μ„Έ 링크](https://www.notion.so/API-21d75cf0b87b80248a0ec55c6134ad20) + +
diff --git a/src/main/java/com/wayble/server/common/config/WebClientConfig.java b/src/main/java/com/wayble/server/common/config/WebClientConfig.java index 8a8d4380..acbbf298 100644 --- a/src/main/java/com/wayble/server/common/config/WebClientConfig.java +++ b/src/main/java/com/wayble/server/common/config/WebClientConfig.java @@ -31,6 +31,11 @@ public WebClient tMapWebClient() { public WebClient kricWebClient() { return WebClient.builder() .baseUrl(kricProperties.baseUrl()) + .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)) + .filter((request, next) -> next.exchange(request) + .timeout(java.time.Duration.ofSeconds(15)) + .retryWhen(reactor.util.retry.Retry.backoff(3, java.time.Duration.ofSeconds(1)) + .filter(throwable -> throwable instanceof org.springframework.web.reactive.function.client.WebClientRequestException))) .build(); } } diff --git a/src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java b/src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java index 1cb9c64b..9ade2ec5 100644 --- a/src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java +++ b/src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java @@ -20,7 +20,7 @@ public record Step( DirectionType mode, // 예: START, WALK, SUBWAY, BUS, FINISH @Nullable List moveInfo, // 같은 Step으둜 μ΄λ™ν•œ μ •λ₯˜μž₯(Node) 정보 (쀑간 μ •λ₯˜μž₯만) @Nullable String routeName, - Integer moveNumber, // 같은 Step(route)둜 μ΄λ™ν•œ 횟수 + Integer moveNumber, // 같은 Step(route)둜 μ΄λ™ν•œ 횟수 λ˜λŠ” WALK step의 경우 거리(λ―Έν„° λ‹¨μœ„) @Nullable BusInfo busInfo, // λ²„μŠ€μΌ κ²½μš°μ—λ§Œ 생성, μ΄μ™Έμ˜ 경우 null @Nullable SubwayInfo subwayInfo, // μ§€ν•˜μ² μΌ κ²½μš°μ—λ§Œ 생성, μ΄μ™Έμ˜ 경우 null String from, @@ -43,8 +43,8 @@ public record BusInfo( ){} public record SubwayInfo( - List wheelchair, - List elevator, + List wheelchair, + List elevator, Boolean accessibleRestroom ) {} @@ -55,8 +55,8 @@ public record LocationInfo( // μ§€ν•˜μ²  μ‹œμ„€ 정보 묢음 (μ„œλΉ„μŠ€ λ‚΄λΆ€μ—μ„œ μ‚¬μš©) public record NodeInfo( - List wheelchair, - List elevator, + List wheelchair, + List elevator, Boolean accessibleRestroom ) {} } diff --git a/src/main/java/com/wayble/server/direction/entity/transportation/Facility.java b/src/main/java/com/wayble/server/direction/entity/transportation/Facility.java index c4e62daf..88e58c22 100644 --- a/src/main/java/com/wayble/server/direction/entity/transportation/Facility.java +++ b/src/main/java/com/wayble/server/direction/entity/transportation/Facility.java @@ -16,7 +16,6 @@ @Table(name = "facility") public class Facility { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name="stationName") diff --git a/src/main/java/com/wayble/server/direction/entity/transportation/Route.java b/src/main/java/com/wayble/server/direction/entity/transportation/Route.java index 38caac82..d35c1714 100644 --- a/src/main/java/com/wayble/server/direction/entity/transportation/Route.java +++ b/src/main/java/com/wayble/server/direction/entity/transportation/Route.java @@ -6,6 +6,8 @@ import jakarta.persistence.*; import lombok.*; +import java.util.List; + @Entity @Getter @Builder(access = AccessLevel.PRIVATE) @@ -33,4 +35,8 @@ public class Route { @ManyToOne @JoinColumn(name = "end_node_id") private Node endNode; + + // νœ μ²΄μ–΄ 정보 + @OneToMany(mappedBy = "route", fetch = FetchType.LAZY) + private List wheelchairs; } diff --git a/src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java b/src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java new file mode 100644 index 00000000..0a21f445 --- /dev/null +++ b/src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java @@ -0,0 +1,24 @@ +package com.wayble.server.direction.entity.transportation; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "wheelchair") +public class Wheelchair { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", nullable = false) + private Route route; + + @Column(name = "wheelchair_location", nullable = false) + private String wheelchairLocation; // 1-4 λ“± + +} diff --git a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java b/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java deleted file mode 100644 index 3af1db3f..00000000 --- a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawBody.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.wayble.server.direction.external.kric.dto; - -import java.util.List; - -public record KricToiletRawBody( - List item -) {} diff --git a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java b/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java index ddd6f918..43d676de 100644 --- a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java +++ b/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawItem.java @@ -3,6 +3,15 @@ import lombok.Getter; public record KricToiletRawItem( + String railOprIsttCd, + String lnCd, String stinCd, - String toltNum + String grndDvNm, + String stinFlor, + String gateInotDvNm, + String exitNo, + String dtlLoc, + String mlFmlDvNm, + String toltNum, + String diapExchNum ) {} diff --git a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java b/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java index b0b3faac..26ecde7d 100644 --- a/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java +++ b/src/main/java/com/wayble/server/direction/external/kric/dto/KricToiletRawResponse.java @@ -3,5 +3,5 @@ import java.util.List; public record KricToiletRawResponse( - KricToiletRawBody body + List body ) {} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java index 7fe4a12d..d8a12c9c 100644 --- a/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java +++ b/src/main/java/com/wayble/server/direction/repository/FacilityRepository.java @@ -6,7 +6,12 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface FacilityRepository extends JpaRepository { - Optional findByNodeId(Long nodeId); + @Query("SELECT f FROM Facility f " + + "LEFT JOIN FETCH f.lifts " + + "WHERE f.id = :nodeId") + Optional findByNodeId(@Param("nodeId") Long nodeId); } diff --git a/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java new file mode 100644 index 00000000..007fd898 --- /dev/null +++ b/src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java @@ -0,0 +1,15 @@ +package com.wayble.server.direction.repository; + +import com.wayble.server.direction.entity.transportation.Wheelchair; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface WheelchairInfoRepository extends JpaRepository { + + @Query("SELECT w FROM Wheelchair w WHERE w.route.routeId = :routeId") + List findByRouteId(@Param("routeId") Long routeId); +} diff --git a/src/main/java/com/wayble/server/direction/service/FacilityService.java b/src/main/java/com/wayble/server/direction/service/FacilityService.java index fbd4450c..25f9f000 100644 --- a/src/main/java/com/wayble/server/direction/service/FacilityService.java +++ b/src/main/java/com/wayble/server/direction/service/FacilityService.java @@ -2,9 +2,13 @@ import com.wayble.server.direction.dto.response.TransportationResponseDto; import com.wayble.server.direction.entity.transportation.Facility; +import com.wayble.server.direction.entity.transportation.Node; +import com.wayble.server.direction.entity.transportation.Wheelchair; import com.wayble.server.direction.external.kric.dto.KricToiletRawItem; import com.wayble.server.direction.external.kric.dto.KricToiletRawResponse; import com.wayble.server.direction.repository.FacilityRepository; +import com.wayble.server.direction.repository.NodeRepository; +import com.wayble.server.direction.repository.WheelchairInfoRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,7 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; @Service @@ -25,38 +29,49 @@ @RequiredArgsConstructor public class FacilityService { private final FacilityRepository facilityRepository; + private final NodeRepository nodeRepository; + private final WheelchairInfoRepository wheelchairInfoRepository; private final WebClient kricWebClient; private final KricProperties kricProperties; - public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId){ - Facility facility = facilityRepository.findByNodeId(nodeId).orElse(null); - List wheelchair = new ArrayList<>(); - List elevator = new ArrayList<>(); + public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId, Long routeId) { + List wheelchair = new ArrayList<>(); + List elevator = new ArrayList<>(); Boolean accessibleRestroom = false; - if (facility != null) { - if (facility.getLifts() != null) { - wheelchair = facility.getLifts().stream() - .map(lift -> new TransportationResponseDto.LocationInfo( - lift.getLatitude(), - lift.getLongitude() - )) - .toList(); + Optional nodeOpt = nodeRepository.findById(nodeId); + + if (nodeOpt.isPresent()) { + Node node = nodeOpt.get(); + + if (routeId != null) { + List wheelchairs = wheelchairInfoRepository.findByRouteId(routeId); + for (Wheelchair wheelchairInfo : wheelchairs) { + String location = wheelchairInfo.getWheelchairLocation(); + if (location != null && !location.trim().isEmpty()) { + wheelchair.add(location.trim()); + } + } } - - if (facility.getElevators() != null) { - elevator = facility.getElevators().stream() - .map(elev -> new TransportationResponseDto.LocationInfo( - elev.getLatitude(), - elev.getLongitude() - )) - .toList(); + + elevator = new ArrayList<>(); + + Facility facility = facilityRepository.findByNodeId(nodeId).orElse(null); + if (facility != null) { + String stinCd = facility.getStinCd(); + String railOprLsttCd = facility.getRailOprLsttCd(); + String lnCd = facility.getLnCd(); + + if (stinCd != null && railOprLsttCd != null && lnCd != null) { + Map toiletInfo = getToiletInfo(facility); + accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); + } else { + log.error("Facility 정보 λˆ„λ½ - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", + nodeId, stinCd, railOprLsttCd, lnCd); + } + } else { + log.error("Facility 정보 μ—†μŒ - nodeId: {}", nodeId); } - - // Get toilet information - Map toiletInfo = getToiletInfo(facility); - String stinCd = facility.getStinCd(); - accessibleRestroom = toiletInfo.getOrDefault(stinCd, false); } return new TransportationResponseDto.NodeInfo( @@ -66,11 +81,13 @@ public TransportationResponseDto.NodeInfo getNodeInfo(Long nodeId){ ); } - private Map getToiletInfo(Facility facility){ - String uri = UriComponentsBuilder.fromPath("/api/vulnerableUserInfo/stationDisabledToilet") + + + private Map getToiletInfo(Facility facility) { + String uri = UriComponentsBuilder.fromPath("/openapi/vulnerableUserInfo/stationDisabledToilet") .queryParam("serviceKey", kricProperties.key()) .queryParam("format", "json") - .queryParam("railOprLsttCd", facility.getRailOprLsttCd()) + .queryParam("railOprIsttCd", facility.getRailOprLsttCd()) .queryParam("lnCd", facility.getLnCd()) .queryParam("stinCd", facility.getStinCd()) .toUriString(); @@ -83,25 +100,34 @@ private Map getToiletInfo(Facility facility){ .retrieve() .bodyToMono(KricToiletRawResponse.class) .block(); - - items = response.body().item(); - - } catch(Exception e){ - log.info("역사 ν™”μž₯μ‹€ api 호좜 쀑 μ—λŸ¬ λ°œμƒ: {}: {}", uri, e.getCause()); + + if (response == null || response.body() == null) { + return new HashMap<>(); + } + + items = response.body(); + if (items == null) { + return new HashMap<>(); + } + } catch(Exception e) { + log.error("KRIC API 호좜 μ‹€νŒ¨ - stinCd: {}, railOprIsttCd: {}, lnCd: {}, error: {}", + facility.getStinCd(), facility.getRailOprLsttCd(), facility.getLnCd(), e.getMessage(), e); return new HashMap<>(); } - // μ—­λ³„λ‘œ ν™”μž₯μ‹€ 쑴재 μ—¬λΆ€ μΆ”μΆœ (쀑볡 제거) Map stationToiletMap = new HashMap<>(); - for (KricToiletRawItem item : items) { - String stinCd = item.stinCd(); - int toiletCount = 0; - try { - toiletCount = Integer.parseInt(item.toltNum()); - } catch (NumberFormatException e) { - log.warn("μ§€ν•˜μ²  μ—­ 토이렛 개수 νŒŒμ‹± μ‹€νŒ¨. μ§€ν•˜μ² μ—­ 번호 {}: {}", stinCd, item.toltNum(), e); + if (items != null) { + for (KricToiletRawItem item : items) { + String stinCd = item.stinCd(); + int toiletCount = 0; + try { + toiletCount = Integer.parseInt(item.toltNum()); + } catch (NumberFormatException e) { + log.warn("μž₯애인 ν™”μž₯μ‹€ 정보 νŒŒμ‹± μ‹€νŒ¨. stinCd: {}, toltNum: {}", stinCd, item.toltNum()); + } + boolean hasToilet = stationToiletMap.getOrDefault(stinCd, false) || toiletCount > 0; + stationToiletMap.put(stinCd, hasToilet); } - stationToiletMap.put(stinCd, stationToiletMap.getOrDefault(stinCd, false) || toiletCount > 0); } return stationToiletMap; diff --git a/src/main/java/com/wayble/server/direction/service/TransportationService.java b/src/main/java/com/wayble/server/direction/service/TransportationService.java index 50e4759d..ecb9a886 100644 --- a/src/main/java/com/wayble/server/direction/service/TransportationService.java +++ b/src/main/java/com/wayble/server/direction/service/TransportationService.java @@ -277,7 +277,7 @@ private boolean areRoutesIdentical(List route1, } private List> filterAndSortRoutes(List> routes) { - return routes.stream() + return routes.stream() .filter(route -> { // λŒ€μ€‘κ΅ν†΅ 포함 μ—¬λΆ€ 확인 boolean hasPublicTransport = route.stream() @@ -287,9 +287,9 @@ private List> filterAndSortRoutes(List>comparingInt(this::calculateTransferCount) @@ -590,8 +590,20 @@ private List mergeConsecutiveRoutes(List p String toName = getNodeName(pathEdges.get(j - 1).getEndNode()); if (currentType == DirectionType.WALK) { + int walkDistance = 0; // λ―Έν„° λ‹¨μœ„ + Node walkStartNode = pathEdges.get(i).getStartNode(); + Node walkEndNode = pathEdges.get(j - 1).getEndNode(); + + if (walkStartNode != null && walkEndNode != null) { + double distanceKm = haversine( + walkStartNode.getLatitude(), walkStartNode.getLongitude(), + walkEndNode.getLatitude(), walkEndNode.getLongitude() + ); + walkDistance = (int) (distanceKm * 1000); // kmλ₯Ό m둜 λ³€ν™˜ + } + mergedSteps.add(new TransportationResponseDto.Step( - DirectionType.WALK, null, null, 0, null, null, fromName, toName + DirectionType.WALK, null, null, walkDistance, null, null, fromName, toName )); i = j; continue; @@ -623,20 +635,27 @@ private List mergeConsecutiveRoutes(List p } } } catch (Exception e) { - log.info("λ²„μŠ€ 정보 쑰회 μ‹€νŒ¨: {}", e.getMessage()); - } + log.error("λ²„μŠ€ 정보 쑰회 μ‹€νŒ¨: {}", e.getMessage()); + } } else if (currentType == DirectionType.SUBWAY) { try { if (currentEdge.getStartNode() != null) { - TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId()); + TransportationResponseDto.NodeInfo nodeInfo = facilityService.getNodeInfo(currentEdge.getStartNode().getId(), currentEdge.getRoute().getRouteId()); + subwayInfo = new TransportationResponseDto.SubwayInfo( nodeInfo.wheelchair(), nodeInfo.elevator(), nodeInfo.accessibleRestroom() ); + } else { + subwayInfo = new TransportationResponseDto.SubwayInfo( + new ArrayList<>(), + new ArrayList<>(), + false + ); } } catch (Exception e) { - log.info("μ§€ν•˜μ²  정보 쑰회 μ‹€νŒ¨: {}", e.getMessage()); + log.error("μ§€ν•˜μ²  정보 쑰회 μ‹€νŒ¨: {}", e.getMessage()); subwayInfo = new TransportationResponseDto.SubwayInfo( new ArrayList<>(), new ArrayList<>(), @@ -716,18 +735,26 @@ private Node findNearestNode(List nodes, double lat, double lon) { private int calculateTransferCount(List steps) { int transferCount = 0; - for (int i = 0; i < steps.size() - 1; i++) { - TransportationResponseDto.Step currentStep = steps.get(i); - TransportationResponseDto.Step nextStep = steps.get(i + 1); - - if (currentStep.mode() != DirectionType.WALK && nextStep.mode() != DirectionType.WALK) { - if (currentStep.mode() == nextStep.mode() && - currentStep.routeName() != null && nextStep.routeName() != null && - !currentStep.routeName().equals(nextStep.routeName())) { - transferCount++; - } else if (currentStep.mode() != nextStep.mode()) { - transferCount++; + DirectionType previousMode = null; + String previousRouteName = null; + + for (TransportationResponseDto.Step step : steps) { + if (step.mode() != DirectionType.WALK && step.mode() != DirectionType.FROM_WAYPOINT && step.mode() != DirectionType.TO_WAYPOINT) { + if (previousMode != null) { + if (previousMode == step.mode() && + previousRouteName != null && step.routeName() != null && + !previousRouteName.equals(step.routeName())) { + transferCount++; + } else if (previousMode == step.mode() && + previousRouteName != null && step.routeName() != null && + previousRouteName.equals(step.routeName())) { + transferCount++; + } else if (previousMode != step.mode()) { + transferCount++; + } } + previousMode = step.mode(); + previousRouteName = step.routeName(); } } return transferCount;