-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 버스 교통편 조회 API #1099
feat: 버스 교통편 조회 API #1099
Changes from 18 commits
f9c9d8d
f62e322
c138c80
70e5b38
180f899
8f487af
fa05713
ab7e344
cd81e3b
69ed6b8
e8f80cf
552b763
65a66b9
fca0a90
bb908b5
a8646e4
475534c
ef1ef9e
187fbd2
0552b56
031e938
2c4291b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,17 +3,20 @@ | |
import java.time.LocalDate; | ||
import java.util.List; | ||
|
||
import org.springframework.format.annotation.DateTimeFormat; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
|
||
import in.koreatech.koin.domain.bus.dto.BusCourseResponse; | ||
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; | ||
import in.koreatech.koin.domain.bus.dto.BusScheduleResponse; | ||
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse; | ||
import in.koreatech.koin.domain.bus.dto.CityBusTimetableResponse; | ||
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse; | ||
import in.koreatech.koin.domain.bus.model.BusTimetable; | ||
import in.koreatech.koin.domain.bus.model.enums.BusRouteType; | ||
import in.koreatech.koin.domain.bus.model.enums.BusStation; | ||
import in.koreatech.koin.domain.bus.model.enums.BusType; | ||
import in.koreatech.koin.domain.bus.model.enums.CityBusDirection; | ||
|
@@ -90,4 +93,30 @@ ResponseEntity<List<SingleBusTimeResponse>> getSearchTimetable( | |
@Operation(summary = "버스 노선 조회") | ||
@GetMapping("/courses") | ||
ResponseEntity<List<BusCourseResponse>> getBusCourses(); | ||
|
||
@ApiResponses( | ||
value = { | ||
@ApiResponse(responseCode = "200"), | ||
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), | ||
} | ||
) | ||
@Operation( | ||
summary = "버스 교통편 조회", | ||
description = """ | ||
### 버스 교통편 조회 | ||
- **시간** : 00:00 인 경우 해당 날짜의 모든 스케줄을 조회합니다. | ||
- **날짜** : 요일을 기준으로 스케줄을 출력합니다. 공휴일 처리는 구현되어 있지 않습니다. | ||
- **출발지 & 도착지** : 출발지와 도착지가 일치하는 경우 빈 리스트를 반환합니다. (천안역 -> 터미널) & (터미널 -> 천안역) 역시 빈 리스트를 반환합니다. | ||
""" | ||
) | ||
@GetMapping("/route") | ||
ResponseEntity<BusScheduleResponse> getBusRouteSchedule( | ||
@Parameter(description = "yyyy-MM-dd") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date, | ||
@Parameter(description = "HH:mm") @RequestParam String time, | ||
Comment on lines
+114
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Crequest와 response에서 모두 date와 time을 분리하여 전달하고 있는데, 분리해야 했던 이유가 있을까요?? 다른 곳에서는 대부분은 합쳐서 사용하고 있지 않나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 디자인에 '오늘 오후 10:30 출발' 이런식으로 출력하도록 되어 있길래 분리 하는 게 클라이언트 입장에서 더 편할 것 같다고 생각했습니다. 버스 도메인 다른 API(버스 검색)도 저런 형태로 정보를 받고 있어서 그대로 진행했습니다. |
||
@Parameter( | ||
description = "CITY, EXPRESS, SHUTTLE, ALL" | ||
) @RequestParam BusRouteType busRouteType, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A파라미터 타입으로 enum을 주면 swagger에서 조회 시 드롭다운으로 나타날텐데 description에 예시를 제공해야 할까요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불필요하네요! 제거하겠습니다. |
||
@Parameter(description = "KOREATECH, TERMINAL, STATION") @RequestParam BusStation depart, | ||
@Parameter(description = "KOREATECH, TERMINAL, STATION") @RequestParam BusStation arrival | ||
); | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Crequest dto 용도의 클래스를 만들어야 한다면 차라리 GET요청이지만 request body를 사용하는 건 어떻게 생각하시나요?? 기능에 비해 복잡도가 커지는 것 같아서 우려되네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파라미터로 받은 내용들을 다시 전부 단일 record로 묶어서 service를 호출하길래 get 요청에 body를 담지 않으려고 이렇게 한건가? 했습니다. 그런 구조였다면 괜찮을 수도 있을 것 같네요 👍 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package in.koreatech.koin.domain.bus.dto; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
import in.koreatech.koin.domain.bus.model.enums.BusRouteType; | ||
import in.koreatech.koin.domain.bus.model.enums.BusStation; | ||
|
||
public record BusRouteCommand( | ||
|
||
BusStation depart, | ||
BusStation arrive, | ||
BusRouteType busRouteType, | ||
LocalDate date, | ||
LocalTime time | ||
) { | ||
|
||
public boolean checkAvailableCourse() { | ||
if (depart == arrive) return false; | ||
if (depart == BusStation.STATION && arrive == BusStation.TERMINAL) return false; | ||
if (depart == BusStation.TERMINAL && arrive == BusStation.STATION) return false; | ||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package in.koreatech.koin.domain.bus.dto; | ||
|
||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; | ||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
import in.koreatech.koin.domain.bus.model.enums.BusStation; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
public record BusScheduleResponse( | ||
@Schema(description = "출발 정류장", example = "KOREATECH", requiredMode = REQUIRED) | ||
BusStation depart, | ||
@Schema(description = "도착 정류장", example = "TERMINAL", requiredMode = REQUIRED) | ||
BusStation arrival, | ||
@Schema(description = "출발 날짜", example = "2024-11-05", requiredMode = REQUIRED) | ||
LocalDate departDate, | ||
@Schema(description = "출발 시간", example = "12:00", requiredMode = REQUIRED) | ||
LocalTime departTime, | ||
@Schema(description = "교통편 조회 결과", example = """ | ||
[ | ||
{ | ||
"bus_type" : "express", | ||
"route_name" : "대성티앤이", | ||
"depart_time" : "16:50" | ||
}, | ||
{ | ||
"bus_type" : "city", | ||
"route_name" : "400", | ||
"depart_time" : "16:56" | ||
}, | ||
{ | ||
"bus_type" : "city", | ||
"route_name" : "402", | ||
"depart_time" : "17:30" | ||
}, | ||
{ | ||
"bus_type" : "shuttle", | ||
"route_name" : "주중(20시 00분)", | ||
"depart_time" : "20:00" | ||
} | ||
] | ||
""", requiredMode = NOT_REQUIRED) | ||
List<ScheduleInfo> schedule | ||
|
||
) { | ||
@JsonNaming(SnakeCaseStrategy.class) | ||
public record ScheduleInfo( | ||
String busType, | ||
String busName, | ||
LocalTime departTime | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A여기도 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정했습니다. |
||
|
||
public static Comparator<ScheduleInfo> compareBusType() { | ||
List<String> priority = List.of("shuttle", "express", "city"); | ||
return Comparator.comparingInt(schedule -> priority.indexOf(schedule.busType)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package in.koreatech.koin.domain.bus.model.enums; | ||
|
||
import java.util.Arrays; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
|
||
import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException; | ||
|
||
public enum BusRouteType { | ||
CITY, | ||
EXPRESS, | ||
SHUTTLE, | ||
ALL; | ||
|
||
@JsonCreator | ||
public static BusRouteType from(String busRouteTypeName) { | ||
return Arrays.stream(values()) | ||
.filter(busType -> busType.name().equalsIgnoreCase(busRouteTypeName)) | ||
.findAny() | ||
.orElseThrow(() -> BusTypeNotFoundException.withDetail("busRouteTypeName: " + busRouteTypeName)); | ||
} | ||
|
||
public String getName() { | ||
return this.name().toLowerCase(); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A주석으로 부연설명을 조금 달아두는 것도 좋을 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 적용했습니다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package in.koreatech.koin.domain.bus.model.express; | ||
|
||
import java.time.LocalTime; | ||
import java.util.List; | ||
|
||
public final class ExpressBusSchedule { | ||
|
||
private static final List<LocalTime> KOREA_TECH_SCHEDULE = List.of( | ||
LocalTime.of(7, 0), | ||
LocalTime.of(8, 30), | ||
LocalTime.of(9, 0), | ||
LocalTime.of(10, 0), | ||
LocalTime.of(12, 0), | ||
LocalTime.of(12, 30), | ||
LocalTime.of(13, 0), | ||
LocalTime.of(15, 0), | ||
LocalTime.of(16, 0), | ||
LocalTime.of(16, 40), | ||
LocalTime.of(18, 0), | ||
LocalTime.of(19, 30), | ||
LocalTime.of(20, 30) | ||
); | ||
|
||
private static final List<LocalTime> TERMINAL_SCHEDULE = List.of( | ||
LocalTime.of(8, 35), | ||
LocalTime.of(10, 35), | ||
LocalTime.of(11, 5), | ||
LocalTime.of(11, 35), | ||
LocalTime.of(13, 35), | ||
LocalTime.of(14, 35), | ||
LocalTime.of(15, 5), | ||
LocalTime.of(16, 35), | ||
LocalTime.of(17, 35), | ||
LocalTime.of(19, 5), | ||
LocalTime.of(19, 35), | ||
LocalTime.of(21, 5), | ||
LocalTime.of(22, 5) | ||
); | ||
|
||
public static List<LocalTime> getExpressBusScheduleToKoreaTech() { | ||
return KOREA_TECH_SCHEDULE; | ||
} | ||
|
||
public static List<LocalTime> getExpressBusScheduleToTerminal() { | ||
return TERMINAL_SCHEDULE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,18 @@ | ||
package in.koreatech.koin.domain.bus.model.mongo; | ||
|
||
import java.time.DayOfWeek; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import org.springframework.data.mongodb.core.mapping.Document; | ||
import org.springframework.data.mongodb.core.mapping.Field; | ||
|
||
import in.koreatech.koin.domain.bus.dto.BusScheduleResponse.ScheduleInfo; | ||
import in.koreatech.koin.domain.bus.model.enums.BusStation; | ||
import jakarta.persistence.Id; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
|
@@ -18,6 +24,11 @@ | |
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class CityBusTimetable { | ||
|
||
private static final Integer ADDITIONAL_TIME_DEPART_TO_KOREATECH_400 = 6; | ||
private static final Integer ADDITIONAL_TIME_DEPART_TO_KOREATECH_402 = 13; | ||
private static final Integer ADDITIONAL_TIME_DEPART_TO_KOREATECH_405 = 7; | ||
private static final Integer ADDITIONAL_TIME_DEPART_TO_STATION = 7; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A기점에서 정류장까지 걸리는 시간(분)인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다! 시내버스 API 에서 받아오는 출발 시간 데이터는 기점 출발 기준입니다. 예를 들어 400번의 경우 한기대 정류장이 아니라 출발지인 병천3리 정류장을 기준으로 스케줄 정보를 제공합니다. 따라서 사용자가 한기대 -> 터미널의 스케줄을 요청한 경우 시간 데이터에 병천3리 -> 한기대 사이의 이동 시간을 추가하여 시간을 보정합니다. 해당 작업을 위해 정의한 상수입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A해당 정보의 출처는 어디인지, 이 상수들의 용도가 무엇인지 주석으로 명시하면 좋을 것 같아요! |
||
|
||
@Id | ||
@Field("_id") | ||
private String routeId; | ||
|
@@ -38,6 +49,15 @@ private CityBusTimetable(BusInfo busInfo, List<BusTimetable> busTimetables, Loca | |
this.updatedAt = updatedAt; | ||
} | ||
|
||
public List<ScheduleInfo> getScheduleInfo(LocalDate date, BusStation depart) { | ||
Long busNumber = busInfo.getNumber(); | ||
return busTimetables.stream() | ||
.filter(busTimetable -> busTimetable.filterByDayOfWeek(date)) | ||
.flatMap(busTimetable -> busTimetable.applyTimeOffset(busNumber, depart).stream() | ||
.map(time -> new ScheduleInfo("city", busNumber.toString(), time))) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
@Getter | ||
public static class BusInfo { | ||
|
||
|
@@ -71,5 +91,31 @@ private BusTimetable(String dayOfWeek, List<String> departInfo) { | |
this.dayOfWeek = dayOfWeek; | ||
this.departInfo = departInfo; | ||
} | ||
|
||
public boolean filterByDayOfWeek(LocalDate date) { | ||
return switch (dayOfWeek) { | ||
case "평일" -> date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY; | ||
case "주말" -> date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY; | ||
default -> false; | ||
}; | ||
} | ||
|
||
public List<LocalTime> applyTimeOffset(Long busNumber, BusStation depart) { | ||
return departInfo.stream() | ||
.map(time -> { | ||
LocalTime schedule = LocalTime.parse(time); | ||
if (busNumber == 400 && depart == BusStation.KOREATECH) { | ||
schedule = schedule.plusMinutes(ADDITIONAL_TIME_DEPART_TO_KOREATECH_400); | ||
} else if (busNumber == 402 && depart == BusStation.KOREATECH) { | ||
schedule = schedule.plusMinutes(ADDITIONAL_TIME_DEPART_TO_KOREATECH_402); | ||
} else if (busNumber == 405 && depart == BusStation.KOREATECH) { | ||
schedule = schedule.plusMinutes(ADDITIONAL_TIME_DEPART_TO_KOREATECH_405); | ||
} else if (depart == BusStation.STATION) { | ||
schedule = schedule.plusMinutes(ADDITIONAL_TIME_DEPART_TO_STATION); | ||
} | ||
return schedule; | ||
}) | ||
.collect(Collectors.toList()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
R
터미널, 천안역 간 교통편 조회는 빈 결과를 반환한다는 요구사항이 있었나요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저희 데이터 중 터미널-천안역 간 정확한 스케줄 정보를 제공할 수 있는 데이터가 없어서 빈 결과를 반환하게 했습니다. 따로 제시된 요구사항은 없습니다. 천안역 -> 천안역이 빈 리스트를 반환하는 것과 같은 맥락으로 생각했습니다. PM님과 상의해야하는 부분일까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 회색지대인 요구사항이 있다면 임의로 구현하기보다는 PM님과 팀에서 논의하는게 좋을 것 같아요~
논의 올려주신내용 확인했습니다