From 2caa8292d6878f6a79712afa5ee7c4e39c60fcd7 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 14 Feb 2024 20:41:12 +0900 Subject: [PATCH 001/123] =?UTF-8?q?feat:=20API=20=EA=B3=A8=EA=B2=A9=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/controller/BusApi.java | 31 +++++++++++++++++++ .../domain/bus/controller/BusController.java | 26 ++++++++++++++++ .../domain/bus/dto/BusRemainTimeResponse.java | 4 +++ .../koreatech/koin/domain/bus/model/Bus.java | 4 +++ .../domain/bus/repository/BusRepository.java | 8 +++++ .../koin/domain/bus/service/BusService.java | 18 +++++++++++ 6 files changed, 91 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Bus.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/service/BusService.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java new file mode 100644 index 000000000..7fc11f901 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java @@ -0,0 +1,31 @@ +package in.koreatech.koin.domain.bus.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "(Normal) Bus: 버스", description = "버스 정보를 조회한다.") +public interface BusApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "이번 / 다음 버스 남은 시간 조회") + @GetMapping("/bus") + ResponseEntity getBusRemainTime( + @RequestParam(value = "bus_type") String busType, + @RequestParam String depart, + @RequestParam String arrival + ); +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java new file mode 100644 index 000000000..ec0fa2073 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.service.BusService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class BusController implements BusApi { + + private final BusService busService; + + @Override + public ResponseEntity getBusRemainTime( + @RequestParam(value = "bus_type") String busType, + @RequestParam String depart, + @RequestParam String arrival + ) { + BusRemainTimeResponse busRemainTime = busService.getBusRemainTime(busType, depart, arrival); + return ResponseEntity.ok().body(busRemainTime); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java new file mode 100644 index 000000000..f1e37d562 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -0,0 +1,4 @@ +package in.koreatech.koin.domain.bus.dto; + +public record BusRemainTimeResponse() { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java new file mode 100644 index 000000000..b7435aa9a --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -0,0 +1,4 @@ +package in.koreatech.koin.domain.bus.model; + +public class Bus { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java new file mode 100644 index 000000000..3711cf17f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -0,0 +1,8 @@ +package in.koreatech.koin.domain.bus.repository; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.bus.model.Bus; + +public interface BusRepository extends Repository { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java new file mode 100644 index 000000000..6f9be35e8 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -0,0 +1,18 @@ +package in.koreatech.koin.domain.bus.service; + +import org.springframework.stereotype.Service; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.repository.BusRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BusService { + + private final BusRepository busRepository; + + public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { + return null; + } +} From 26c1012834408515cadca267f573e665a5feb5f2 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:17:04 +0900 Subject: [PATCH 002/123] =?UTF-8?q?feat:=20mongoDB=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../koreatech/koin/domain/bus/model/Bus.java | 24 +++++++++++++++++++ .../domain/bus/repository/BusRepository.java | 8 +++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 062ad5076..f0f8826af 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.mysql:mysql-connector-j' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'com.mysql:mysql-connector-j' implementation 'org.jsoup:jsoup:1.15.3' diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java index b7435aa9a..fb36440ad 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -1,4 +1,28 @@ package in.koreatech.koin.domain.bus.model; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import jakarta.persistence.Id; +import lombok.Getter; + +@Getter +@Document(collection = "bus_timetables") public class Bus { + + @Id + @Field("_id") + private String id; + + @Field("bus_type") + private String busType; + + @Field("region") + private String region; + + @Field("direction") + private String direction; + + @Field("routes") + private String routes; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 3711cf17f..33e413cf1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,8 +1,12 @@ package in.koreatech.koin.domain.bus.repository; -import org.springframework.data.repository.Repository; +import java.util.List; + +import org.springframework.data.mongodb.repository.MongoRepository; import in.koreatech.koin.domain.bus.model.Bus; -public interface BusRepository extends Repository { +public interface BusRepository extends MongoRepository { + + List findAll(); } From f9140d034b4891ecde6ec31722783d3543ce3f6b Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:17:42 +0900 Subject: [PATCH 003/123] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index f1e37d562..b58dfd034 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -1,4 +1,29 @@ package in.koreatech.koin.domain.bus.dto; -public record BusRemainTimeResponse() { +import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; + +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.bus.model.Bus; + +@JsonNaming(SnakeCaseStrategy.class) +public record BusRemainTimeResponse( + String busType, + InnerBusResponse nextBus, + InnerBusResponse now_bus +) { + public static BusRemainTimeResponse from(Bus bus) { + return null; + } + + @JsonNaming(SnakeCaseStrategy.class) + private record InnerBusResponse( + Long busNumber, + Long remainTime + ) { + + public static InnerBusResponse from(Bus bus) { + return null; + } + } } From ab0fa003c0c7381541c84d9c59cc24a3e676f88e Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:49:57 +0900 Subject: [PATCH 004/123] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/exception/BusNotFoundException.java | 17 +++++++++++++++++ .../domain/bus/repository/BusRepository.java | 9 +++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java new file mode 100644 index 000000000..f3ef6ee4c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스가 존재하지 않습니다."; + + public BusNotFoundException(String message) { + super(message); + } + + public static BusNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 33e413cf1..0a2a56e56 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,12 +1,21 @@ package in.koreatech.koin.domain.bus.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.mongodb.repository.MongoRepository; +import in.koreatech.koin.domain.bus.exception.BusNotFoundException; import in.koreatech.koin.domain.bus.model.Bus; public interface BusRepository extends MongoRepository { List findAll(); + + Optional findByBusTypeAndDirection(String busType, String direction); + + default Bus getBusByBusTypeAndDirection(String busType, String direction) { + return findByBusTypeAndDirection(busType, direction) + .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); + } } From fc27b05fa248cb94c4d4edb93574382fa824b652 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 23:45:59 +0900 Subject: [PATCH 005/123] =?UTF-8?q?feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/controller/BusController.java | 3 +- .../BusStationNotFoundException.java | 17 ++++++++++ .../koreatech/koin/domain/bus/model/Bus.java | 4 ++- .../koin/domain/bus/model/BusStation.java | 34 +++++++++++++++++++ .../koin/domain/bus/model/Route.java | 26 ++++++++++++++ .../domain/bus/repository/BusRepository.java | 2 +- .../koin/domain/bus/service/BusService.java | 7 ++++ 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Route.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java index ec0fa2073..4f4f60d8f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.bus.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -14,7 +15,7 @@ public class BusController implements BusApi { private final BusService busService; - @Override + @GetMapping("/bus") public ResponseEntity getBusRemainTime( @RequestParam(value = "bus_type") String busType, @RequestParam String depart, diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java new file mode 100644 index 000000000..10bab1e3e --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusStationNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 정류장이 존재하지 않습니다."; + + public BusStationNotFoundException(String message) { + super(message); + } + + public static BusStationNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusStationNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java index fb36440ad..f68abf61d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.bus.model; +import java.util.List; + import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @@ -24,5 +26,5 @@ public class Bus { private String direction; @Field("routes") - private String routes; + private List routes; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java new file mode 100644 index 000000000..f8f84e287 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -0,0 +1,34 @@ +package in.koreatech.koin.domain.bus.model; + +import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; + +public enum BusStation { + KOREATECH("koreatech"), + STATION("station"), + TERMINAL("terminal"), + ; + + private final String name; + + BusStation(String name) { + this.name = name; + } + + public static String getDirection(String depart, String arrival) { + int departIndex = getBusStationIndex(depart); + int arrivalIndex = getBusStationIndex(arrival); + if (departIndex < arrivalIndex) { + return "from"; // 등교 + } + return "to"; // 하교 + } + + private static int getBusStationIndex(String busStation) { + for (int i = 0; i < values().length; i++) { + if (values()[i].name.equals(busStation)) { + return i; + } + } + throw BusStationNotFoundException.withDetail("busStation: " + busStation); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java new file mode 100644 index 000000000..cf0da00c6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.model; + +import java.util.List; + +import org.springframework.data.mongodb.core.mapping.Field; + +public class Route { + + @Field("route_name") + private String routeName; + + @Field("running_days") + private List runningDays; + + @Field("arrival_info") + private List arrivalInfo; + + public static class ArrivalNode { + + @Field("node_name") + private String nodeName; + + @Field("arrival_time") + private String arrivalTime; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 0a2a56e56..25ef6d11b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -14,7 +14,7 @@ public interface BusRepository extends MongoRepository { Optional findByBusTypeAndDirection(String busType, String direction); - default Bus getBusByBusTypeAndDirection(String busType, String direction) { + default Bus getByBusTypeAndDirection(String busType, String direction) { return findByBusTypeAndDirection(busType, direction) .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 6f9be35e8..2ae5e377d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,8 +1,11 @@ package in.koreatech.koin.domain.bus.service; +import java.util.List; + import org.springframework.stereotype.Service; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -13,6 +16,10 @@ public class BusService { private final BusRepository busRepository; public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { + List all = busRepository.findAll(); + // String direction = BusStation.getDirection(depart, arrival); + // Bus foundBus = busRepository.getByBusTypeAndDirection(busType, direction); + // return BusRemainTimeResponse.from(foundBus); return null; } } From 6d2326d01d9d2729eea0eea5772142af25bad3c1 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 16 Feb 2024 20:04:35 +0900 Subject: [PATCH 006/123] =?UTF-8?q?feat:=20=EB=AF=B8=EC=9A=B4=ED=96=89?= =?UTF-8?q?=EC=9D=B8=20=EB=B2=84=EC=8A=A4=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 6 +++--- .../bus/model/{Bus.java => BusCourse.java} | 10 ++++++++-- .../koin/domain/bus/model/Route.java | 10 ++++++++++ .../domain/bus/repository/BusRepository.java | 19 +++++++++---------- .../koin/domain/bus/service/BusService.java | 10 ++++++---- 5 files changed, 36 insertions(+), 19 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/{Bus.java => BusCourse.java} (69%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index b58dfd034..3ed542fef 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -12,7 +12,7 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus, InnerBusResponse now_bus ) { - public static BusRemainTimeResponse from(Bus bus) { + public static BusRemainTimeResponse from(BusCourse busCourse) { return null; } @@ -22,7 +22,7 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse from(Bus bus) { + public static InnerBusResponse from(BusCourse busCourse) { return null; } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java similarity index 69% rename from src/main/java/in/koreatech/koin/domain/bus/model/Bus.java rename to src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index f68abf61d..3dbcf5034 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.util.ArrayList; import java.util.List; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,7 +11,7 @@ @Getter @Document(collection = "bus_timetables") -public class Bus { +public class BusCourse { @Id @Field("_id") @@ -26,5 +27,10 @@ public class Bus { private String direction; @Field("routes") - private List routes; + private List routes = new ArrayList<>(); + + public boolean isRunning() { + routes.removeIf(route -> !route.isRunning()); + return !routes.isEmpty(); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index cf0da00c6..3c1f49234 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -4,6 +4,9 @@ import org.springframework.data.mongodb.core.mapping.Field; +import lombok.Getter; + +@Getter public class Route { @Field("route_name") @@ -15,6 +18,13 @@ public class Route { @Field("arrival_info") private List arrivalInfo; + public boolean isRunning() { + if (routeName.equals("미운행")) { + return false; + } + return !arrivalInfo.isEmpty(); + } + public static class ArrivalNode { @Field("node_name") diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 25ef6d11b..2d0731ab4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,21 +1,20 @@ package in.koreatech.koin.domain.bus.repository; import java.util.List; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.bus.exception.BusNotFoundException; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; -public interface BusRepository extends MongoRepository { +public interface BusRepository extends Repository { - List findAll(); + List findAll(); - Optional findByBusTypeAndDirection(String busType, String direction); + List findByBusTypeAndDirection(String busType, String direction); - default Bus getByBusTypeAndDirection(String busType, String direction) { - return findByBusTypeAndDirection(busType, direction) - .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); + default List getByBusTypeAndDirection(String busType, String direction) { + List busCourses = findByBusTypeAndDirection(busType, direction); + busCourses.removeIf(busCourse -> !busCourse.isRunning()); + return busCourses; } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 2ae5e377d..cfefc04fa 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -5,7 +5,8 @@ import org.springframework.stereotype.Service; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -16,9 +17,10 @@ public class BusService { private final BusRepository busRepository; public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - List all = busRepository.findAll(); - // String direction = BusStation.getDirection(depart, arrival); - // Bus foundBus = busRepository.getByBusTypeAndDirection(busType, direction); + List all = busRepository.findAll(); + String direction = BusStation.getDirection(depart, arrival); + + List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); // return BusRemainTimeResponse.from(foundBus); return null; } From 2c553cbeabe0fd43e0cd1a34891a57d8508ba36a Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 19 Feb 2024 18:31:58 +0900 Subject: [PATCH 007/123] =?UTF-8?q?feat:=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/BusTypeNotFoundException.java | 17 ++++++++++++ .../koin/domain/bus/model/BusType.java | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusType.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java new file mode 100644 index 000000000..80d5fbf5c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusTypeNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 타입이 존재하지 않습니다."; + + public BusTypeNotFoundException(String message) { + super(message); + } + + public static BusTypeNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusTypeNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java new file mode 100644 index 000000000..af5e3104f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.model; + +import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; + +public enum BusType { + CITY("city"), + EXPRESS("express"), + SHUTTLE("shuttle"), + COMMUTING("commuting"), + ; + + private String name; + + BusType(String name) { + this.name = name; + } + + public static void validate(String busType) { + for (int i = 0; i < values().length; i++) { + if (values()[i].name.equals(busType)) { + return; + } + } + throw BusStationNotFoundException.withDetail("busType: " + busType); + } +} From 1933b339c5c5720243ba3c5af30b87039c86a84f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 19 Feb 2024 20:12:59 +0900 Subject: [PATCH 008/123] =?UTF-8?q?feat:=20=EB=82=A8=EC=9D=80=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/BusRemainTime.java | 40 +++++++++++++++++++ .../koin/domain/bus/service/BusService.java | 2 + 2 files changed, 42 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java new file mode 100644 index 000000000..5d87de9e5 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -0,0 +1,40 @@ +package in.koreatech.koin.domain.bus.model; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import lombok.Builder; + +public class BusRemainTime { + + private final LocalTime busArrivalTime; + + public boolean isBefore() { + return LocalTime.now().isBefore(busArrivalTime); + } + + public Long getRemainSeconds() { + if (isBefore()) { + return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); + } + return null; + // return 86400L - Duration.between(busArrivalTime, LocalTime.now()).toSeconds(); + } + + public static BusRemainTime from(String remainTime) { + return builder() + .busArrivalTime(toLocalTime(remainTime)) + .build(); + } + + private static LocalTime toLocalTime(String remainTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + return LocalTime.parse(remainTime, formatter); + } + + @Builder + private BusRemainTime(LocalTime busArrivalTime) { + this.busArrivalTime = busArrivalTime; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index cfefc04fa..50b49bd1c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -6,6 +6,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str String direction = BusStation.getDirection(depart, arrival); List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); + BusRemainTime busRemainTime = BusRemainTime.from("10:13"); // return BusRemainTimeResponse.from(foundBus); return null; } From 023840c236e3349345bbae1dac6be3bd8089637c Mon Sep 17 00:00:00 2001 From: songsunkook Date: Tue, 20 Feb 2024 16:57:01 +0900 Subject: [PATCH 009/123] =?UTF-8?q?feat:=20=EA=B0=80=EC=9E=A5=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=B2=84=EC=8A=A4=EA=B9=8C=EC=A7=80=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20=EC=8B=9C=EA=B0=84=20=EC=9D=91=EB=8B=B5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/dto/BusRemainTimeCityResponse.java | 29 +++++++++++++++++++ .../domain/bus/dto/BusRemainTimeResponse.java | 26 ++++++++++++----- .../koin/domain/bus/model/BusRemainTime.java | 7 ++++- .../koin/domain/bus/model/Route.java | 5 ++-- .../koin/domain/bus/service/BusService.java | 28 +++++++++++++++--- 5 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java new file mode 100644 index 000000000..92235643c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java @@ -0,0 +1,29 @@ +package in.koreatech.koin.domain.bus.dto; + +import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; + +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.bus.model.BusCourse; + +@JsonNaming(SnakeCaseStrategy.class) +public record BusRemainTimeCityResponse( + String busType, + InnerBusResponse nowBus, + InnerBusResponse nextBus +) { + public static BusRemainTimeCityResponse from(BusCourse busCourse) { + return null; + } + + @JsonNaming(SnakeCaseStrategy.class) + private record InnerBusResponse( + Long busNumber, + Long remainTime + ) { + + public static InnerBusResponse from(BusCourse busCourse) { + return null; + } + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 3ed542fef..a39565cec 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -2,28 +2,38 @@ import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import java.util.List; + import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( String busType, - InnerBusResponse nextBus, - InnerBusResponse now_bus + InnerBusResponse nowBus, + InnerBusResponse nextBus ) { - public static BusRemainTimeResponse from(BusCourse busCourse) { - return null; + + public static BusRemainTimeResponse of(String busType, List remainTimes) { + return new BusRemainTimeResponse( + busType, + InnerBusResponse.from(remainTimes, 0), + InnerBusResponse.from(remainTimes, 1) + ); } @JsonNaming(SnakeCaseStrategy.class) private record InnerBusResponse( - Long busNumber, Long remainTime ) { - public static InnerBusResponse from(BusCourse busCourse) { - return null; + public static InnerBusResponse from(List remainTimes, int index) { + Long result = null; + if (index < remainTimes.size()) { + result = remainTimes.get(index).getRemainSeconds(); + } + return new InnerBusResponse(result); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 5d87de9e5..579a005b1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -6,7 +6,7 @@ import lombok.Builder; -public class BusRemainTime { +public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -37,4 +37,9 @@ private static LocalTime toLocalTime(String remainTime) { private BusRemainTime(LocalTime busArrivalTime) { this.busArrivalTime = busArrivalTime; } + + @Override + public int compareTo(BusRemainTime o) { + return busArrivalTime.compareTo(o.busArrivalTime); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 3c1f49234..cf5429c4f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -16,15 +16,16 @@ public class Route { private List runningDays; @Field("arrival_info") - private List arrivalInfo; + private List arrivalInfos; public boolean isRunning() { if (routeName.equals("미운행")) { return false; } - return !arrivalInfo.isEmpty(); + return !arrivalInfos.isEmpty(); } + @Getter public static class ArrivalNode { @Field("node_name") diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 50b49bd1c..2e02861d2 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -8,6 +8,7 @@ import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -17,13 +18,32 @@ public class BusService { private final BusRepository busRepository; + /** + * TODO + * 궁금한 점 + * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? + * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) + * - 응답 객체를 보면 시내버스만 버스 번호를 함께 반환하는데, 그럼 Response DTO를 두 개 둬야 하는가? 그럼 controller 메서드에서는 반환하는 객체 타입을 명시할 수 없는데 괜찮은가? + * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) + */ + public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { List all = busRepository.findAll(); String direction = BusStation.getDirection(depart, arrival); - List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); - BusRemainTime busRemainTime = BusRemainTime.from("10:13"); - // return BusRemainTimeResponse.from(foundBus); - return null; + + List remainTimes = foundBusCourses.stream() + .map(BusCourse::getRoutes) + .flatMap(routes -> + routes.stream() + .map(route -> route.getArrivalInfos().get(0)) + .map(Route.ArrivalNode::getArrivalTime) + .map(BusRemainTime::from) + .filter(BusRemainTime::isBefore) + ) + .sorted() + .toList(); + + return BusRemainTimeResponse.of(busType, remainTimes); } } From 0545361e8ae1537343ec8e16bae1a61d228e7523 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Tue, 20 Feb 2024 17:23:29 +0900 Subject: [PATCH 010/123] =?UTF-8?q?feat:=20=EB=AF=B8=EC=9A=B4=ED=96=89=20?= =?UTF-8?q?=EC=9A=94=EC=9D=BC=20=EC=A0=9C=EC=99=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/model/Route.java | 8 ++++++-- .../in/koreatech/koin/domain/bus/service/BusService.java | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index cf5429c4f..290ed44a9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -1,6 +1,9 @@ package in.koreatech.koin.domain.bus.model; +import java.time.LocalDateTime; +import java.time.format.TextStyle; import java.util.List; +import java.util.Locale; import org.springframework.data.mongodb.core.mapping.Field; @@ -19,10 +22,11 @@ public class Route { private List arrivalInfos; public boolean isRunning() { - if (routeName.equals("미운행")) { + if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - return !arrivalInfos.isEmpty(); + String todayOfWeek = LocalDateTime.now().getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase(); + return runningDays.contains(todayOfWeek); } @Getter diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 2e02861d2..8a9bdfd60 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -19,7 +19,6 @@ public class BusService { private final BusRepository busRepository; /** - * TODO * 궁금한 점 * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) @@ -36,6 +35,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() + .filter(Route::isRunning) .map(route -> route.getArrivalInfos().get(0)) .map(Route.ArrivalNode::getArrivalTime) .map(BusRemainTime::from) From 9f00613f2dc7f0fe4a9bd0c0e83146f26a848335 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 15:39:48 +0900 Subject: [PATCH 011/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EA=B1=B0=EC=9E=A5=20=EA=B2=80=EC=A6=9D=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=8B=9C=EA=B0=84=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/dto/BusRemainTimeCityResponse.java | 29 ------------------- .../domain/bus/dto/BusRemainTimeResponse.java | 9 +++--- .../koin/domain/bus/model/BusRemainTime.java | 3 +- .../domain/bus/repository/BusRepository.java | 8 ++--- .../koin/domain/bus/service/BusService.java | 15 +++++----- 5 files changed, 17 insertions(+), 47 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java deleted file mode 100644 index 92235643c..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package in.koreatech.koin.domain.bus.dto; - -import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; - -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import in.koreatech.koin.domain.bus.model.BusCourse; - -@JsonNaming(SnakeCaseStrategy.class) -public record BusRemainTimeCityResponse( - String busType, - InnerBusResponse nowBus, - InnerBusResponse nextBus -) { - public static BusRemainTimeCityResponse from(BusCourse busCourse) { - return null; - } - - @JsonNaming(SnakeCaseStrategy.class) - private record InnerBusResponse( - Long busNumber, - Long remainTime - ) { - - public static InnerBusResponse from(BusCourse busCourse) { - return null; - } - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index a39565cec..aec490576 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -18,22 +18,23 @@ public record BusRemainTimeResponse( public static BusRemainTimeResponse of(String busType, List remainTimes) { return new BusRemainTimeResponse( busType, - InnerBusResponse.from(remainTimes, 0), - InnerBusResponse.from(remainTimes, 1) + InnerBusResponse.of(remainTimes, 0), + InnerBusResponse.of(remainTimes, 1) ); } @JsonNaming(SnakeCaseStrategy.class) private record InnerBusResponse( + Long busNumber, Long remainTime ) { - public static InnerBusResponse from(List remainTimes, int index) { + public static InnerBusResponse of(List remainTimes, int index) { Long result = null; if (index < remainTimes.size()) { result = remainTimes.get(index).getRemainSeconds(); } - return new InnerBusResponse(result); + return new InnerBusResponse(null, result); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 579a005b1..fd281513c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -5,7 +5,9 @@ import java.time.format.DateTimeFormatter; import lombok.Builder; +import lombok.EqualsAndHashCode; +@EqualsAndHashCode public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -19,7 +21,6 @@ public Long getRemainSeconds() { return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); } return null; - // return 86400L - Duration.between(busArrivalTime, LocalTime.now()).toSeconds(); } public static BusRemainTime from(String remainTime) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 2d0731ab4..a4423ec9b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -8,12 +8,10 @@ public interface BusRepository extends Repository { - List findAll(); + List findByBusType(String busType); - List findByBusTypeAndDirection(String busType, String direction); - - default List getByBusTypeAndDirection(String busType, String direction) { - List busCourses = findByBusTypeAndDirection(busType, direction); + default List getByBusType(String busType) { + List busCourses = findByBusType(busType); busCourses.removeIf(busCourse -> !busCourse.isRunning()); return busCourses; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 8a9bdfd60..0c18098a9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -7,7 +7,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -21,17 +21,15 @@ public class BusService { /** * 궁금한 점 * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? - * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) - * - 응답 객체를 보면 시내버스만 버스 번호를 함께 반환하는데, 그럼 Response DTO를 두 개 둬야 하는가? 그럼 controller 메서드에서는 반환하는 객체 타입을 명시할 수 없는데 괜찮은가? + * -> O * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) + * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - List all = busRepository.findAll(); - String direction = BusStation.getDirection(depart, arrival); - List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); - - List remainTimes = foundBusCourses.stream() + BusType.validate(busType); + List remainTimes = busRepository.getByBusType(busType) + .stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() @@ -41,6 +39,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str .map(BusRemainTime::from) .filter(BusRemainTime::isBefore) ) + .distinct() .sorted() .toList(); From b72496397fcbc54e8987a31a77004cd5bb74f269 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 16:30:48 +0900 Subject: [PATCH 012/123] =?UTF-8?q?refactor:=20enum=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../koin/domain/bus/dto/BusRemainTimeResponse.java | 5 +++-- .../koreatech/koin/domain/bus/model/BusStation.java | 13 ++----------- .../in/koreatech/koin/domain/bus/model/BusType.java | 6 ++++-- .../koin/domain/bus/repository/BusRepository.java | 5 +++-- .../koin/domain/bus/service/BusService.java | 7 +++++-- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index aec490576..fe94b60d7 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusType; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -15,9 +16,9 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(String busType, List remainTimes) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes) { return new BusRemainTimeResponse( - busType, + busType.getName(), InnerBusResponse.of(remainTimes, 0), InnerBusResponse.of(remainTimes, 1) ); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index f8f84e287..3571bdb22 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -14,19 +14,10 @@ public enum BusStation { this.name = name; } - public static String getDirection(String depart, String arrival) { - int departIndex = getBusStationIndex(depart); - int arrivalIndex = getBusStationIndex(arrival); - if (departIndex < arrivalIndex) { - return "from"; // 등교 - } - return "to"; // 하교 - } - - private static int getBusStationIndex(String busStation) { + public static BusStation from(String busStation) { for (int i = 0; i < values().length; i++) { if (values()[i].name.equals(busStation)) { - return i; + return values()[i]; } } throw BusStationNotFoundException.withDetail("busStation: " + busStation); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java index af5e3104f..1509fb105 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -1,7 +1,9 @@ package in.koreatech.koin.domain.bus.model; import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import lombok.Getter; +@Getter public enum BusType { CITY("city"), EXPRESS("express"), @@ -15,10 +17,10 @@ public enum BusType { this.name = name; } - public static void validate(String busType) { + public static BusType from(String busType) { for (int i = 0; i < values().length; i++) { if (values()[i].name.equals(busType)) { - return; + return values()[i]; } } throw BusStationNotFoundException.withDetail("busType: " + busType); diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index a4423ec9b..ddd2cb84b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -5,13 +5,14 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusType; public interface BusRepository extends Repository { List findByBusType(String busType); - default List getByBusType(String busType) { - List busCourses = findByBusType(busType); + default List getByBusType(BusType busType) { + List busCourses = findByBusType(busType.getName()); busCourses.removeIf(busCourse -> !busCourse.isRunning()); return busCourses; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 0c18098a9..76655ad06 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -7,6 +7,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; @@ -26,8 +27,10 @@ public class BusService { * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ - public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - BusType.validate(busType); + public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { + BusStation departStation = BusStation.from(departStr); + BusStation arrivalStation = BusStation.from(arrivalStr); + BusType busType = BusType.from(busTypeStr); List remainTimes = busRepository.getByBusType(busType) .stream() .map(BusCourse::getRoutes) From a18ccf5006de329d94000935e681bf0d51013fef Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 18:37:36 +0900 Subject: [PATCH 013/123] =?UTF-8?q?refactor:=20=EB=93=B1=ED=95=98=EA=B5=90?= =?UTF-8?q?=20=EB=B0=A9=ED=96=A5=20=EA=B8=B0=EC=A4=80=20=ED=83=90=EC=83=89?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A0=95=EB=A5=98=EC=9E=A5=20=ED=83=90?= =?UTF-8?q?=EC=83=89=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusArrivalNodeNotFoundException.java | 17 ++++++++++ .../koin/domain/bus/model/BusStation.java | 14 +++++--- .../koin/domain/bus/model/Route.java | 32 ++++++++++++++++++- .../domain/bus/repository/BusRepository.java | 4 +-- .../koin/domain/bus/service/BusService.java | 12 +++---- 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java new file mode 100644 index 000000000..a8700eaf0 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusArrivalNodeNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 경로 상에 해당 노드가 존재하지 않습니다."; + + public BusArrivalNodeNotFoundException(String message) { + super(message); + } + + public static BusArrivalNodeNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusArrivalNodeNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 3571bdb22..25bc2bc24 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -1,17 +1,23 @@ package in.koreatech.koin.domain.bus.model; +import java.util.List; + import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import lombok.Getter; +@Getter public enum BusStation { - KOREATECH("koreatech"), - STATION("station"), - TERMINAL("terminal"), + KOREATECH("koreatech", List.of("학교", "한기대")), + STATION("station", List.of("천안역", "천안역(학화호두과자)")), + TERMINAL("terminal", List.of("터미널", "터미널(신세계 앞 횡단보도)")), ; private final String name; + private final List displayNames; - BusStation(String name) { + BusStation(String name, List displayNames) { this.name = name; + this.displayNames = displayNames; } public static BusStation from(String busStation) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 290ed44a9..60ee2db27 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -7,6 +7,7 @@ import org.springframework.data.mongodb.core.mapping.Field; +import in.koreatech.koin.domain.bus.exception.BusArrivalNodeNotFoundException; import lombok.Getter; @Getter @@ -25,10 +26,39 @@ public boolean isRunning() { if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - String todayOfWeek = LocalDateTime.now().getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase(); + String todayOfWeek = LocalDateTime.now() + .getDayOfWeek() + .getDisplayName(TextStyle.SHORT, Locale.US) + .toUpperCase(); return runningDays.contains(todayOfWeek); } + public boolean isCorrectRoute(BusStation depart, BusStation arrival) { + boolean foundDepart = false; + for (ArrivalNode node : arrivalInfos) { + if (depart.getDisplayNames().contains(node.getNodeName()) + && (BusRemainTime.from(node.getArrivalTime()).isBefore())) { + foundDepart = true; + } + if (arrival.getDisplayNames().contains(node.getNodeName()) && foundDepart) { + return true; + } + } + return false; + } + + public BusRemainTime getRemainTime(BusStation busStation) { + ArrivalNode convertedNode = convertToArrivalNode(busStation); + return BusRemainTime.from(convertedNode.arrivalTime); + } + + private ArrivalNode convertToArrivalNode(BusStation busStation) { + return arrivalInfos.stream() + .filter(node -> busStation.getDisplayNames().contains(node.getNodeName())) + .findFirst() + .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail("routeName: " + routeName + ", busStation: " + busStation.getName())); + } + @Getter public static class ArrivalNode { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index ddd2cb84b..b0abee48d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -12,8 +12,6 @@ public interface BusRepository extends Repository { List findByBusType(String busType); default List getByBusType(BusType busType) { - List busCourses = findByBusType(busType.getName()); - busCourses.removeIf(busCourse -> !busCourse.isRunning()); - return busCourses; + return findByBusType(busType.getName()); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 76655ad06..e3572cf58 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -21,8 +21,6 @@ public class BusService { /** * 궁금한 점 - * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? - * -> O * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ @@ -31,16 +29,14 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt BusStation departStation = BusStation.from(departStr); BusStation arrivalStation = BusStation.from(arrivalStr); BusType busType = BusType.from(busTypeStr); - List remainTimes = busRepository.getByBusType(busType) - .stream() + + List remainTimes = busRepository.getByBusType(busType).stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() .filter(Route::isRunning) - .map(route -> route.getArrivalInfos().get(0)) - .map(Route.ArrivalNode::getArrivalTime) - .map(BusRemainTime::from) - .filter(BusRemainTime::isBefore) + .filter(route -> route.isCorrectRoute(departStation, arrivalStation)) + .map(route -> route.getRemainTime(departStation)) ) .distinct() .sorted() From 239ba06f394b140ae8277c59f84f6d227953166f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 18:51:09 +0900 Subject: [PATCH 014/123] =?UTF-8?q?remove:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BusCourse.isRunning() --- .../java/in/koreatech/koin/domain/bus/model/BusCourse.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index 3dbcf5034..b0430edea 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -28,9 +28,4 @@ public class BusCourse { @Field("routes") private List routes = new ArrayList<>(); - - public boolean isRunning() { - routes.removeIf(route -> !route.isRunning()); - return !routes.isEmpty(); - } } From b30244bdd56d6a6c46c08ae7630ff2e875873722 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 19:00:14 +0900 Subject: [PATCH 015/123] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20mocking=20=EC=9C=84=ED=95=B4=20Clock=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 11 ++++++----- .../koin/domain/bus/model/BusRemainTime.java | 11 ++++++----- .../in/koreatech/koin/domain/bus/model/Route.java | 9 +++++---- .../koin/domain/bus/service/BusService.java | 9 +++++---- .../koreatech/koin/global/config/ClockConfig.java | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/global/config/ClockConfig.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index fe94b60d7..3128ac186 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -2,6 +2,7 @@ import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import java.time.Clock; import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -16,11 +17,11 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(BusType busType, List remainTimes) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.getName(), - InnerBusResponse.of(remainTimes, 0), - InnerBusResponse.of(remainTimes, 1) + InnerBusResponse.of(remainTimes, 0, clock), + InnerBusResponse.of(remainTimes, 1, clock) ); } @@ -30,10 +31,10 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse of(List remainTimes, int index) { + public static InnerBusResponse of(List remainTimes, int index, Clock clock) { Long result = null; if (index < remainTimes.size()) { - result = remainTimes.get(index).getRemainSeconds(); + result = remainTimes.get(index).getRemainSeconds(clock); } return new InnerBusResponse(null, result); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index fd281513c..427a5d958 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.time.Clock; import java.time.Duration; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -12,13 +13,13 @@ public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; - public boolean isBefore() { - return LocalTime.now().isBefore(busArrivalTime); + public boolean isBefore(Clock clock) { + return LocalTime.now(clock).isBefore(busArrivalTime); } - public Long getRemainSeconds() { - if (isBefore()) { - return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); + public Long getRemainSeconds(Clock clock) { + if (isBefore(clock)) { + return Duration.between(LocalTime.now(clock), busArrivalTime).toSeconds(); } return null; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 60ee2db27..a67abbcca 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.time.Clock; import java.time.LocalDateTime; import java.time.format.TextStyle; import java.util.List; @@ -22,22 +23,22 @@ public class Route { @Field("arrival_info") private List arrivalInfos; - public boolean isRunning() { + public boolean isRunning(Clock clock) { if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - String todayOfWeek = LocalDateTime.now() + String todayOfWeek = LocalDateTime.now(clock) .getDayOfWeek() .getDisplayName(TextStyle.SHORT, Locale.US) .toUpperCase(); return runningDays.contains(todayOfWeek); } - public boolean isCorrectRoute(BusStation depart, BusStation arrival) { + public boolean isCorrectRoute(BusStation depart, BusStation arrival, Clock clock) { boolean foundDepart = false; for (ArrivalNode node : arrivalInfos) { if (depart.getDisplayNames().contains(node.getNodeName()) - && (BusRemainTime.from(node.getArrivalTime()).isBefore())) { + && (BusRemainTime.from(node.getArrivalTime()).isBefore(clock))) { foundDepart = true; } if (arrival.getDisplayNames().contains(node.getNodeName()) && foundDepart) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index e3572cf58..afe6741df 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.service; +import java.time.Clock; import java.util.List; import org.springframework.stereotype.Service; @@ -9,7 +10,6 @@ import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; -import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -17,6 +17,7 @@ @RequiredArgsConstructor public class BusService { + private final Clock clock; private final BusRepository busRepository; /** @@ -34,14 +35,14 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() - .filter(Route::isRunning) - .filter(route -> route.isCorrectRoute(departStation, arrivalStation)) + .filter(route -> route.isRunning(clock)) + .filter(route -> route.isCorrectRoute(departStation, arrivalStation, clock)) .map(route -> route.getRemainTime(departStation)) ) .distinct() .sorted() .toList(); - return BusRemainTimeResponse.of(busType, remainTimes); + return BusRemainTimeResponse.of(busType, remainTimes, clock); } } diff --git a/src/main/java/in/koreatech/koin/global/config/ClockConfig.java b/src/main/java/in/koreatech/koin/global/config/ClockConfig.java new file mode 100644 index 000000000..ae4211a0e --- /dev/null +++ b/src/main/java/in/koreatech/koin/global/config/ClockConfig.java @@ -0,0 +1,15 @@ +package in.koreatech.koin.global.config; + +import java.time.Clock; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ClockConfig { + + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } +} From b8788b45d0a7bd84878fda710413d6dd689ad4bb Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 23 Feb 2024 19:08:58 +0900 Subject: [PATCH 016/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20mongoDB=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/AcceptanceTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index 01acc0509..a960fad73 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -2,8 +2,6 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import in.koreatech.koin.support.DBInitializer; -import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,6 +15,9 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.utility.DockerImageName; +import in.koreatech.koin.support.DBInitializer; +import io.restassured.RestAssured; + @SpringBootTest(webEnvironment = RANDOM_PORT) @Import(DBInitializer.class) @ActiveProfiles("test") @@ -37,6 +38,9 @@ public abstract class AcceptanceTest { @Container protected static GenericContainer redisContainer; + @Container + protected static GenericContainer mongoContainer; + @DynamicPropertySource private static void configureProperties(final DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", mySqlContainer::getJdbcUrl); @@ -44,6 +48,9 @@ private static void configureProperties(final DynamicPropertyRegistry registry) registry.add("spring.datasource.password", () -> ROOT_PASSWORD); registry.add("spring.data.redis.host", redisContainer::getHost); registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(6379).toString()); + registry.add("spring.data.mongodb.host", mongoContainer::getHost); + registry.add("spring.data.mongodb.port", () -> mongoContainer.getMappedPort(27017).toString()); + registry.add("spring.data.mongodb.database", () -> "test"); } static { @@ -57,8 +64,13 @@ private static void configureProperties(final DynamicPropertyRegistry registry) DockerImageName.parse("redis:4.0.10")) .withExposedPorts(6379); + mongoContainer = new GenericContainer<>( + DockerImageName.parse("mongo:7.0")) + .withExposedPorts(27017); + mySqlContainer.start(); redisContainer.start(); + mongoContainer.start(); } @BeforeEach From f356d513b31ffd4c7ab4cad5bdb463a13aafa8b0 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 23 Feb 2024 19:09:24 +0900 Subject: [PATCH 017/123] =?UTF-8?q?test:=20=EC=85=94=ED=8B=80=20=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/BusCourse.java | 12 ++ .../koin/domain/bus/model/Route.java | 17 +++ .../domain/bus/repository/BusRepository.java | 2 + .../koin/domain/bus/service/BusService.java | 6 +- .../koreatech/koin/acceptance/BusApiTest.java | 115 ++++++++++++++++++ 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/test/java/in/koreatech/koin/acceptance/BusApiTest.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index b0430edea..e3ea129bc 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -7,10 +7,14 @@ import org.springframework.data.mongodb.core.mapping.Field; import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Document(collection = "bus_timetables") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class BusCourse { @Id @@ -28,4 +32,12 @@ public class BusCourse { @Field("routes") private List routes = new ArrayList<>(); + + @Builder + public BusCourse(String busType, String region, String direction, List routes) { + this.busType = busType; + this.region = region; + this.direction = direction; + this.routes = routes; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index a67abbcca..362ed7d6f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -9,9 +9,13 @@ import org.springframework.data.mongodb.core.mapping.Field; import in.koreatech.koin.domain.bus.exception.BusArrivalNodeNotFoundException; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Route { @Field("route_name") @@ -60,6 +64,13 @@ private ArrivalNode convertToArrivalNode(BusStation busStation) { .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail("routeName: " + routeName + ", busStation: " + busStation.getName())); } + @Builder + public Route(String routeName, List runningDays, List arrivalInfos) { + this.routeName = routeName; + this.runningDays = runningDays; + this.arrivalInfos = arrivalInfos; + } + @Getter public static class ArrivalNode { @@ -68,5 +79,11 @@ public static class ArrivalNode { @Field("arrival_time") private String arrivalTime; + + @Builder + public ArrivalNode(String nodeName, String arrivalTime) { + this.nodeName = nodeName; + this.arrivalTime = arrivalTime; + } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index b0abee48d..aebcf21d4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -9,6 +9,8 @@ public interface BusRepository extends Repository { + BusCourse save(BusCourse busCourse); + List findByBusType(String busType); default List getByBusType(BusType busType) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index afe6741df..725ba58e1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -21,9 +21,9 @@ public class BusService { private final BusRepository busRepository; /** - * 궁금한 점 - * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) - * -> X. 테스트 코드는 LocalDateTime을 mocking한다. + * TODO + * 1. city (시내버스) 구현 + * 2. express (시외버스) 구현 */ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java new file mode 100644 index 000000000..5ef4b5e9e --- /dev/null +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -0,0 +1,115 @@ +package in.koreatech.koin.acceptance; + +import static java.time.format.DateTimeFormatter.ofPattern; +import static org.mockito.Mockito.when; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.List; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.Route; +import in.koreatech.koin.domain.bus.repository.BusRepository; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +@ExtendWith(MockitoExtension.class) +class BusApiTest extends AcceptanceTest { + + @MockBean + private Clock clock; + + @Autowired + private BusRepository busRepository; + + @BeforeEach + void mockingClock() { + when(clock.instant()).thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + } + + @Test + @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") + void getNextShuttleBusRemainTime() { + final String arrivalTime = "18:10"; + + BusType busType = BusType.from("shuttle"); + BusStation depart = BusStation.from("koreatech"); + BusStation arrival = BusStation.from("terminal"); + + BusCourse busCourse = BusCourse.builder() + .busType("shuttle") + .region("천안") + .direction("from") + .routes( + List.of( + Route.builder() + .routeName("주중") + .runningDays(List.of("MON", "TUE", "WED", "THU", "FRI")) + .arrivalInfos( + List.of( + Route.ArrivalNode.builder() + .nodeName("한기대") + .arrivalTime(arrivalTime) + .build(), + Route.ArrivalNode.builder() + .nodeName("신계초,운전리,연춘리") + .arrivalTime("정차") + .build(), + Route.ArrivalNode.builder() + .nodeName("천안역(학화호두과자)") + .arrivalTime("18:50") + .build(), + Route.ArrivalNode.builder() + .nodeName("터미널(신세계 앞 횡단보도)") + .arrivalTime("18:55") + .build() + ) + ) + .build() + ) + ) + .build(); + busRepository.save(busCourse); + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("bus_type", busType.getName()) + .param("depart", depart.getName()) + .param("arrival", arrival.getName()) + .get("/bus") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.body().jsonPath().getString("bus_type")).isEqualTo(busType.getName()); + softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull(); + softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( + BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull(); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull(); + } + ); + } +} From 09ca1737e18e262f483826112ff6e584b53a334d Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 26 Feb 2024 11:29:56 +0900 Subject: [PATCH 018/123] =?UTF-8?q?feat:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20Open=20API=20=ED=98=B8=EC=B6=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusDirectionNotFoundException.java | 15 +++++ .../koin/domain/bus/model/BusDirection.java | 28 ++++++++ .../koin/domain/bus/model/BusStation.java | 10 +-- .../koin/domain/bus/model/BusStationNode.java | 27 ++++++++ .../domain/bus/util/BusOpenApiRequestor.java | 65 +++++++++++++++++++ 5 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusDirectionNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusDirectionNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusDirectionNotFoundException.java new file mode 100644 index 000000000..9c6f0f63c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusDirectionNotFoundException.java @@ -0,0 +1,15 @@ +package in.koreatech.koin.domain.bus.exception; + +public class BusDirectionNotFoundException extends IllegalArgumentException { + + private static final String DEFAULT_MESSAGE = "정상적인 버스 방향이 아닙니다."; + + public BusDirectionNotFoundException(String message) { + super(message); + } + + public static BusDirectionNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusDirectionNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java new file mode 100644 index 000000000..00ba4f1b6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java @@ -0,0 +1,28 @@ +package in.koreatech.koin.domain.bus.model; + +import org.apache.commons.lang3.StringUtils; + +import in.koreatech.koin.domain.bus.exception.BusDirectionNotFoundException; + +public enum BusDirection { + /** + * 상행: 종합터미널 -> 병천 (한기대행) + */ + NORTH, + + /** + * 하행: 병천 -> 종합터미널 (천안역, 터미널행) + */ + SOUTH, + ; + + public static BusDirection from(String direction) { + if (StringUtils.equals(direction, "to")) { + return NORTH; + } + if (StringUtils.equals(direction, "from")) { + return SOUTH; + } + throw BusDirectionNotFoundException.withDetail("direction: " + direction); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 25bc2bc24..17ec19197 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -7,17 +7,19 @@ @Getter public enum BusStation { - KOREATECH("koreatech", List.of("학교", "한기대")), - STATION("station", List.of("천안역", "천안역(학화호두과자)")), - TERMINAL("terminal", List.of("터미널", "터미널(신세계 앞 횡단보도)")), + KOREATECH("koreatech", List.of("학교", "한기대"), BusStationNode.KOREATECH), + STATION("station", List.of("천안역", "천안역(학화호두과자)"), BusStationNode.STATION), + TERMINAL("terminal", List.of("터미널", "터미널(신세계 앞 횡단보도)"), BusStationNode.TERMINAL), ; private final String name; private final List displayNames; + private final BusStationNode node; - BusStation(String name, List displayNames) { + BusStation(String name, List displayNames, BusStationNode node) { this.name = name; this.displayNames = displayNames; + this.node = node; } public static BusStation from(String busStation) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java new file mode 100644 index 000000000..9114b729a --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java @@ -0,0 +1,27 @@ +package in.koreatech.koin.domain.bus.model; + +import static in.koreatech.koin.domain.bus.model.BusDirection.NORTH; +import static in.koreatech.koin.domain.bus.model.BusDirection.SOUTH; + +import java.util.Map; + +/** + * OpenApi 상세: 국토교통부_전국 버스정류장 위치정보 (버스 정류장 노드 ID) + * https://www.data.go.kr/data/15067528/fileData.do + */ +public enum BusStationNode { + TERMINAL(Map.of(NORTH, "CAB285000686", SOUTH, "CAB285000685")), // 종합터미널 + KOREATECH(Map.of(NORTH, "CAB285000406", SOUTH, "CAB285000405")), // 코리아텍 + STATION(Map.of(NORTH, "CAB285000655", SOUTH, "CAB285000656")), // 천안역 동부광장 + ; + + private final Map node; + + BusStationNode(Map node) { + this.node = node; + } + + public String getId(BusDirection direction) { + return node.get(direction); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java new file mode 100644 index 000000000..4ada34cba --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java @@ -0,0 +1,65 @@ +package in.koreatech.koin.domain.bus.util; + +import static java.net.URLEncoder.encode; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 + * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098530 + */ +@Component +public class BusOpenApiRequestor { + + private static final String CHEONAN_CITY_CODE = "34010"; + private static final String ENCODE_TYPE = "UTF-8"; + + @Value("${OPEN_API_KEY}") + private String OPEN_API_KEY; + + public String getCityBusArrivalInfo(String nodeId) { + try { + URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId).toString()); + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Content-type", "application/json"); + + BufferedReader input; + if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) { + input = new BufferedReader(new InputStreamReader(conn.getInputStream())); + } else { + input = new BufferedReader(new InputStreamReader(conn.getErrorStream())); + } + + StringBuilder response = new StringBuilder(); + String line; + while ((line = input.readLine()) != null) { + response.append(line); + } + input.close(); + conn.disconnect(); + return response.toString(); + } catch (IOException e) { + return null; + } + } + + private StringBuilder getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { + String url = "http://apis.data.go.kr/1613000/BusSttnInfoInqireService/getCrdntPrxmtSttnList"; + String contentCount = "30"; + StringBuilder urlBuilder = new StringBuilder(url); + urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(OPEN_API_KEY, ENCODE_TYPE)); + urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); + urlBuilder.append("&" + encode("cityCode",ENCODE_TYPE) + "=" + encode(cityCode, ENCODE_TYPE)); + urlBuilder.append("&" + encode("nodeId",ENCODE_TYPE) + "=" + encode(nodeId, ENCODE_TYPE)); + return urlBuilder; + } +} From d821c64d4c2ebd9ed1185ced41d5094b767856ab Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 26 Feb 2024 18:22:32 +0900 Subject: [PATCH 019/123] =?UTF-8?q?feat:=20=EB=B0=A9=ED=96=A5=20=ED=8C=90?= =?UTF-8?q?=EB=8B=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/bus/model/BusStation.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 17ec19197..c64a0ce09 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -30,4 +30,15 @@ public static BusStation from(String busStation) { } throw BusStationNotFoundException.withDetail("busStation: " + busStation); } + + public static BusDirection getDirection(BusStation depart, BusStation arrival) { + if (depart.ordinal() < arrival.ordinal()) { + return BusDirection.SOUTH; + } + return BusDirection.NORTH; + } + + public String getNodeId(BusDirection direction) { + return node.getId(direction); + } } From a4ad5b591e20e2be05c63d5abb28778e99896b5a Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 26 Feb 2024 18:25:26 +0900 Subject: [PATCH 020/123] =?UTF-8?q?fix:=20=ED=98=B8=EC=B6=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/in/koreatech/koin/domain/bus/model/BusType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java index 1509fb105..abc22b7d4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -1,6 +1,6 @@ package in.koreatech.koin.domain.bus.model; -import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException; import lombok.Getter; @Getter @@ -23,6 +23,6 @@ public static BusType from(String busType) { return values()[i]; } } - throw BusStationNotFoundException.withDetail("busType: " + busType); + throw BusTypeNotFoundException.withDetail("busType: " + busType); } } From cff7afbebb919003c79113250091d3a5095246bc Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 28 Feb 2024 09:37:34 +0900 Subject: [PATCH 021/123] =?UTF-8?q?feat:=20json=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. json 파싱 로직 작성 2. 결과 코드 검증 3. 예외 추가 (500 반환) --- build.gradle | 1 + .../bus/exception/BusOpenApiException.java | 17 ++++ .../domain/bus/model/CityBusArrivalInfo.java | 30 ++++++ .../koin/domain/bus/service/BusService.java | 20 ++-- .../domain/bus/util/BusOpenApiRequestor.java | 95 +++++++++++++++++-- .../exception/ExternalServiceException.java | 8 ++ .../exception/GlobalExceptionHandler.java | 14 ++- 7 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusOpenApiException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java create mode 100644 src/main/java/in/koreatech/koin/global/exception/ExternalServiceException.java diff --git a/build.gradle b/build.gradle index f0f8826af..2126cf795 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'com.mysql:mysql-connector-j' implementation 'org.jsoup:jsoup:1.15.3' + implementation 'com.google.code.gson:gson:2.10.1' // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusOpenApiException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusOpenApiException.java new file mode 100644 index 000000000..61b5694bf --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusOpenApiException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.ExternalServiceException; + +public class BusOpenApiException extends ExternalServiceException { + + private static final String DEFAULT_MESSAGE = "버스 Open API 응답이 정상적이지 않습니다."; + + public BusOpenApiException(String message) { + super(message); + } + + public static BusOpenApiException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusOpenApiException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java new file mode 100644 index 000000000..04136da53 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -0,0 +1,30 @@ +package in.koreatech.koin.domain.bus.model; + +import lombok.Getter; + +@Getter +public class CityBusArrivalInfo/* implements Comparable */{ + /** + * "arrprevstationcnt": 5, + * "arrtime": 222, + * "nodeid": "CAB285000405", + * "nodenm": "코리아텍", + * "routeid": "CAB285000147", + * "routeno": 402, + * "routetp": "일반버스", + * "vehicletp": "일반차량" + */ + private int arrprevstationcnt; // 남은 정거장 개수 + private int arrtime; // 도착까지 남은 시간 + private String nodeid; + private String nodenm; + private String routeid; + private int routeno; // 버스 번호 + private String routetp; + private String vehicletp; +/* + @Override + public int compareTo(CityBusArrivalInfo o) { + return this.arrtime - o.arrtime; + }*/ +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 725ba58e1..a8b69280e 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -7,10 +7,12 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusDirection; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; +import in.koreatech.koin.domain.bus.util.BusOpenApiRequestor; import lombok.RequiredArgsConstructor; @Service @@ -19,18 +21,24 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - - /** - * TODO - * 1. city (시내버스) 구현 - * 2. express (시외버스) 구현 - */ + private final BusOpenApiRequestor busOpenApiRequestor; public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { + BusStation departStation = BusStation.from(departStr); BusStation arrivalStation = BusStation.from(arrivalStr); + BusDirection direction = BusStation.getDirection(departStation, arrivalStation); BusType busType = BusType.from(busTypeStr); + + // ===================================== + + + // String result1 = busOpenApiRequestor.getCityBusArrivalInfo(departStation.getNodeId(direction)); + + + // ===================================== + List remainTimes = busRepository.getByBusType(busType).stream() .map(BusCourse::getRoutes) .flatMap(routes -> diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java index 4ada34cba..06cfdb095 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java @@ -6,17 +6,32 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +import in.koreatech.koin.domain.bus.exception.BusOpenApiException; +import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; +import lombok.RequiredArgsConstructor; + /** * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098530 */ @Component +@RequiredArgsConstructor public class BusOpenApiRequestor { private static final String CHEONAN_CITY_CODE = "34010"; @@ -25,9 +40,23 @@ public class BusOpenApiRequestor { @Value("${OPEN_API_KEY}") private String OPEN_API_KEY; - public String getCityBusArrivalInfo(String nodeId) { + private final Gson gson; + + private static final Type arrivalInfoType = new TypeToken>() { + }.getType(); + + public List getCityBusArrivalInfoByOpenApi(String nodeId) { + List arrivalInfos = extractBusArrivalInfo(getCityBusArrivalInfo(nodeId)); + + // TODO + // 특정 버스번호 필터링 + + return null; + } + + private String getCityBusArrivalInfo(String nodeId) { try { - URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId).toString()); + URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-type", "application/json"); @@ -52,14 +81,68 @@ public String getCityBusArrivalInfo(String nodeId) { } } - private StringBuilder getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { + private String getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { String url = "http://apis.data.go.kr/1613000/BusSttnInfoInqireService/getCrdntPrxmtSttnList"; String contentCount = "30"; StringBuilder urlBuilder = new StringBuilder(url); urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(OPEN_API_KEY, ENCODE_TYPE)); urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); - urlBuilder.append("&" + encode("cityCode",ENCODE_TYPE) + "=" + encode(cityCode, ENCODE_TYPE)); - urlBuilder.append("&" + encode("nodeId",ENCODE_TYPE) + "=" + encode(nodeId, ENCODE_TYPE)); - return urlBuilder; + urlBuilder.append("&" + encode("cityCode", ENCODE_TYPE) + "=" + encode(cityCode, ENCODE_TYPE)); + urlBuilder.append("&" + encode("nodeId", ENCODE_TYPE) + "=" + encode(nodeId, ENCODE_TYPE)); + return urlBuilder.toString(); + } + + // 캐싱 전까지 작업하고 푸시 (파싱 끝내고) + private List extractBusArrivalInfo(String jsonResponse) { + List result = new ArrayList<>(); + + try { + JsonObject response = JsonParser.parseString(jsonResponse) + .getAsJsonObject() + .get("response") + .getAsJsonObject(); + validateResponse(response); + JsonObject body = response.get("body").getAsJsonObject(); + + if (body.get("totalCount").getAsLong() == 0) { + return result; + } + + JsonElement item = body.get("items").getAsJsonObject().get("item"); + if (item.isJsonArray()) { + return gson.fromJson(item, arrivalInfoType); + } + if (item.isJsonObject()) { + result.add(gson.fromJson(item, CityBusArrivalInfo.class)); + } + return result; + } catch (JsonSyntaxException e) { + return result; + } + } + + private void validateResponse(JsonObject response) { + String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); + + String errorMessage = ""; + if ("12".equals(resultCode)) { + errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; + } + if ("20".equals(resultCode)) { + errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; + } + if ("22".equals(resultCode)) { + errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; + } + if ("30".equals(resultCode)) { + errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; + } + if ("31".equals(resultCode)) { + errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; + } + if (!"00".equals(resultCode)) { + String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); + throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); + } } } diff --git a/src/main/java/in/koreatech/koin/global/exception/ExternalServiceException.java b/src/main/java/in/koreatech/koin/global/exception/ExternalServiceException.java new file mode 100644 index 000000000..6513383e0 --- /dev/null +++ b/src/main/java/in/koreatech/koin/global/exception/ExternalServiceException.java @@ -0,0 +1,8 @@ +package in.koreatech.koin.global.exception; + +public class ExternalServiceException extends RuntimeException { + + public ExternalServiceException(String message) { + super(message); + } +} diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index 245bf975f..54c7bec88 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -1,14 +1,15 @@ package in.koreatech.koin.global.exception; -import in.koreatech.koin.domain.version.exception.VersionException; -import in.koreatech.koin.global.auth.exception.AuthException; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import in.koreatech.koin.domain.version.exception.VersionException; +import in.koreatech.koin.global.auth.exception.AuthException; +import lombok.extern.slf4j.Slf4j; + @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @@ -44,4 +45,11 @@ public ResponseEntity handleDataNotFoundException(DataNotFoundExc return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ErrorResponse.from("데이터를 찾을 수 없습니다.")); } + + @ExceptionHandler + public ResponseEntity handleExternalServiceException(ExternalServiceException e) { + log.warn(e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ErrorResponse.from("외부 서비스에 접속할 수 없습니다.")); + } } From 390de3e3a1e9756b307c3fe79af49994393cd31e Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 14 Feb 2024 20:41:12 +0900 Subject: [PATCH 022/123] =?UTF-8?q?feat:=20API=20=EA=B3=A8=EA=B2=A9=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/controller/BusApi.java | 31 +++++++++++++++++++ .../domain/bus/controller/BusController.java | 26 ++++++++++++++++ .../domain/bus/dto/BusRemainTimeResponse.java | 4 +++ .../koreatech/koin/domain/bus/model/Bus.java | 4 +++ .../domain/bus/repository/BusRepository.java | 8 +++++ .../koin/domain/bus/service/BusService.java | 18 +++++++++++ 6 files changed, 91 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Bus.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/service/BusService.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java new file mode 100644 index 000000000..7fc11f901 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java @@ -0,0 +1,31 @@ +package in.koreatech.koin.domain.bus.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "(Normal) Bus: 버스", description = "버스 정보를 조회한다.") +public interface BusApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "이번 / 다음 버스 남은 시간 조회") + @GetMapping("/bus") + ResponseEntity getBusRemainTime( + @RequestParam(value = "bus_type") String busType, + @RequestParam String depart, + @RequestParam String arrival + ); +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java new file mode 100644 index 000000000..ec0fa2073 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.service.BusService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class BusController implements BusApi { + + private final BusService busService; + + @Override + public ResponseEntity getBusRemainTime( + @RequestParam(value = "bus_type") String busType, + @RequestParam String depart, + @RequestParam String arrival + ) { + BusRemainTimeResponse busRemainTime = busService.getBusRemainTime(busType, depart, arrival); + return ResponseEntity.ok().body(busRemainTime); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java new file mode 100644 index 000000000..f1e37d562 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -0,0 +1,4 @@ +package in.koreatech.koin.domain.bus.dto; + +public record BusRemainTimeResponse() { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java new file mode 100644 index 000000000..b7435aa9a --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -0,0 +1,4 @@ +package in.koreatech.koin.domain.bus.model; + +public class Bus { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java new file mode 100644 index 000000000..3711cf17f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -0,0 +1,8 @@ +package in.koreatech.koin.domain.bus.repository; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.bus.model.Bus; + +public interface BusRepository extends Repository { +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java new file mode 100644 index 000000000..6f9be35e8 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -0,0 +1,18 @@ +package in.koreatech.koin.domain.bus.service; + +import org.springframework.stereotype.Service; + +import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.repository.BusRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BusService { + + private final BusRepository busRepository; + + public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { + return null; + } +} From f6422844822e5426cde228c0a67792993eb86f66 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:17:04 +0900 Subject: [PATCH 023/123] =?UTF-8?q?feat:=20mongoDB=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../koreatech/koin/domain/bus/model/Bus.java | 24 +++++++++++++++++++ .../domain/bus/repository/BusRepository.java | 8 +++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 062ad5076..f0f8826af 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.mysql:mysql-connector-j' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'com.mysql:mysql-connector-j' implementation 'org.jsoup:jsoup:1.15.3' diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java index b7435aa9a..fb36440ad 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -1,4 +1,28 @@ package in.koreatech.koin.domain.bus.model; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import jakarta.persistence.Id; +import lombok.Getter; + +@Getter +@Document(collection = "bus_timetables") public class Bus { + + @Id + @Field("_id") + private String id; + + @Field("bus_type") + private String busType; + + @Field("region") + private String region; + + @Field("direction") + private String direction; + + @Field("routes") + private String routes; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 3711cf17f..33e413cf1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,8 +1,12 @@ package in.koreatech.koin.domain.bus.repository; -import org.springframework.data.repository.Repository; +import java.util.List; + +import org.springframework.data.mongodb.repository.MongoRepository; import in.koreatech.koin.domain.bus.model.Bus; -public interface BusRepository extends Repository { +public interface BusRepository extends MongoRepository { + + List findAll(); } From 9300bab1143955d56b0d0395b3ade68822e1b4da Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:17:42 +0900 Subject: [PATCH 024/123] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index f1e37d562..b58dfd034 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -1,4 +1,29 @@ package in.koreatech.koin.domain.bus.dto; -public record BusRemainTimeResponse() { +import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; + +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.bus.model.Bus; + +@JsonNaming(SnakeCaseStrategy.class) +public record BusRemainTimeResponse( + String busType, + InnerBusResponse nextBus, + InnerBusResponse now_bus +) { + public static BusRemainTimeResponse from(Bus bus) { + return null; + } + + @JsonNaming(SnakeCaseStrategy.class) + private record InnerBusResponse( + Long busNumber, + Long remainTime + ) { + + public static InnerBusResponse from(Bus bus) { + return null; + } + } } From b36daea44f5ca4fecb0cdc5c68ecbfe048be4075 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 19:49:57 +0900 Subject: [PATCH 025/123] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/exception/BusNotFoundException.java | 17 +++++++++++++++++ .../domain/bus/repository/BusRepository.java | 9 +++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java new file mode 100644 index 000000000..f3ef6ee4c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스가 존재하지 않습니다."; + + public BusNotFoundException(String message) { + super(message); + } + + public static BusNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 33e413cf1..0a2a56e56 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,12 +1,21 @@ package in.koreatech.koin.domain.bus.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.mongodb.repository.MongoRepository; +import in.koreatech.koin.domain.bus.exception.BusNotFoundException; import in.koreatech.koin.domain.bus.model.Bus; public interface BusRepository extends MongoRepository { List findAll(); + + Optional findByBusTypeAndDirection(String busType, String direction); + + default Bus getBusByBusTypeAndDirection(String busType, String direction) { + return findByBusTypeAndDirection(busType, direction) + .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); + } } From 2676ff48ab2d39d6e67649dab22a127b940da344 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 15 Feb 2024 23:45:59 +0900 Subject: [PATCH 026/123] =?UTF-8?q?feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/controller/BusController.java | 3 +- .../BusStationNotFoundException.java | 17 ++++++++++ .../koreatech/koin/domain/bus/model/Bus.java | 4 ++- .../koin/domain/bus/model/BusStation.java | 34 +++++++++++++++++++ .../koin/domain/bus/model/Route.java | 26 ++++++++++++++ .../domain/bus/repository/BusRepository.java | 2 +- .../koin/domain/bus/service/BusService.java | 7 ++++ 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Route.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java index ec0fa2073..4f4f60d8f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.bus.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -14,7 +15,7 @@ public class BusController implements BusApi { private final BusService busService; - @Override + @GetMapping("/bus") public ResponseEntity getBusRemainTime( @RequestParam(value = "bus_type") String busType, @RequestParam String depart, diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java new file mode 100644 index 000000000..10bab1e3e --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusStationNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusStationNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 정류장이 존재하지 않습니다."; + + public BusStationNotFoundException(String message) { + super(message); + } + + public static BusStationNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusStationNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java index fb36440ad..f68abf61d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.bus.model; +import java.util.List; + import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @@ -24,5 +26,5 @@ public class Bus { private String direction; @Field("routes") - private String routes; + private List routes; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java new file mode 100644 index 000000000..f8f84e287 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -0,0 +1,34 @@ +package in.koreatech.koin.domain.bus.model; + +import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; + +public enum BusStation { + KOREATECH("koreatech"), + STATION("station"), + TERMINAL("terminal"), + ; + + private final String name; + + BusStation(String name) { + this.name = name; + } + + public static String getDirection(String depart, String arrival) { + int departIndex = getBusStationIndex(depart); + int arrivalIndex = getBusStationIndex(arrival); + if (departIndex < arrivalIndex) { + return "from"; // 등교 + } + return "to"; // 하교 + } + + private static int getBusStationIndex(String busStation) { + for (int i = 0; i < values().length; i++) { + if (values()[i].name.equals(busStation)) { + return i; + } + } + throw BusStationNotFoundException.withDetail("busStation: " + busStation); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java new file mode 100644 index 000000000..cf0da00c6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.model; + +import java.util.List; + +import org.springframework.data.mongodb.core.mapping.Field; + +public class Route { + + @Field("route_name") + private String routeName; + + @Field("running_days") + private List runningDays; + + @Field("arrival_info") + private List arrivalInfo; + + public static class ArrivalNode { + + @Field("node_name") + private String nodeName; + + @Field("arrival_time") + private String arrivalTime; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 0a2a56e56..25ef6d11b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -14,7 +14,7 @@ public interface BusRepository extends MongoRepository { Optional findByBusTypeAndDirection(String busType, String direction); - default Bus getBusByBusTypeAndDirection(String busType, String direction) { + default Bus getByBusTypeAndDirection(String busType, String direction) { return findByBusTypeAndDirection(busType, direction) .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 6f9be35e8..2ae5e377d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,8 +1,11 @@ package in.koreatech.koin.domain.bus.service; +import java.util.List; + import org.springframework.stereotype.Service; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -13,6 +16,10 @@ public class BusService { private final BusRepository busRepository; public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { + List all = busRepository.findAll(); + // String direction = BusStation.getDirection(depart, arrival); + // Bus foundBus = busRepository.getByBusTypeAndDirection(busType, direction); + // return BusRemainTimeResponse.from(foundBus); return null; } } From 28b5028a3395c0640c0726382bcaba2f54dcf6a6 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 16 Feb 2024 20:04:35 +0900 Subject: [PATCH 027/123] =?UTF-8?q?feat:=20=EB=AF=B8=EC=9A=B4=ED=96=89?= =?UTF-8?q?=EC=9D=B8=20=EB=B2=84=EC=8A=A4=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 6 +++--- .../bus/model/{Bus.java => BusCourse.java} | 10 ++++++++-- .../koin/domain/bus/model/Route.java | 10 ++++++++++ .../domain/bus/repository/BusRepository.java | 19 +++++++++---------- .../koin/domain/bus/service/BusService.java | 10 ++++++---- 5 files changed, 36 insertions(+), 19 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/{Bus.java => BusCourse.java} (69%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index b58dfd034..3ed542fef 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -12,7 +12,7 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus, InnerBusResponse now_bus ) { - public static BusRemainTimeResponse from(Bus bus) { + public static BusRemainTimeResponse from(BusCourse busCourse) { return null; } @@ -22,7 +22,7 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse from(Bus bus) { + public static InnerBusResponse from(BusCourse busCourse) { return null; } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java similarity index 69% rename from src/main/java/in/koreatech/koin/domain/bus/model/Bus.java rename to src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index f68abf61d..3dbcf5034 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.util.ArrayList; import java.util.List; import org.springframework.data.mongodb.core.mapping.Document; @@ -10,7 +11,7 @@ @Getter @Document(collection = "bus_timetables") -public class Bus { +public class BusCourse { @Id @Field("_id") @@ -26,5 +27,10 @@ public class Bus { private String direction; @Field("routes") - private List routes; + private List routes = new ArrayList<>(); + + public boolean isRunning() { + routes.removeIf(route -> !route.isRunning()); + return !routes.isEmpty(); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index cf0da00c6..3c1f49234 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -4,6 +4,9 @@ import org.springframework.data.mongodb.core.mapping.Field; +import lombok.Getter; + +@Getter public class Route { @Field("route_name") @@ -15,6 +18,13 @@ public class Route { @Field("arrival_info") private List arrivalInfo; + public boolean isRunning() { + if (routeName.equals("미운행")) { + return false; + } + return !arrivalInfo.isEmpty(); + } + public static class ArrivalNode { @Field("node_name") diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 25ef6d11b..2d0731ab4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -1,21 +1,20 @@ package in.koreatech.koin.domain.bus.repository; import java.util.List; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.bus.exception.BusNotFoundException; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; -public interface BusRepository extends MongoRepository { +public interface BusRepository extends Repository { - List findAll(); + List findAll(); - Optional findByBusTypeAndDirection(String busType, String direction); + List findByBusTypeAndDirection(String busType, String direction); - default Bus getByBusTypeAndDirection(String busType, String direction) { - return findByBusTypeAndDirection(busType, direction) - .orElseThrow(() -> BusNotFoundException.withDetail("busType: " + busType + ", direction: " + direction)); + default List getByBusTypeAndDirection(String busType, String direction) { + List busCourses = findByBusTypeAndDirection(busType, direction); + busCourses.removeIf(busCourse -> !busCourse.isRunning()); + return busCourses; } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 2ae5e377d..cfefc04fa 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -5,7 +5,8 @@ import org.springframework.stereotype.Service; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -16,9 +17,10 @@ public class BusService { private final BusRepository busRepository; public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - List all = busRepository.findAll(); - // String direction = BusStation.getDirection(depart, arrival); - // Bus foundBus = busRepository.getByBusTypeAndDirection(busType, direction); + List all = busRepository.findAll(); + String direction = BusStation.getDirection(depart, arrival); + + List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); // return BusRemainTimeResponse.from(foundBus); return null; } From 3fbedcca79e13bc478391a83c2c3a22f8788b01f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 19 Feb 2024 18:31:58 +0900 Subject: [PATCH 028/123] =?UTF-8?q?feat:=20enum=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/BusTypeNotFoundException.java | 17 ++++++++++++ .../koin/domain/bus/model/BusType.java | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusType.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java new file mode 100644 index 000000000..80d5fbf5c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusTypeNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusTypeNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 타입이 존재하지 않습니다."; + + public BusTypeNotFoundException(String message) { + super(message); + } + + public static BusTypeNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusTypeNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java new file mode 100644 index 000000000..af5e3104f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -0,0 +1,26 @@ +package in.koreatech.koin.domain.bus.model; + +import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; + +public enum BusType { + CITY("city"), + EXPRESS("express"), + SHUTTLE("shuttle"), + COMMUTING("commuting"), + ; + + private String name; + + BusType(String name) { + this.name = name; + } + + public static void validate(String busType) { + for (int i = 0; i < values().length; i++) { + if (values()[i].name.equals(busType)) { + return; + } + } + throw BusStationNotFoundException.withDetail("busType: " + busType); + } +} From 00911eee0021215e57de17acea49aa7642d45633 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 19 Feb 2024 20:12:59 +0900 Subject: [PATCH 029/123] =?UTF-8?q?feat:=20=EB=82=A8=EC=9D=80=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/BusRemainTime.java | 40 +++++++++++++++++++ .../koin/domain/bus/service/BusService.java | 2 + 2 files changed, 42 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java new file mode 100644 index 000000000..5d87de9e5 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -0,0 +1,40 @@ +package in.koreatech.koin.domain.bus.model; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import lombok.Builder; + +public class BusRemainTime { + + private final LocalTime busArrivalTime; + + public boolean isBefore() { + return LocalTime.now().isBefore(busArrivalTime); + } + + public Long getRemainSeconds() { + if (isBefore()) { + return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); + } + return null; + // return 86400L - Duration.between(busArrivalTime, LocalTime.now()).toSeconds(); + } + + public static BusRemainTime from(String remainTime) { + return builder() + .busArrivalTime(toLocalTime(remainTime)) + .build(); + } + + private static LocalTime toLocalTime(String remainTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + return LocalTime.parse(remainTime, formatter); + } + + @Builder + private BusRemainTime(LocalTime busArrivalTime) { + this.busArrivalTime = busArrivalTime; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index cfefc04fa..50b49bd1c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -6,6 +6,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str String direction = BusStation.getDirection(depart, arrival); List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); + BusRemainTime busRemainTime = BusRemainTime.from("10:13"); // return BusRemainTimeResponse.from(foundBus); return null; } From b87f0fa496c2a363d08aa4c1e9a68a9e11f634c2 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Tue, 20 Feb 2024 16:57:01 +0900 Subject: [PATCH 030/123] =?UTF-8?q?feat:=20=EA=B0=80=EC=9E=A5=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=20=EB=B2=84=EC=8A=A4=EA=B9=8C=EC=A7=80=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20=EC=8B=9C=EA=B0=84=20=EC=9D=91=EB=8B=B5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/dto/BusRemainTimeCityResponse.java | 29 +++++++++++++++++++ .../domain/bus/dto/BusRemainTimeResponse.java | 26 ++++++++++++----- .../koin/domain/bus/model/BusRemainTime.java | 7 ++++- .../koin/domain/bus/model/Route.java | 5 ++-- .../koin/domain/bus/service/BusService.java | 28 +++++++++++++++--- 5 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java new file mode 100644 index 000000000..92235643c --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java @@ -0,0 +1,29 @@ +package in.koreatech.koin.domain.bus.dto; + +import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; + +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.bus.model.BusCourse; + +@JsonNaming(SnakeCaseStrategy.class) +public record BusRemainTimeCityResponse( + String busType, + InnerBusResponse nowBus, + InnerBusResponse nextBus +) { + public static BusRemainTimeCityResponse from(BusCourse busCourse) { + return null; + } + + @JsonNaming(SnakeCaseStrategy.class) + private record InnerBusResponse( + Long busNumber, + Long remainTime + ) { + + public static InnerBusResponse from(BusCourse busCourse) { + return null; + } + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 3ed542fef..a39565cec 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -2,28 +2,38 @@ import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import java.util.List; + import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( String busType, - InnerBusResponse nextBus, - InnerBusResponse now_bus + InnerBusResponse nowBus, + InnerBusResponse nextBus ) { - public static BusRemainTimeResponse from(BusCourse busCourse) { - return null; + + public static BusRemainTimeResponse of(String busType, List remainTimes) { + return new BusRemainTimeResponse( + busType, + InnerBusResponse.from(remainTimes, 0), + InnerBusResponse.from(remainTimes, 1) + ); } @JsonNaming(SnakeCaseStrategy.class) private record InnerBusResponse( - Long busNumber, Long remainTime ) { - public static InnerBusResponse from(BusCourse busCourse) { - return null; + public static InnerBusResponse from(List remainTimes, int index) { + Long result = null; + if (index < remainTimes.size()) { + result = remainTimes.get(index).getRemainSeconds(); + } + return new InnerBusResponse(result); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 5d87de9e5..579a005b1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -6,7 +6,7 @@ import lombok.Builder; -public class BusRemainTime { +public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -37,4 +37,9 @@ private static LocalTime toLocalTime(String remainTime) { private BusRemainTime(LocalTime busArrivalTime) { this.busArrivalTime = busArrivalTime; } + + @Override + public int compareTo(BusRemainTime o) { + return busArrivalTime.compareTo(o.busArrivalTime); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 3c1f49234..cf5429c4f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -16,15 +16,16 @@ public class Route { private List runningDays; @Field("arrival_info") - private List arrivalInfo; + private List arrivalInfos; public boolean isRunning() { if (routeName.equals("미운행")) { return false; } - return !arrivalInfo.isEmpty(); + return !arrivalInfos.isEmpty(); } + @Getter public static class ArrivalNode { @Field("node_name") diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 50b49bd1c..2e02861d2 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -8,6 +8,7 @@ import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -17,13 +18,32 @@ public class BusService { private final BusRepository busRepository; + /** + * TODO + * 궁금한 점 + * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? + * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) + * - 응답 객체를 보면 시내버스만 버스 번호를 함께 반환하는데, 그럼 Response DTO를 두 개 둬야 하는가? 그럼 controller 메서드에서는 반환하는 객체 타입을 명시할 수 없는데 괜찮은가? + * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) + */ + public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { List all = busRepository.findAll(); String direction = BusStation.getDirection(depart, arrival); - List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); - BusRemainTime busRemainTime = BusRemainTime.from("10:13"); - // return BusRemainTimeResponse.from(foundBus); - return null; + + List remainTimes = foundBusCourses.stream() + .map(BusCourse::getRoutes) + .flatMap(routes -> + routes.stream() + .map(route -> route.getArrivalInfos().get(0)) + .map(Route.ArrivalNode::getArrivalTime) + .map(BusRemainTime::from) + .filter(BusRemainTime::isBefore) + ) + .sorted() + .toList(); + + return BusRemainTimeResponse.of(busType, remainTimes); } } From 84b91629709032d150b364c379b3fd77b1b904f9 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Tue, 20 Feb 2024 17:23:29 +0900 Subject: [PATCH 031/123] =?UTF-8?q?feat:=20=EB=AF=B8=EC=9A=B4=ED=96=89=20?= =?UTF-8?q?=EC=9A=94=EC=9D=BC=20=EC=A0=9C=EC=99=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/model/Route.java | 8 ++++++-- .../in/koreatech/koin/domain/bus/service/BusService.java | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index cf5429c4f..290ed44a9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -1,6 +1,9 @@ package in.koreatech.koin.domain.bus.model; +import java.time.LocalDateTime; +import java.time.format.TextStyle; import java.util.List; +import java.util.Locale; import org.springframework.data.mongodb.core.mapping.Field; @@ -19,10 +22,11 @@ public class Route { private List arrivalInfos; public boolean isRunning() { - if (routeName.equals("미운행")) { + if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - return !arrivalInfos.isEmpty(); + String todayOfWeek = LocalDateTime.now().getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase(); + return runningDays.contains(todayOfWeek); } @Getter diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 2e02861d2..8a9bdfd60 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -19,7 +19,6 @@ public class BusService { private final BusRepository busRepository; /** - * TODO * 궁금한 점 * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) @@ -36,6 +35,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() + .filter(Route::isRunning) .map(route -> route.getArrivalInfos().get(0)) .map(Route.ArrivalNode::getArrivalTime) .map(BusRemainTime::from) From 8be80d4ed436cc5d91b16b1d73a2e156c10e4cd9 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 15:39:48 +0900 Subject: [PATCH 032/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EA=B1=B0=EC=9E=A5=20=EA=B2=80=EC=A6=9D=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=8B=9C=EA=B0=84=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/dto/BusRemainTimeCityResponse.java | 29 ------------------- .../domain/bus/dto/BusRemainTimeResponse.java | 9 +++--- .../koin/domain/bus/model/BusRemainTime.java | 3 +- .../domain/bus/repository/BusRepository.java | 8 ++--- .../koin/domain/bus/service/BusService.java | 15 +++++----- 5 files changed, 17 insertions(+), 47 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java deleted file mode 100644 index 92235643c..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeCityResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package in.koreatech.koin.domain.bus.dto; - -import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; - -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import in.koreatech.koin.domain.bus.model.BusCourse; - -@JsonNaming(SnakeCaseStrategy.class) -public record BusRemainTimeCityResponse( - String busType, - InnerBusResponse nowBus, - InnerBusResponse nextBus -) { - public static BusRemainTimeCityResponse from(BusCourse busCourse) { - return null; - } - - @JsonNaming(SnakeCaseStrategy.class) - private record InnerBusResponse( - Long busNumber, - Long remainTime - ) { - - public static InnerBusResponse from(BusCourse busCourse) { - return null; - } - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index a39565cec..aec490576 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -18,22 +18,23 @@ public record BusRemainTimeResponse( public static BusRemainTimeResponse of(String busType, List remainTimes) { return new BusRemainTimeResponse( busType, - InnerBusResponse.from(remainTimes, 0), - InnerBusResponse.from(remainTimes, 1) + InnerBusResponse.of(remainTimes, 0), + InnerBusResponse.of(remainTimes, 1) ); } @JsonNaming(SnakeCaseStrategy.class) private record InnerBusResponse( + Long busNumber, Long remainTime ) { - public static InnerBusResponse from(List remainTimes, int index) { + public static InnerBusResponse of(List remainTimes, int index) { Long result = null; if (index < remainTimes.size()) { result = remainTimes.get(index).getRemainSeconds(); } - return new InnerBusResponse(result); + return new InnerBusResponse(null, result); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 579a005b1..fd281513c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -5,7 +5,9 @@ import java.time.format.DateTimeFormatter; import lombok.Builder; +import lombok.EqualsAndHashCode; +@EqualsAndHashCode public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -19,7 +21,6 @@ public Long getRemainSeconds() { return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); } return null; - // return 86400L - Duration.between(busArrivalTime, LocalTime.now()).toSeconds(); } public static BusRemainTime from(String remainTime) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index 2d0731ab4..a4423ec9b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -8,12 +8,10 @@ public interface BusRepository extends Repository { - List findAll(); + List findByBusType(String busType); - List findByBusTypeAndDirection(String busType, String direction); - - default List getByBusTypeAndDirection(String busType, String direction) { - List busCourses = findByBusTypeAndDirection(busType, direction); + default List getByBusType(String busType) { + List busCourses = findByBusType(busType); busCourses.removeIf(busCourse -> !busCourse.isRunning()); return busCourses; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 8a9bdfd60..0c18098a9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -7,7 +7,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -21,17 +21,15 @@ public class BusService { /** * 궁금한 점 * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? - * - now_bus와 next_bus 응답은 오늘 남은 버스가 없으면 null이 응답되는 것인지?(클라 출력: 운행 정보 없음) (BusRemainTime 주석처리된 부분) - * - 응답 객체를 보면 시내버스만 버스 번호를 함께 반환하는데, 그럼 Response DTO를 두 개 둬야 하는가? 그럼 controller 메서드에서는 반환하는 객체 타입을 명시할 수 없는데 괜찮은가? + * -> O * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) + * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - List all = busRepository.findAll(); - String direction = BusStation.getDirection(depart, arrival); - List foundBusCourses = busRepository.getByBusTypeAndDirection(busType, direction); - - List remainTimes = foundBusCourses.stream() + BusType.validate(busType); + List remainTimes = busRepository.getByBusType(busType) + .stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() @@ -41,6 +39,7 @@ public BusRemainTimeResponse getBusRemainTime(String busType, String depart, Str .map(BusRemainTime::from) .filter(BusRemainTime::isBefore) ) + .distinct() .sorted() .toList(); From fcc7fbf08d94d101cf11af5d0578e7758c25af70 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 16:30:48 +0900 Subject: [PATCH 033/123] =?UTF-8?q?refactor:=20enum=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../koin/domain/bus/dto/BusRemainTimeResponse.java | 5 +++-- .../koreatech/koin/domain/bus/model/BusStation.java | 13 ++----------- .../in/koreatech/koin/domain/bus/model/BusType.java | 6 ++++-- .../koin/domain/bus/repository/BusRepository.java | 5 +++-- .../koin/domain/bus/service/BusService.java | 7 +++++-- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index aec490576..fe94b60d7 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusType; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -15,9 +16,9 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(String busType, List remainTimes) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes) { return new BusRemainTimeResponse( - busType, + busType.getName(), InnerBusResponse.of(remainTimes, 0), InnerBusResponse.of(remainTimes, 1) ); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index f8f84e287..3571bdb22 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -14,19 +14,10 @@ public enum BusStation { this.name = name; } - public static String getDirection(String depart, String arrival) { - int departIndex = getBusStationIndex(depart); - int arrivalIndex = getBusStationIndex(arrival); - if (departIndex < arrivalIndex) { - return "from"; // 등교 - } - return "to"; // 하교 - } - - private static int getBusStationIndex(String busStation) { + public static BusStation from(String busStation) { for (int i = 0; i < values().length; i++) { if (values()[i].name.equals(busStation)) { - return i; + return values()[i]; } } throw BusStationNotFoundException.withDetail("busStation: " + busStation); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java index af5e3104f..1509fb105 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -1,7 +1,9 @@ package in.koreatech.koin.domain.bus.model; import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import lombok.Getter; +@Getter public enum BusType { CITY("city"), EXPRESS("express"), @@ -15,10 +17,10 @@ public enum BusType { this.name = name; } - public static void validate(String busType) { + public static BusType from(String busType) { for (int i = 0; i < values().length; i++) { if (values()[i].name.equals(busType)) { - return; + return values()[i]; } } throw BusStationNotFoundException.withDetail("busType: " + busType); diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index a4423ec9b..ddd2cb84b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -5,13 +5,14 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusType; public interface BusRepository extends Repository { List findByBusType(String busType); - default List getByBusType(String busType) { - List busCourses = findByBusType(busType); + default List getByBusType(BusType busType) { + List busCourses = findByBusType(busType.getName()); busCourses.removeIf(busCourse -> !busCourse.isRunning()); return busCourses; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 0c18098a9..76655ad06 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -7,6 +7,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; @@ -26,8 +27,10 @@ public class BusService { * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ - public BusRemainTimeResponse getBusRemainTime(String busType, String depart, String arrival) { - BusType.validate(busType); + public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { + BusStation departStation = BusStation.from(departStr); + BusStation arrivalStation = BusStation.from(arrivalStr); + BusType busType = BusType.from(busTypeStr); List remainTimes = busRepository.getByBusType(busType) .stream() .map(BusCourse::getRoutes) From 9a1651869e01573324b6015459d9c2dd42f980aa Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 18:37:36 +0900 Subject: [PATCH 034/123] =?UTF-8?q?refactor:=20=EB=93=B1=ED=95=98=EA=B5=90?= =?UTF-8?q?=20=EB=B0=A9=ED=96=A5=20=EA=B8=B0=EC=A4=80=20=ED=83=90=EC=83=89?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A0=95=EB=A5=98=EC=9E=A5=20=ED=83=90?= =?UTF-8?q?=EC=83=89=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusArrivalNodeNotFoundException.java | 17 ++++++++++ .../koin/domain/bus/model/BusStation.java | 14 +++++--- .../koin/domain/bus/model/Route.java | 32 ++++++++++++++++++- .../domain/bus/repository/BusRepository.java | 4 +-- .../koin/domain/bus/service/BusService.java | 12 +++---- 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java new file mode 100644 index 000000000..a8700eaf0 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusArrivalNodeNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusArrivalNodeNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 경로 상에 해당 노드가 존재하지 않습니다."; + + public BusArrivalNodeNotFoundException(String message) { + super(message); + } + + public static BusArrivalNodeNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusArrivalNodeNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 3571bdb22..25bc2bc24 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -1,17 +1,23 @@ package in.koreatech.koin.domain.bus.model; +import java.util.List; + import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import lombok.Getter; +@Getter public enum BusStation { - KOREATECH("koreatech"), - STATION("station"), - TERMINAL("terminal"), + KOREATECH("koreatech", List.of("학교", "한기대")), + STATION("station", List.of("천안역", "천안역(학화호두과자)")), + TERMINAL("terminal", List.of("터미널", "터미널(신세계 앞 횡단보도)")), ; private final String name; + private final List displayNames; - BusStation(String name) { + BusStation(String name, List displayNames) { this.name = name; + this.displayNames = displayNames; } public static BusStation from(String busStation) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 290ed44a9..60ee2db27 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -7,6 +7,7 @@ import org.springframework.data.mongodb.core.mapping.Field; +import in.koreatech.koin.domain.bus.exception.BusArrivalNodeNotFoundException; import lombok.Getter; @Getter @@ -25,10 +26,39 @@ public boolean isRunning() { if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - String todayOfWeek = LocalDateTime.now().getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase(); + String todayOfWeek = LocalDateTime.now() + .getDayOfWeek() + .getDisplayName(TextStyle.SHORT, Locale.US) + .toUpperCase(); return runningDays.contains(todayOfWeek); } + public boolean isCorrectRoute(BusStation depart, BusStation arrival) { + boolean foundDepart = false; + for (ArrivalNode node : arrivalInfos) { + if (depart.getDisplayNames().contains(node.getNodeName()) + && (BusRemainTime.from(node.getArrivalTime()).isBefore())) { + foundDepart = true; + } + if (arrival.getDisplayNames().contains(node.getNodeName()) && foundDepart) { + return true; + } + } + return false; + } + + public BusRemainTime getRemainTime(BusStation busStation) { + ArrivalNode convertedNode = convertToArrivalNode(busStation); + return BusRemainTime.from(convertedNode.arrivalTime); + } + + private ArrivalNode convertToArrivalNode(BusStation busStation) { + return arrivalInfos.stream() + .filter(node -> busStation.getDisplayNames().contains(node.getNodeName())) + .findFirst() + .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail("routeName: " + routeName + ", busStation: " + busStation.getName())); + } + @Getter public static class ArrivalNode { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index ddd2cb84b..b0abee48d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -12,8 +12,6 @@ public interface BusRepository extends Repository { List findByBusType(String busType); default List getByBusType(BusType busType) { - List busCourses = findByBusType(busType.getName()); - busCourses.removeIf(busCourse -> !busCourse.isRunning()); - return busCourses; + return findByBusType(busType.getName()); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 76655ad06..e3572cf58 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -21,8 +21,6 @@ public class BusService { /** * 궁금한 점 - * - 천안역-> 한기대의 경우: 천안 셔틀 하교(시간표: 한기대->터미널->천안역->...->한기대)에서 천안역->한기대 부분도 포함해야 하나? 시간표명은 하교인데 등교를 위해 사용할 수 있는가?? - * -> O * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) * -> X. 테스트 코드는 LocalDateTime을 mocking한다. */ @@ -31,16 +29,14 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt BusStation departStation = BusStation.from(departStr); BusStation arrivalStation = BusStation.from(arrivalStr); BusType busType = BusType.from(busTypeStr); - List remainTimes = busRepository.getByBusType(busType) - .stream() + + List remainTimes = busRepository.getByBusType(busType).stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() .filter(Route::isRunning) - .map(route -> route.getArrivalInfos().get(0)) - .map(Route.ArrivalNode::getArrivalTime) - .map(BusRemainTime::from) - .filter(BusRemainTime::isBefore) + .filter(route -> route.isCorrectRoute(departStation, arrivalStation)) + .map(route -> route.getRemainTime(departStation)) ) .distinct() .sorted() From b76afe5cd507c6d0c4bbbc57ef73de711a735c52 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 18:51:09 +0900 Subject: [PATCH 035/123] =?UTF-8?q?remove:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BusCourse.isRunning() --- .../java/in/koreatech/koin/domain/bus/model/BusCourse.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index 3dbcf5034..b0430edea 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -28,9 +28,4 @@ public class BusCourse { @Field("routes") private List routes = new ArrayList<>(); - - public boolean isRunning() { - routes.removeIf(route -> !route.isRunning()); - return !routes.isEmpty(); - } } From d5f3daac1d77f3bfa6c5d7f37f037a9880efa73e Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 21 Feb 2024 19:00:14 +0900 Subject: [PATCH 036/123] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20mocking=20=EC=9C=84=ED=95=B4=20Clock=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 11 ++++++----- .../koin/domain/bus/model/BusRemainTime.java | 11 ++++++----- .../in/koreatech/koin/domain/bus/model/Route.java | 9 +++++---- .../koin/domain/bus/service/BusService.java | 9 +++++---- .../koreatech/koin/global/config/ClockConfig.java | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/global/config/ClockConfig.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index fe94b60d7..3128ac186 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -2,6 +2,7 @@ import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import java.time.Clock; import java.util.List; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -16,11 +17,11 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(BusType busType, List remainTimes) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.getName(), - InnerBusResponse.of(remainTimes, 0), - InnerBusResponse.of(remainTimes, 1) + InnerBusResponse.of(remainTimes, 0, clock), + InnerBusResponse.of(remainTimes, 1, clock) ); } @@ -30,10 +31,10 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse of(List remainTimes, int index) { + public static InnerBusResponse of(List remainTimes, int index, Clock clock) { Long result = null; if (index < remainTimes.size()) { - result = remainTimes.get(index).getRemainSeconds(); + result = remainTimes.get(index).getRemainSeconds(clock); } return new InnerBusResponse(null, result); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index fd281513c..427a5d958 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.time.Clock; import java.time.Duration; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -12,13 +13,13 @@ public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; - public boolean isBefore() { - return LocalTime.now().isBefore(busArrivalTime); + public boolean isBefore(Clock clock) { + return LocalTime.now(clock).isBefore(busArrivalTime); } - public Long getRemainSeconds() { - if (isBefore()) { - return Duration.between(LocalTime.now(), busArrivalTime).toSeconds(); + public Long getRemainSeconds(Clock clock) { + if (isBefore(clock)) { + return Duration.between(LocalTime.now(clock), busArrivalTime).toSeconds(); } return null; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 60ee2db27..a67abbcca 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.time.Clock; import java.time.LocalDateTime; import java.time.format.TextStyle; import java.util.List; @@ -22,22 +23,22 @@ public class Route { @Field("arrival_info") private List arrivalInfos; - public boolean isRunning() { + public boolean isRunning(Clock clock) { if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { return false; } - String todayOfWeek = LocalDateTime.now() + String todayOfWeek = LocalDateTime.now(clock) .getDayOfWeek() .getDisplayName(TextStyle.SHORT, Locale.US) .toUpperCase(); return runningDays.contains(todayOfWeek); } - public boolean isCorrectRoute(BusStation depart, BusStation arrival) { + public boolean isCorrectRoute(BusStation depart, BusStation arrival, Clock clock) { boolean foundDepart = false; for (ArrivalNode node : arrivalInfos) { if (depart.getDisplayNames().contains(node.getNodeName()) - && (BusRemainTime.from(node.getArrivalTime()).isBefore())) { + && (BusRemainTime.from(node.getArrivalTime()).isBefore(clock))) { foundDepart = true; } if (arrival.getDisplayNames().contains(node.getNodeName()) && foundDepart) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index e3572cf58..afe6741df 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.service; +import java.time.Clock; import java.util.List; import org.springframework.stereotype.Service; @@ -9,7 +10,6 @@ import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; -import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import lombok.RequiredArgsConstructor; @@ -17,6 +17,7 @@ @RequiredArgsConstructor public class BusService { + private final Clock clock; private final BusRepository busRepository; /** @@ -34,14 +35,14 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() - .filter(Route::isRunning) - .filter(route -> route.isCorrectRoute(departStation, arrivalStation)) + .filter(route -> route.isRunning(clock)) + .filter(route -> route.isCorrectRoute(departStation, arrivalStation, clock)) .map(route -> route.getRemainTime(departStation)) ) .distinct() .sorted() .toList(); - return BusRemainTimeResponse.of(busType, remainTimes); + return BusRemainTimeResponse.of(busType, remainTimes, clock); } } diff --git a/src/main/java/in/koreatech/koin/global/config/ClockConfig.java b/src/main/java/in/koreatech/koin/global/config/ClockConfig.java new file mode 100644 index 000000000..ae4211a0e --- /dev/null +++ b/src/main/java/in/koreatech/koin/global/config/ClockConfig.java @@ -0,0 +1,15 @@ +package in.koreatech.koin.global.config; + +import java.time.Clock; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ClockConfig { + + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } +} From 5f6bb596be0e3b36f9e460c768c229f8b59a160d Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 23 Feb 2024 19:08:58 +0900 Subject: [PATCH 037/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20mongoDB=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/AcceptanceTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index 01acc0509..a960fad73 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -2,8 +2,6 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import in.koreatech.koin.support.DBInitializer; -import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,6 +15,9 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.utility.DockerImageName; +import in.koreatech.koin.support.DBInitializer; +import io.restassured.RestAssured; + @SpringBootTest(webEnvironment = RANDOM_PORT) @Import(DBInitializer.class) @ActiveProfiles("test") @@ -37,6 +38,9 @@ public abstract class AcceptanceTest { @Container protected static GenericContainer redisContainer; + @Container + protected static GenericContainer mongoContainer; + @DynamicPropertySource private static void configureProperties(final DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", mySqlContainer::getJdbcUrl); @@ -44,6 +48,9 @@ private static void configureProperties(final DynamicPropertyRegistry registry) registry.add("spring.datasource.password", () -> ROOT_PASSWORD); registry.add("spring.data.redis.host", redisContainer::getHost); registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(6379).toString()); + registry.add("spring.data.mongodb.host", mongoContainer::getHost); + registry.add("spring.data.mongodb.port", () -> mongoContainer.getMappedPort(27017).toString()); + registry.add("spring.data.mongodb.database", () -> "test"); } static { @@ -57,8 +64,13 @@ private static void configureProperties(final DynamicPropertyRegistry registry) DockerImageName.parse("redis:4.0.10")) .withExposedPorts(6379); + mongoContainer = new GenericContainer<>( + DockerImageName.parse("mongo:7.0")) + .withExposedPorts(27017); + mySqlContainer.start(); redisContainer.start(); + mongoContainer.start(); } @BeforeEach From b8bb74f5ad4c61d199ff56a02680e743e68f4b79 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 23 Feb 2024 19:09:24 +0900 Subject: [PATCH 038/123] =?UTF-8?q?test:=20=EC=85=94=ED=8B=80=20=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/BusCourse.java | 12 ++ .../koin/domain/bus/model/Route.java | 17 +++ .../domain/bus/repository/BusRepository.java | 2 + .../koin/domain/bus/service/BusService.java | 6 +- .../koreatech/koin/acceptance/BusApiTest.java | 115 ++++++++++++++++++ 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/test/java/in/koreatech/koin/acceptance/BusApiTest.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index b0430edea..e3ea129bc 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -7,10 +7,14 @@ import org.springframework.data.mongodb.core.mapping.Field; import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Document(collection = "bus_timetables") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class BusCourse { @Id @@ -28,4 +32,12 @@ public class BusCourse { @Field("routes") private List routes = new ArrayList<>(); + + @Builder + public BusCourse(String busType, String region, String direction, List routes) { + this.busType = busType; + this.region = region; + this.direction = direction; + this.routes = routes; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index a67abbcca..362ed7d6f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -9,9 +9,13 @@ import org.springframework.data.mongodb.core.mapping.Field; import in.koreatech.koin.domain.bus.exception.BusArrivalNodeNotFoundException; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Route { @Field("route_name") @@ -60,6 +64,13 @@ private ArrivalNode convertToArrivalNode(BusStation busStation) { .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail("routeName: " + routeName + ", busStation: " + busStation.getName())); } + @Builder + public Route(String routeName, List runningDays, List arrivalInfos) { + this.routeName = routeName; + this.runningDays = runningDays; + this.arrivalInfos = arrivalInfos; + } + @Getter public static class ArrivalNode { @@ -68,5 +79,11 @@ public static class ArrivalNode { @Field("arrival_time") private String arrivalTime; + + @Builder + public ArrivalNode(String nodeName, String arrivalTime) { + this.nodeName = nodeName; + this.arrivalTime = arrivalTime; + } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index b0abee48d..aebcf21d4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -9,6 +9,8 @@ public interface BusRepository extends Repository { + BusCourse save(BusCourse busCourse); + List findByBusType(String busType); default List getByBusType(BusType busType) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index afe6741df..725ba58e1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -21,9 +21,9 @@ public class BusService { private final BusRepository busRepository; /** - * 궁금한 점 - * - 테스트를 위해 이번 API 요청 파라미터에 현재 시각을 기입할 수 있도록 하자는 한수형의 의견이 있었는데, 지금 바로 도입해야 할지(하위호환성) - * -> X. 테스트 코드는 LocalDateTime을 mocking한다. + * TODO + * 1. city (시내버스) 구현 + * 2. express (시외버스) 구현 */ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java new file mode 100644 index 000000000..5ef4b5e9e --- /dev/null +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -0,0 +1,115 @@ +package in.koreatech.koin.acceptance; + +import static java.time.format.DateTimeFormatter.ofPattern; +import static org.mockito.Mockito.when; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.List; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusStation; +import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.Route; +import in.koreatech.koin.domain.bus.repository.BusRepository; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +@ExtendWith(MockitoExtension.class) +class BusApiTest extends AcceptanceTest { + + @MockBean + private Clock clock; + + @Autowired + private BusRepository busRepository; + + @BeforeEach + void mockingClock() { + when(clock.instant()).thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + } + + @Test + @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") + void getNextShuttleBusRemainTime() { + final String arrivalTime = "18:10"; + + BusType busType = BusType.from("shuttle"); + BusStation depart = BusStation.from("koreatech"); + BusStation arrival = BusStation.from("terminal"); + + BusCourse busCourse = BusCourse.builder() + .busType("shuttle") + .region("천안") + .direction("from") + .routes( + List.of( + Route.builder() + .routeName("주중") + .runningDays(List.of("MON", "TUE", "WED", "THU", "FRI")) + .arrivalInfos( + List.of( + Route.ArrivalNode.builder() + .nodeName("한기대") + .arrivalTime(arrivalTime) + .build(), + Route.ArrivalNode.builder() + .nodeName("신계초,운전리,연춘리") + .arrivalTime("정차") + .build(), + Route.ArrivalNode.builder() + .nodeName("천안역(학화호두과자)") + .arrivalTime("18:50") + .build(), + Route.ArrivalNode.builder() + .nodeName("터미널(신세계 앞 횡단보도)") + .arrivalTime("18:55") + .build() + ) + ) + .build() + ) + ) + .build(); + busRepository.save(busCourse); + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("bus_type", busType.getName()) + .param("depart", depart.getName()) + .param("arrival", arrival.getName()) + .get("/bus") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.body().jsonPath().getString("bus_type")).isEqualTo(busType.getName()); + softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull(); + softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( + BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull(); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull(); + } + ); + } +} From ea771da467fe8770fc01daec36b9f1eb4173f8a5 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 28 Feb 2024 10:54:53 +0900 Subject: [PATCH 039/123] =?UTF-8?q?docs:=20API=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=EC=97=90=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/controller/BusApi.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java index 7fc11f901..ab749d584 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java @@ -6,6 +6,7 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -24,8 +25,8 @@ public interface BusApi { @Operation(summary = "이번 / 다음 버스 남은 시간 조회") @GetMapping("/bus") ResponseEntity getBusRemainTime( - @RequestParam(value = "bus_type") String busType, - @RequestParam String depart, - @RequestParam String arrival + @Parameter(description = "버스 종류(city, express, shuttle, commuting)") @RequestParam(value = "bus_type") String busType, + @Parameter(description = "koreatech, station, terminal") @RequestParam String depart, + @Parameter(description = "koreatech, station, terminal") @RequestParam String arrival ); } From a524721d08c184ac0b23659bd2be35195f84fafd Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 28 Feb 2024 11:08:53 +0900 Subject: [PATCH 040/123] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/service/BusService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 725ba58e1..62ddae697 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -20,12 +20,6 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - /** - * TODO - * 1. city (시내버스) 구현 - * 2. express (시외버스) 구현 - */ - public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { BusStation departStation = BusStation.from(departStr); BusStation arrivalStation = BusStation.from(arrivalStr); From 1ce634353d9482125950a6722279f6e4d092a967 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 29 Feb 2024 07:59:47 +0900 Subject: [PATCH 041/123] =?UTF-8?q?refactor:=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20Transactional=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/service/BusService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 62ddae697..c71319158 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; @@ -14,6 +15,7 @@ import lombok.RequiredArgsConstructor; @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class BusService { From 42aac243a051ccb42162ff6b06d6056907b74381 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 29 Feb 2024 08:12:55 +0900 Subject: [PATCH 042/123] =?UTF-8?q?fix:=20mocking=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=EB=B6=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/AcceptanceTest.java | 6 +++++ .../koreatech/koin/acceptance/BusApiTest.java | 22 +++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index a960fad73..c2e2f8d57 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -2,9 +2,12 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import java.time.Clock; + import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; @@ -32,6 +35,9 @@ public abstract class AcceptanceTest { @Autowired private DBInitializer dataInitializer; + @MockBean + protected Clock clock; + @Container protected static MySQLContainer mySqlContainer; diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 5ef4b5e9e..f916e7686 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -8,13 +8,9 @@ import java.util.List; import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import in.koreatech.koin.AcceptanceTest; @@ -28,27 +24,25 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; -@ExtendWith(MockitoExtension.class) class BusApiTest extends AcceptanceTest { - @MockBean - private Clock clock; + // @MockBean + // private Clock clock; @Autowired private BusRepository busRepository; - @BeforeEach - void mockingClock() { - when(clock.instant()).thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); - when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); - } - @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { final String arrivalTime = "18:10"; + // BDDMockito.given(clock.instant()).willReturn(ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); + // BDDMockito.given(clock.getZone()).willReturn(Clock.systemDefaultZone().getZone()); + when(clock.instant()).thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + BusType busType = BusType.from("shuttle"); BusStation depart = BusStation.from("koreatech"); BusStation arrival = BusStation.from("terminal"); From 295fa277dbe02caf3ccba56f63e72fa5d077abf1 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 29 Feb 2024 08:26:02 +0900 Subject: [PATCH 043/123] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/in/koreatech/koin/AcceptanceTest.java | 6 +++--- src/test/java/in/koreatech/koin/acceptance/BusApiTest.java | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index c2e2f8d57..ff6805a19 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -32,12 +32,12 @@ public abstract class AcceptanceTest { @LocalServerPort protected int port; - @Autowired - private DBInitializer dataInitializer; - @MockBean protected Clock clock; + @Autowired + private DBInitializer dataInitializer; + @Container protected static MySQLContainer mySqlContainer; diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index f916e7686..a9915987d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -26,9 +26,6 @@ class BusApiTest extends AcceptanceTest { - // @MockBean - // private Clock clock; - @Autowired private BusRepository busRepository; @@ -37,8 +34,6 @@ class BusApiTest extends AcceptanceTest { void getNextShuttleBusRemainTime() { final String arrivalTime = "18:10"; - // BDDMockito.given(clock.instant()).willReturn(ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); - // BDDMockito.given(clock.getZone()).willReturn(Clock.systemDefaultZone().getZone()); when(clock.instant()).thenReturn( ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); From 9b8c3f484ede68c125e147e575c03960c184fe07 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 16:59:50 +0900 Subject: [PATCH 044/123] =?UTF-8?q?refactor:=20=EB=8F=84=EC=B0=A9=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=20=ED=8C=A8=ED=84=B4=20=EB=B6=88=EC=9D=BC?= =?UTF-8?q?=EC=B9=98=20=EC=8B=9C=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/exception/BusIllegalArrivalTime.java | 15 +++++++++ .../koin/domain/bus/model/BusRemainTime.java | 33 +++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusIllegalArrivalTime.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusIllegalArrivalTime.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusIllegalArrivalTime.java new file mode 100644 index 000000000..08631437e --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusIllegalArrivalTime.java @@ -0,0 +1,15 @@ +package in.koreatech.koin.domain.bus.exception; + +public class BusIllegalArrivalTime extends IllegalArgumentException { + + private static final String DEFAULT_MESSAGE = "버스 도착 시각이 잘못되었습니다."; + + public BusIllegalArrivalTime(String message) { + super(message); + } + + public static BusIllegalArrivalTime withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusIllegalArrivalTime(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 427a5d958..c1f324915 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -4,11 +4,11 @@ import java.time.Duration; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.Objects; +import in.koreatech.koin.domain.bus.exception.BusIllegalArrivalTime; import lombok.Builder; -import lombok.EqualsAndHashCode; -@EqualsAndHashCode public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -24,15 +24,19 @@ public Long getRemainSeconds(Clock clock) { return null; } - public static BusRemainTime from(String remainTime) { + public static BusRemainTime from(String arrivalTime) { return builder() - .busArrivalTime(toLocalTime(remainTime)) + .busArrivalTime(toLocalTime(arrivalTime)) .build(); } - private static LocalTime toLocalTime(String remainTime) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); - return LocalTime.parse(remainTime, formatter); + private static LocalTime toLocalTime(String arrivalTime) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + return LocalTime.parse(arrivalTime, formatter); + } catch (Exception e) { + throw BusIllegalArrivalTime.withDetail("arrivalTime: " + arrivalTime); + } } @Builder @@ -40,6 +44,21 @@ private BusRemainTime(LocalTime busArrivalTime) { this.busArrivalTime = busArrivalTime; } + @Override + public int hashCode() { + return Objects.hash(busArrivalTime); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BusRemainTime that = (BusRemainTime)o; + return Objects.equals(busArrivalTime, that.busArrivalTime); + } + @Override public int compareTo(BusRemainTime o) { return busArrivalTime.compareTo(o.busArrivalTime); From b54cff90355501507793ac8b20410538b07a01e1 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 17:08:15 +0900 Subject: [PATCH 045/123] =?UTF-8?q?refactor:=20=EB=B0=98=EB=B3=B5=EB=AC=B8?= =?UTF-8?q?=EC=9D=84=20stream=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/BusStation.java | 13 ++++++------- .../koreatech/koin/domain/bus/model/BusType.java | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 25bc2bc24..93a99ce6d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import java.util.Arrays; import java.util.List; import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; @@ -20,12 +21,10 @@ public enum BusStation { this.displayNames = displayNames; } - public static BusStation from(String busStation) { - for (int i = 0; i < values().length; i++) { - if (values()[i].name.equals(busStation)) { - return values()[i]; - } - } - throw BusStationNotFoundException.withDetail("busStation: " + busStation); + public static BusStation from(String busStationName) { + return Arrays.stream(values()) + .filter(busStation -> busStation.name.equals(busStationName)) + .findAny() + .orElseThrow(() -> BusStationNotFoundException.withDetail("busStation: " + busStationName)); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java index 1509fb105..4102c53ed 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.bus.model; +import java.util.Arrays; + import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; import lombok.Getter; @@ -17,12 +19,10 @@ public enum BusType { this.name = name; } - public static BusType from(String busType) { - for (int i = 0; i < values().length; i++) { - if (values()[i].name.equals(busType)) { - return values()[i]; - } - } - throw BusStationNotFoundException.withDetail("busType: " + busType); + public static BusType from(String busTypeName) { + return Arrays.stream(values()) + .filter(busType -> busType.name.equals(busTypeName)) + .findAny() + .orElseThrow(() -> BusStationNotFoundException.withDetail("busType: " + busTypeName)); } } From 40f2d8141d7fe0acdd942a71d3f38b7ab8105700 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 17:19:35 +0900 Subject: [PATCH 046/123] =?UTF-8?q?refactor:=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=EB=A1=9C=20List=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=8B=9C=20=EB=AF=B8=EB=A6=AC=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/in/koreatech/koin/domain/bus/model/Route.java | 5 +++-- .../in/koreatech/koin/domain/community/model/Article.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 362ed7d6f..866a5fe91 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -3,6 +3,7 @@ import java.time.Clock; import java.time.LocalDateTime; import java.time.format.TextStyle; +import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -22,10 +23,10 @@ public class Route { private String routeName; @Field("running_days") - private List runningDays; + private List runningDays = new ArrayList<>(); @Field("arrival_info") - private List arrivalInfos; + private List arrivalInfos = new ArrayList<>(); public boolean isRunning(Clock clock) { if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index 8e6d7a673..468456a5e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.community.model; +import java.util.ArrayList; import java.util.List; import org.hibernate.annotations.Where; @@ -84,7 +85,7 @@ public class Article extends BaseEntity { private Boolean isDeleted = false; @OneToMany(mappedBy = "article", fetch = FetchType.LAZY) - private List comment; + private List comment = new ArrayList<>(); @NotNull @Column(name = "comment_count", nullable = false) From 0a2387554daff632cd8ea06b3d05b7b67e7576d3 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 17:29:23 +0900 Subject: [PATCH 047/123] =?UTF-8?q?refactor:=20String=20=EB=B9=84=EA=B5=90?= =?UTF-8?q?=20=EC=8B=9C=20NPE=20=EC=98=88=EB=B0=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/in/koreatech/koin/domain/bus/model/Route.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 866a5fe91..3d61f6651 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -29,7 +29,7 @@ public class Route { private List arrivalInfos = new ArrayList<>(); public boolean isRunning(Clock clock) { - if (routeName.equals("미운행") || arrivalInfos.isEmpty()) { + if ("미운행".equals(routeName) || arrivalInfos.isEmpty()) { return false; } String todayOfWeek = LocalDateTime.now(clock) @@ -62,7 +62,8 @@ private ArrivalNode convertToArrivalNode(BusStation busStation) { return arrivalInfos.stream() .filter(node -> busStation.getDisplayNames().contains(node.getNodeName())) .findFirst() - .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail("routeName: " + routeName + ", busStation: " + busStation.getName())); + .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail( + "routeName: " + routeName + ", busStation: " + busStation.getName())); } @Builder From cab5593b464976ab48b170b4e773571b703ae3bc Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 17:57:26 +0900 Subject: [PATCH 048/123] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A7=A4=ED=95=91=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/bus/repository/BusRepository.java | 5 ----- .../in/koreatech/koin/domain/bus/service/BusService.java | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index aebcf21d4..f247054b9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -5,15 +5,10 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.model.BusCourse; -import in.koreatech.koin.domain.bus.model.BusType; public interface BusRepository extends Repository { BusCourse save(BusCourse busCourse); List findByBusType(String busType); - - default List getByBusType(BusType busType) { - return findByBusType(busType.getName()); - } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index c71319158..73bf3c04d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -27,7 +27,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt BusStation arrivalStation = BusStation.from(arrivalStr); BusType busType = BusType.from(busTypeStr); - List remainTimes = busRepository.getByBusType(busType).stream() + List remainTimes = busRepository.findByBusType(busType.getName()).stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() From 3bf468ede7aedc52f4045e8a493605fb979d958f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 18:00:44 +0900 Subject: [PATCH 049/123] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/service/BusService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 73bf3c04d..f933ed4ae 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -27,7 +27,8 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt BusStation arrivalStation = BusStation.from(arrivalStr); BusType busType = BusType.from(busTypeStr); - List remainTimes = busRepository.findByBusType(busType.getName()).stream() + List busCourses = busRepository.findByBusType(busType.getName()); + List remainTimes = busCourses.stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() From 56d985c511f8cb198b2ec8302ffc7b49d0e24a3a Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 6 Mar 2024 18:30:38 +0900 Subject: [PATCH 050/123] =?UTF-8?q?rename:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/service/BusService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index f933ed4ae..608b777e4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -22,10 +22,10 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departStr, String arrivalStr) { - BusStation departStation = BusStation.from(departStr); - BusStation arrivalStation = BusStation.from(arrivalStr); - BusType busType = BusType.from(busTypeStr); + public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { + BusStation depart = BusStation.from(departName); + BusStation arrival = BusStation.from(arrivalName); + BusType busType = BusType.from(busTypeName); List busCourses = busRepository.findByBusType(busType.getName()); List remainTimes = busCourses.stream() @@ -33,8 +33,8 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeStr, String departSt .flatMap(routes -> routes.stream() .filter(route -> route.isRunning(clock)) - .filter(route -> route.isCorrectRoute(departStation, arrivalStation, clock)) - .map(route -> route.getRemainTime(departStation)) + .filter(route -> route.isCorrectRoute(depart, arrival, clock)) + .map(route -> route.getRemainTime(depart)) ) .distinct() .sorted() From 52b773d59947c333707c34933d800849a5dde097 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 7 Mar 2024 09:19:37 +0900 Subject: [PATCH 051/123] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/bus/model/BusStation.java | 3 --- .../koreatech/koin/domain/bus/service/BusService.java | 11 +++-------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 5901564e7..9aac00e45 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -1,9 +1,6 @@ package in.koreatech.koin.domain.bus.model; -<<<<<<< HEAD -======= import java.util.Arrays; ->>>>>>> feature/99-get-bus-shuttle-and-commuting import java.util.List; import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index d8dc05b87..fabb6a477 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -4,26 +4,20 @@ import java.util.List; import org.springframework.stereotype.Service; - -import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; -import in.koreatech.koin.domain.bus.model.BusCourse; -import in.koreatech.koin.domain.bus.model.BusDirection; import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusDirection; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; -import lombok.RequiredArgsConstructor; - -@Service -@Transactional(readOnly = true) import in.koreatech.koin.domain.bus.util.BusOpenApiRequestor; import lombok.RequiredArgsConstructor; @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class BusService { @@ -31,6 +25,7 @@ public class BusService { private final BusRepository busRepository; private final BusOpenApiRequestor busOpenApiRequestor; + @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { BusStation depart = BusStation.from(departName); BusStation arrival = BusStation.from(arrivalName); From 75cf95ee6aa26e674e97d5d4810690ee8b99866f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 7 Mar 2024 10:03:39 +0900 Subject: [PATCH 052/123] =?UTF-8?q?refactor:=20Builder=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/model/BusCourse.java | 2 +- src/main/java/in/koreatech/koin/domain/bus/model/Route.java | 4 ++-- .../koreatech/koin/domain/community/model/ArticleViewLog.java | 2 +- .../in/koreatech/koin/domain/community/model/Comment.java | 2 +- .../java/in/koreatech/koin/domain/owner/domain/Owner.java | 2 +- .../koreatech/koin/domain/owner/domain/OwnerAttachment.java | 2 +- src/main/java/in/koreatech/koin/domain/shop/model/Shop.java | 2 +- .../java/in/koreatech/koin/domain/user/model/Student.java | 2 +- src/main/java/in/koreatech/koin/domain/user/model/User.java | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java index e3ea129bc..542861d8a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java @@ -34,7 +34,7 @@ public class BusCourse { private List routes = new ArrayList<>(); @Builder - public BusCourse(String busType, String region, String direction, List routes) { + private BusCourse(String busType, String region, String direction, List routes) { this.busType = busType; this.region = region; this.direction = direction; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 3d61f6651..587b376ab 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -67,7 +67,7 @@ private ArrivalNode convertToArrivalNode(BusStation busStation) { } @Builder - public Route(String routeName, List runningDays, List arrivalInfos) { + private Route(String routeName, List runningDays, List arrivalInfos) { this.routeName = routeName; this.runningDays = runningDays; this.arrivalInfos = arrivalInfos; @@ -83,7 +83,7 @@ public static class ArrivalNode { private String arrivalTime; @Builder - public ArrivalNode(String nodeName, String arrivalTime) { + private ArrivalNode(String nodeName, String arrivalTime) { this.nodeName = nodeName; this.arrivalTime = arrivalTime; } diff --git a/src/main/java/in/koreatech/koin/domain/community/model/ArticleViewLog.java b/src/main/java/in/koreatech/koin/domain/community/model/ArticleViewLog.java index a8d0f42b2..9f09d297a 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/ArticleViewLog.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/ArticleViewLog.java @@ -53,7 +53,7 @@ public void updateExpiredTime() { } @Builder - public ArticleViewLog(Article article, User user, String ip) { + private ArticleViewLog(Article article, User user, String ip) { this.article = article; this.user = user; this.ip = ip; diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Comment.java b/src/main/java/in/koreatech/koin/domain/community/model/Comment.java index 18f371517..d53b545b5 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Comment.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Comment.java @@ -69,7 +69,7 @@ public void updateAuthority(Long userId) { } @Builder - public Comment(Article article, String content, Long userId, + private Comment(Article article, String content, Long userId, String nickname, Boolean isDeleted) { this.article = article; this.content = content; diff --git a/src/main/java/in/koreatech/koin/domain/owner/domain/Owner.java b/src/main/java/in/koreatech/koin/domain/owner/domain/Owner.java index 1018a1594..550f972d8 100644 --- a/src/main/java/in/koreatech/koin/domain/owner/domain/Owner.java +++ b/src/main/java/in/koreatech/koin/domain/owner/domain/Owner.java @@ -47,7 +47,7 @@ public class Owner { private Boolean grantEvent; @Builder - public Owner(User user, String companyRegistrationNumber, + private Owner(User user, String companyRegistrationNumber, String companyRegistrationCertificateImageUrl, Boolean grantShop, Boolean grantEvent) { this.user = user; this.companyRegistrationNumber = companyRegistrationNumber; diff --git a/src/main/java/in/koreatech/koin/domain/owner/domain/OwnerAttachment.java b/src/main/java/in/koreatech/koin/domain/owner/domain/OwnerAttachment.java index b4f1f13eb..10a90c4aa 100644 --- a/src/main/java/in/koreatech/koin/domain/owner/domain/OwnerAttachment.java +++ b/src/main/java/in/koreatech/koin/domain/owner/domain/OwnerAttachment.java @@ -67,7 +67,7 @@ public void updateName() { } @Builder - public OwnerAttachment(Owner owner, String url, Boolean isDeleted, String name) { + private OwnerAttachment(Owner owner, String url, Boolean isDeleted, String name) { this.owner = owner; this.url = url; this.isDeleted = isDeleted; diff --git a/src/main/java/in/koreatech/koin/domain/shop/model/Shop.java b/src/main/java/in/koreatech/koin/domain/shop/model/Shop.java index bf885d45b..91560e159 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/model/Shop.java +++ b/src/main/java/in/koreatech/koin/domain/shop/model/Shop.java @@ -96,7 +96,7 @@ public class Shop extends BaseEntity { private Long hit; @Builder - public Shop(Owner owner, String name, String internalName, String chosung, String phone, String address, + private Shop(Owner owner, String name, String internalName, String chosung, String phone, String address, String description, Boolean delivery, Long deliveryPrice, Boolean payCard, Boolean payBank, Boolean isDeleted, Boolean isEvent, String remarks, Long hit) { this.owner = owner; diff --git a/src/main/java/in/koreatech/koin/domain/user/model/Student.java b/src/main/java/in/koreatech/koin/domain/user/model/Student.java index 28cdc6582..34f0d7fd6 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/Student.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/Student.java @@ -48,7 +48,7 @@ public class Student { private User user; @Builder - public Student(String anonymousNickname, String studentNumber, String department, UserIdentity userIdentity, + private Student(String anonymousNickname, String studentNumber, String department, UserIdentity userIdentity, Boolean isGraduated, User user) { this.anonymousNickname = anonymousNickname; this.studentNumber = studentNumber; diff --git a/src/main/java/in/koreatech/koin/domain/user/model/User.java b/src/main/java/in/koreatech/koin/domain/user/model/User.java index b3b1d0ff4..d8970725b 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/User.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/User.java @@ -97,7 +97,7 @@ public class User extends BaseEntity { private String resetExpiredAt; @Builder - public User(String password, String nickname, String name, String phoneNumber, UserType userType, + private User(String password, String nickname, String name, String phoneNumber, UserType userType, String email, UserGender gender, Boolean isAuthed, LocalDateTime lastLoggedAt, String profileImageUrl, Boolean isDeleted, From 41791b1780a13b3ef27ec7a380fdefc9496af939 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 7 Mar 2024 10:20:22 +0900 Subject: [PATCH 053/123] =?UTF-8?q?refactor:=20enum=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/BusRemainTimeResponse.java | 2 +- .../koin/domain/bus/model/BusStation.java | 12 +++++------- .../koreatech/koin/domain/bus/model/BusType.java | 16 +++++----------- .../koreatech/koin/domain/bus/model/Route.java | 2 +- .../koin/domain/bus/service/BusService.java | 2 +- .../in/koreatech/koin/acceptance/BusApiTest.java | 8 ++++---- 6 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 3128ac186..a4dd54f4b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -19,7 +19,7 @@ public record BusRemainTimeResponse( public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( - busType.getName(), + busType.name().toLowerCase(), InnerBusResponse.of(remainTimes, 0, clock), InnerBusResponse.of(remainTimes, 1, clock) ); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java index 93a99ce6d..1e4b030b3 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java @@ -8,22 +8,20 @@ @Getter public enum BusStation { - KOREATECH("koreatech", List.of("학교", "한기대")), - STATION("station", List.of("천안역", "천안역(학화호두과자)")), - TERMINAL("terminal", List.of("터미널", "터미널(신세계 앞 횡단보도)")), + KOREATECH(List.of("학교", "한기대")), + STATION(List.of("천안역", "천안역(학화호두과자)")), + TERMINAL(List.of("터미널", "터미널(신세계 앞 횡단보도)")), ; - private final String name; private final List displayNames; - BusStation(String name, List displayNames) { - this.name = name; + BusStation(List displayNames) { this.displayNames = displayNames; } public static BusStation from(String busStationName) { return Arrays.stream(values()) - .filter(busStation -> busStation.name.equals(busStationName)) + .filter(busStation -> busStation.name().equalsIgnoreCase(busStationName)) .findAny() .orElseThrow(() -> BusStationNotFoundException.withDetail("busStation: " + busStationName)); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java index 4102c53ed..f7623af0d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java @@ -7,21 +7,15 @@ @Getter public enum BusType { - CITY("city"), - EXPRESS("express"), - SHUTTLE("shuttle"), - COMMUTING("commuting"), + CITY, + EXPRESS, + SHUTTLE, + COMMUTING, ; - private String name; - - BusType(String name) { - this.name = name; - } - public static BusType from(String busTypeName) { return Arrays.stream(values()) - .filter(busType -> busType.name.equals(busTypeName)) + .filter(busType -> busType.name().equalsIgnoreCase(busTypeName)) .findAny() .orElseThrow(() -> BusStationNotFoundException.withDetail("busType: " + busTypeName)); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java index 587b376ab..6e903dd9c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Route.java @@ -63,7 +63,7 @@ private ArrivalNode convertToArrivalNode(BusStation busStation) { .filter(node -> busStation.getDisplayNames().contains(node.getNodeName())) .findFirst() .orElseThrow(() -> BusArrivalNodeNotFoundException.withDetail( - "routeName: " + routeName + ", busStation: " + busStation.getName())); + "routeName: " + routeName + ", busStation: " + busStation.name())); } @Builder diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 608b777e4..2b2ca4484 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -27,7 +27,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN BusStation arrival = BusStation.from(arrivalName); BusType busType = BusType.from(busTypeName); - List busCourses = busRepository.findByBusType(busType.getName()); + List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); List remainTimes = busCourses.stream() .map(BusCourse::getRoutes) .flatMap(routes -> diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index a9915987d..a516f5de9 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -81,9 +81,9 @@ void getNextShuttleBusRemainTime() { .log().all() .when() .log().all() - .param("bus_type", busType.getName()) - .param("depart", depart.getName()) - .param("arrival", arrival.getName()) + .param("bus_type", busType.name().toLowerCase()) + .param("depart", depart.name()) + .param("arrival", arrival.name()) .get("/bus") .then() .log().all() @@ -92,7 +92,7 @@ void getNextShuttleBusRemainTime() { SoftAssertions.assertSoftly( softly -> { - softly.assertThat(response.body().jsonPath().getString("bus_type")).isEqualTo(busType.getName()); + softly.assertThat(response.body().jsonPath().getString("bus_type")).isEqualTo(busType.name().toLowerCase()); softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull(); softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); From 54e2d7ca85777a32238671717b11e0ad32536291 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Fri, 8 Mar 2024 12:22:57 +0900 Subject: [PATCH 054/123] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/bus/service/BusService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 0c781dca9..9e98f4f95 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -8,10 +8,12 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusDirection; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; +import in.koreatech.koin.domain.bus.util.BusOpenApiRequestor; import lombok.RequiredArgsConstructor; @Service From 2cc419843d4bfbdcd4c3d038143c21054eed974c Mon Sep 17 00:00:00 2001 From: songsunkook Date: Sun, 10 Mar 2024 20:10:05 +0900 Subject: [PATCH 055/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=8A=A4=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/util/BusOpenApiRequestor.java | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java index 06cfdb095..563b45455 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java @@ -34,8 +34,9 @@ @RequiredArgsConstructor public class BusOpenApiRequestor { - private static final String CHEONAN_CITY_CODE = "34010"; private static final String ENCODE_TYPE = "UTF-8"; + private static final String CHEONAN_CITY_CODE = "34010"; + private static final List AVAILABLE_CITY_BUS = List.of(400, 401, 402, 405, 815); @Value("${OPEN_API_KEY}") private String OPEN_API_KEY; @@ -47,11 +48,9 @@ public class BusOpenApiRequestor { public List getCityBusArrivalInfoByOpenApi(String nodeId) { List arrivalInfos = extractBusArrivalInfo(getCityBusArrivalInfo(nodeId)); - - // TODO - // 특정 버스번호 필터링 - - return null; + arrivalInfos.removeIf(arrivalInfo -> !AVAILABLE_CITY_BUS.contains(arrivalInfo.getRouteno())); + //TODO: 레디스 캐싱 + return arrivalInfos; } private String getCityBusArrivalInfo(String nodeId) { @@ -92,8 +91,32 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE return urlBuilder.toString(); } - // 캐싱 전까지 작업하고 푸시 (파싱 끝내고) private List extractBusArrivalInfo(String jsonResponse) { + jsonResponse = "{\n" + + "\"response\": {\n" + + "\"header\": {\n" + + "\"resultCode\": \"00\",\n" + + "\"resultMsg\": \"NORMAL SERVICE.\"\n" + + "},\n" + + "\"body\": {\n" + + "\"items\": {\n" + + "\"item\": {\n" + + "\"arrprevstationcnt\": 4,\n" + + "\"arrtime\": 218,\n" + + "\"nodeid\": \"CAB285000405\",\n" + + "\"nodenm\": \"코리아텍\",\n" + + "\"routeid\": \"CAB285000143\",\n" + + "\"routeno\": 400,\n" + + "\"routetp\": \"일반버스\",\n" + + "\"vehicletp\": \"일반차량\"\n" + + "}\n" + + "},\n" + + "\"numOfRows\": 30,\n" + + "\"pageNo\": 1,\n" + + "\"totalCount\": 1\n" + + "}\n" + + "}\n" + + "}"; List result = new ArrayList<>(); try { From f222aa70b6b5fdbec998f05afff6a80d2b5690e3 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 11 Mar 2024 11:40:15 +0900 Subject: [PATCH 056/123] =?UTF-8?q?feat:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EC=A1=B0=ED=9A=8C=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EB=A0=88=EB=94=94=EC=8A=A4=20=EC=BA=90=EC=8B=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/BusCacheNotFoundException.java | 17 ++ .../koin/domain/bus/model/BusRemainTime.java | 6 + .../koin/domain/bus/model/BusStationNode.java | 11 ++ .../domain/bus/model/CityBusArrivalInfo.java | 29 +++- .../koin/domain/bus/model/CityBusCache.java | 42 +++++ .../repository/CityBusCacheRepository.java | 21 +++ .../koin/domain/bus/service/BusService.java | 4 +- .../domain/bus/util/BusOpenApiRequestor.java | 154 ++++++++++++------ 8 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/BusCacheNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/BusCacheNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/BusCacheNotFoundException.java new file mode 100644 index 000000000..3bdf9c0cb --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/BusCacheNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class BusCacheNotFoundException extends DataNotFoundException { + + private static final String DEFAULT_MESSAGE = "버스 캐시가 존재하지 않습니다."; + + public BusCacheNotFoundException(String message) { + super(message); + } + + public static BusCacheNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new BusCacheNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 427a5d958..f22dee635 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -30,6 +30,12 @@ public static BusRemainTime from(String remainTime) { .build(); } + public static BusRemainTime from(Long remainTime) { + return builder() + .busArrivalTime(LocalTime.now().plusSeconds(remainTime)) + .build(); + } + private static LocalTime toLocalTime(String remainTime) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); return LocalTime.parse(remainTime, formatter); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java index 9114b729a..d6c0e3970 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java @@ -3,12 +3,17 @@ import static in.koreatech.koin.domain.bus.model.BusDirection.NORTH; import static in.koreatech.koin.domain.bus.model.BusDirection.SOUTH; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import lombok.Getter; + /** * OpenApi 상세: 국토교통부_전국 버스정류장 위치정보 (버스 정류장 노드 ID) * https://www.data.go.kr/data/15067528/fileData.do */ +@Getter public enum BusStationNode { TERMINAL(Map.of(NORTH, "CAB285000686", SOUTH, "CAB285000685")), // 종합터미널 KOREATECH(Map.of(NORTH, "CAB285000406", SOUTH, "CAB285000405")), // 코리아텍 @@ -24,4 +29,10 @@ public enum BusStationNode { public String getId(BusDirection direction) { return node.get(direction); } + + public static List getNodeIds() { + return Arrays.stream(values()) + .flatMap(station -> station.node.values().stream()) + .toList(); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java index 04136da53..887f0d205 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import lombok.Builder; import lombok.Getter; @Getter @@ -14,15 +15,35 @@ public class CityBusArrivalInfo/* implements Comparable */{ * "routetp": "일반버스", * "vehicletp": "일반차량" */ - private int arrprevstationcnt; // 남은 정거장 개수 - private int arrtime; // 도착까지 남은 시간 + private Long arrprevstationcnt; // 남은 정거장 개수 + private Long arrtime; // 도착까지 남은 시간 private String nodeid; private String nodenm; private String routeid; - private int routeno; // 버스 번호 + private Long routeno; // 버스 번호 private String routetp; private String vehicletp; -/* + //TODO: record로 변환 가능한지 확인 + public static CityBusArrivalInfo getEmpty(String nodeid) { + return builder() + .nodeid(nodeid) + .arrtime(-1L) + .build(); + } + + @Builder + private CityBusArrivalInfo(Long arrprevstationcnt, Long arrtime, String nodeid, String nodenm, String routeid, + Long routeno, String routetp, String vehicletp) { + this.arrprevstationcnt = arrprevstationcnt; + this.arrtime = arrtime; + this.nodeid = nodeid; + this.nodenm = nodenm; + this.routeid = routeid; + this.routeno = routeno; + this.routetp = routetp; + this.vehicletp = vehicletp; + } + /* @Override public int compareTo(CityBusArrivalInfo o) { return this.arrtime - o.arrtime; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java new file mode 100644 index 000000000..03e5ab950 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java @@ -0,0 +1,42 @@ +package in.koreatech.koin.domain.bus.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; + +@Getter +@RedisHash("CityBus") +public class CityBusCache { + + private static final long CACHE_EXPIRE_MINUTE = 1L; + + @Id + private String id; + + private final List remainTime = new ArrayList<>(); + + @TimeToLive(unit = TimeUnit.MINUTES) + private final Long expiration; + + @Builder + private CityBusCache(String id, List remainTime, Long expiration) { + this.id = id; + this.remainTime.addAll(remainTime); + this.expiration = expiration; + } + + public static CityBusCache create(String nodeId, List remainTime) { + return CityBusCache.builder() + .id(nodeId) + .remainTime(remainTime) + .expiration(CACHE_EXPIRE_MINUTE) + .build(); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java new file mode 100644 index 000000000..f5e3af728 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java @@ -0,0 +1,21 @@ +package in.koreatech.koin.domain.bus.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; +import in.koreatech.koin.domain.bus.model.CityBusCache; + +public interface CityBusCacheRepository extends Repository { + + CityBusCache save(CityBusCache cityBusCache); + + List findAll(); + Optional findById(String nodeId); + + default CityBusCache getById(String nodeId) { + return findById(nodeId).orElseThrow(() -> BusCacheNotFoundException.withDetail("nodeId: " + nodeId)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 9e98f4f95..97566b71f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -36,9 +36,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN // ===================================== - - // String result1 = busOpenApiRequestor.getCityBusArrivalInfo(departStation.getNodeId(direction)); - + List cityBusRemainTime = busOpenApiRequestor.getCityBusRemainTime(depart.getNodeId(direction)); // ===================================== diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java index 563b45455..bc1441c05 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java @@ -2,18 +2,16 @@ import static java.net.URLEncoder.encode; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -23,7 +21,12 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.BusStationNode; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; +import in.koreatech.koin.domain.bus.model.CityBusCache; +import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; +import in.koreatech.koin.domain.version.repository.VersionRepository; import lombok.RequiredArgsConstructor; /** @@ -32,30 +35,85 @@ */ @Component @RequiredArgsConstructor +@Transactional(readOnly = true) public class BusOpenApiRequestor { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; - private static final List AVAILABLE_CITY_BUS = List.of(400, 401, 402, 405, 815); @Value("${OPEN_API_KEY}") private String OPEN_API_KEY; private final Gson gson; + private final VersionRepository versionRepository; + private final CityBusCacheRepository cityBusCacheRepository; + private static final Type arrivalInfoType = new TypeToken>() { }.getType(); - public List getCityBusArrivalInfoByOpenApi(String nodeId) { - List arrivalInfos = extractBusArrivalInfo(getCityBusArrivalInfo(nodeId)); - arrivalInfos.removeIf(arrivalInfo -> !AVAILABLE_CITY_BUS.contains(arrivalInfo.getRouteno())); - //TODO: 레디스 캐싱 - return arrivalInfos; + public List getCityBusRemainTime(String nodeId) { + if (cityBusCacheRepository.findById(nodeId).isPresent()) { + return getCityBusArrivalInfoByCache(nodeId); + } + return getCityBusArrivalInfoByOpenApi(nodeId); } - private String getCityBusArrivalInfo(String nodeId) { - try { - URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); + /** + * 당장 해야하는 것: 버전 정보 저장 + * + * 현재: 레디스에 각 노드별 정보를 비어있어도 전부 저장한다. + * 구상: mysql에 버전 최신화 시각 정보를 기준으로 판단한다. -> 정보가 없는건 저장하지 않아도 된다. -> 성능 향상 + * -> Pair 미사용 가능 + * + * BusRemainTIme 대신 ArrivalInfo를 하는게 확장성에 유리 + */ + + private List getCityBusArrivalInfoByCache(String nodeId) { + CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); + return cityBusCache.getRemainTime().stream() + .map(BusRemainTime::from) + .collect(Collectors.toList()); + } + + private List getCityBusArrivalInfoByOpenApi(String nodeId) { + getAllCityBusArrivalInfoByOpenApi(); + CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); + return cityBusCache.getRemainTime().stream() + .map(BusRemainTime::from) + .toList(); + } + + private void getAllCityBusArrivalInfoByOpenApi() { + List>> arrivalInfosList = BusStationNode.getNodeIds().stream() + .map(this::getOpenApiResponse) + .map(this::extractBusArrivalInfo) + .map(arrivalInfo -> { + if (arrivalInfo.getSecond().isEmpty()) { + return Pair.of(arrivalInfo.getFirst(), + List.of(CityBusArrivalInfo.getEmpty(arrivalInfo.getFirst()))); + } + return arrivalInfo; + }) + .toList(); + + arrivalInfosList.forEach(arrivalInfos -> + cityBusCacheRepository.save( + CityBusCache.create( + arrivalInfos.getFirst(), + arrivalInfos.getSecond().stream() + .map(CityBusArrivalInfo::getArrtime) + .toList() + ) + ) + ); + } + + private Pair getOpenApiResponse(String nodeId) { + // try { + //TODO:TEST용, 지우기 + return Pair.of(nodeId, ""); + /*URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-type", "application/json"); @@ -74,10 +132,11 @@ private String getCityBusArrivalInfo(String nodeId) { } input.close(); conn.disconnect(); - return response.toString(); + return Pair.of(nodeId, response.toString()); } catch (IOException e) { - return null; - } + //TODO: 외부 API 호출 예외처리 + return Pair.of(nodeId, ""); + }*/ } private String getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { @@ -91,36 +150,37 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE return urlBuilder.toString(); } - private List extractBusArrivalInfo(String jsonResponse) { - jsonResponse = "{\n" - + "\"response\": {\n" - + "\"header\": {\n" - + "\"resultCode\": \"00\",\n" - + "\"resultMsg\": \"NORMAL SERVICE.\"\n" - + "},\n" - + "\"body\": {\n" - + "\"items\": {\n" - + "\"item\": {\n" - + "\"arrprevstationcnt\": 4,\n" - + "\"arrtime\": 218,\n" - + "\"nodeid\": \"CAB285000405\",\n" - + "\"nodenm\": \"코리아텍\",\n" - + "\"routeid\": \"CAB285000143\",\n" - + "\"routeno\": 400,\n" - + "\"routetp\": \"일반버스\",\n" - + "\"vehicletp\": \"일반차량\"\n" - + "}\n" - + "},\n" - + "\"numOfRows\": 30,\n" - + "\"pageNo\": 1,\n" - + "\"totalCount\": 1\n" - + "}\n" - + "}\n" - + "}"; + private Pair> extractBusArrivalInfo(Pair jsonResponse) { + jsonResponse = Pair.of("CAB285000405", + "{\n" + + "\"response\": {\n" + + "\"header\": {\n" + + "\"resultCode\": \"00\",\n" + + "\"resultMsg\": \"NORMAL SERVICE.\"\n" + + "},\n" + + "\"body\": {\n" + + "\"items\": {\n" + + "\"item\": {\n" + + "\"arrprevstationcnt\": 4,\n" + + "\"arrtime\": 218,\n" + + "\"nodeid\": \"CAB285000405\",\n" + + "\"nodenm\": \"코리아텍\",\n" + + "\"routeid\": \"CAB285000143\",\n" + + "\"routeno\": 400,\n" + + "\"routetp\": \"일반버스\",\n" + + "\"vehicletp\": \"일반차량\"\n" + + "}\n" + + "},\n" + + "\"numOfRows\": 30,\n" + + "\"pageNo\": 1,\n" + + "\"totalCount\": 1\n" + + "}\n" + + "}\n" + + "}"); List result = new ArrayList<>(); try { - JsonObject response = JsonParser.parseString(jsonResponse) + JsonObject response = JsonParser.parseString(jsonResponse.getSecond()) .getAsJsonObject() .get("response") .getAsJsonObject(); @@ -128,7 +188,7 @@ private List extractBusArrivalInfo(String jsonResponse) { JsonObject body = response.get("body").getAsJsonObject(); if (body.get("totalCount").getAsLong() == 0) { - return result; + return Pair.of(jsonResponse.getFirst(), result); } JsonElement item = body.get("items").getAsJsonObject().get("item"); @@ -138,9 +198,9 @@ private List extractBusArrivalInfo(String jsonResponse) { if (item.isJsonObject()) { result.add(gson.fromJson(item, CityBusArrivalInfo.class)); } - return result; + return Pair.of(jsonResponse.getFirst(), result); } catch (JsonSyntaxException e) { - return result; + return Pair.of(jsonResponse.getFirst(), result); } } From 16d184e7edb7101812a30fe64a2e4d8913446a40 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Mon, 11 Mar 2024 12:05:45 +0900 Subject: [PATCH 057/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=A0=84=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=ED=99=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/version/model/Version.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/version/model/Version.java b/src/main/java/in/koreatech/koin/domain/version/model/Version.java index e31217bf7..8740f4b1b 100644 --- a/src/main/java/in/koreatech/koin/domain/version/model/Version.java +++ b/src/main/java/in/koreatech/koin/domain/version/model/Version.java @@ -1,5 +1,8 @@ package in.koreatech.koin.domain.version.model; +import java.time.Clock; +import java.time.LocalDate; + import in.koreatech.koin.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -38,4 +41,15 @@ private Version(@NotNull String version, @NotNull VersionType type) { this.version = version; this.type = type; } + + public void update(Clock clock) { + generateVersionName(clock); + } + + private String generateVersionName(Clock clock) { + String year = Integer.toString(LocalDate.now().getYear()); + String padding = "0_"; + String epochSeconds = Long.toString(clock.instant().getEpochSecond()); + return year + padding + epochSeconds; + } } From a8aa7d509211ca7d8346693d5a5d4ca6b7e61466 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Sat, 16 Mar 2024 21:15:48 +0900 Subject: [PATCH 058/123] =?UTF-8?q?refactor:=20Pair=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=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 --- .../domain/bus/util/BusOpenApiRequestor.java | 73 ++++++------------- 1 file changed, 21 insertions(+), 52 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java index bc1441c05..1fa80ee25 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java @@ -2,14 +2,18 @@ import static java.net.URLEncoder.encode; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -64,7 +68,7 @@ public List getCityBusRemainTime(String nodeId) { * * 현재: 레디스에 각 노드별 정보를 비어있어도 전부 저장한다. * 구상: mysql에 버전 최신화 시각 정보를 기준으로 판단한다. -> 정보가 없는건 저장하지 않아도 된다. -> 성능 향상 - * -> Pair 미사용 가능 + * * * BusRemainTIme 대신 ArrivalInfo를 하는게 확장성에 유리 */ @@ -85,23 +89,17 @@ private List getCityBusArrivalInfoByOpenApi(String nodeId) { } private void getAllCityBusArrivalInfoByOpenApi() { - List>> arrivalInfosList = BusStationNode.getNodeIds().stream() + List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) - .map(arrivalInfo -> { - if (arrivalInfo.getSecond().isEmpty()) { - return Pair.of(arrivalInfo.getFirst(), - List.of(CityBusArrivalInfo.getEmpty(arrivalInfo.getFirst()))); - } - return arrivalInfo; - }) .toList(); + //TODO: 아래 내용 for문으로 수정(save는 forEach에서 하면 이슈 발생) arrivalInfosList.forEach(arrivalInfos -> cityBusCacheRepository.save( CityBusCache.create( - arrivalInfos.getFirst(), - arrivalInfos.getSecond().stream() + arrivalInfos.get(0).getNodeid(), //TODO: 비어있는 정보는 저장 전에 걸러내야 할 거임 (안그러면 에러남) + arrivalInfos.stream() .map(CityBusArrivalInfo::getArrtime) .toList() ) @@ -109,11 +107,9 @@ private void getAllCityBusArrivalInfoByOpenApi() { ); } - private Pair getOpenApiResponse(String nodeId) { - // try { - //TODO:TEST용, 지우기 - return Pair.of(nodeId, ""); - /*URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); + private String getOpenApiResponse(String nodeId) { + try { + URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-type", "application/json"); @@ -132,11 +128,10 @@ private Pair getOpenApiResponse(String nodeId) { } input.close(); conn.disconnect(); - return Pair.of(nodeId, response.toString()); + return response.toString(); } catch (IOException e) { - //TODO: 외부 API 호출 예외처리 - return Pair.of(nodeId, ""); - }*/ + return null; + } } private String getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { @@ -150,37 +145,11 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE return urlBuilder.toString(); } - private Pair> extractBusArrivalInfo(Pair jsonResponse) { - jsonResponse = Pair.of("CAB285000405", - "{\n" - + "\"response\": {\n" - + "\"header\": {\n" - + "\"resultCode\": \"00\",\n" - + "\"resultMsg\": \"NORMAL SERVICE.\"\n" - + "},\n" - + "\"body\": {\n" - + "\"items\": {\n" - + "\"item\": {\n" - + "\"arrprevstationcnt\": 4,\n" - + "\"arrtime\": 218,\n" - + "\"nodeid\": \"CAB285000405\",\n" - + "\"nodenm\": \"코리아텍\",\n" - + "\"routeid\": \"CAB285000143\",\n" - + "\"routeno\": 400,\n" - + "\"routetp\": \"일반버스\",\n" - + "\"vehicletp\": \"일반차량\"\n" - + "}\n" - + "},\n" - + "\"numOfRows\": 30,\n" - + "\"pageNo\": 1,\n" - + "\"totalCount\": 1\n" - + "}\n" - + "}\n" - + "}"); + private List extractBusArrivalInfo(String jsonResponse) { List result = new ArrayList<>(); try { - JsonObject response = JsonParser.parseString(jsonResponse.getSecond()) + JsonObject response = JsonParser.parseString(jsonResponse) .getAsJsonObject() .get("response") .getAsJsonObject(); @@ -188,7 +157,7 @@ private Pair> extractBusArrivalInfo(Pair> extractBusArrivalInfo(Pair Date: Fri, 22 Mar 2024 18:30:11 +0900 Subject: [PATCH 059/123] =?UTF-8?q?feat:=20ApiType=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BusType과 BusOpenApiRequester 타입 맵핑을 위함 Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../exception/ApiTypeNotFoundException.java | 17 ++++++++++++++ .../koin/domain/bus/model/ApiType.java | 22 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java new file mode 100644 index 000000000..0ea5945a3 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java @@ -0,0 +1,17 @@ +package in.koreatech.koin.domain.bus.exception; + +import in.koreatech.koin.global.exception.DataNotFoundException; + +public class ApiTypeNotFoundException extends DataNotFoundException { + + public static final String DEFAULT_MESSAGE = "존재하지 않는 API 타입입니다."; + + public ApiTypeNotFoundException(String message) { + super(message); + } + + public static in.koreatech.koin.domain.version.exception.VersionTypeNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new in.koreatech.koin.domain.version.exception.VersionTypeNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java new file mode 100644 index 000000000..0833e8cb5 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java @@ -0,0 +1,22 @@ +package in.koreatech.koin.domain.bus.model; + +import java.util.Arrays; + +import in.koreatech.koin.domain.bus.exception.ApiTypeNotFoundException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ApiType { + CITY("cityBusOpenApiRequester"), EXPRESS("intercityBusOpenApiRequester"); + + private final String value; + + public static ApiType from(BusType value) { + return Arrays.stream(values()) + .filter(apiType -> apiType.toString().equalsIgnoreCase(value.toString())) + .findAny() + .orElseThrow(() -> ApiTypeNotFoundException.withDetail("apiType: " + value)); + } +} From f8a64bc00dba505fac3daf3cc199be2d162a91a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 18:34:02 +0900 Subject: [PATCH 060/123] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/model/CityBusArrivalInfo.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java index 887f0d205..a1e655524 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -4,7 +4,7 @@ import lombok.Getter; @Getter -public class CityBusArrivalInfo/* implements Comparable */{ +public class CityBusArrivalInfo/* implements Comparable */ { /** * "arrprevstationcnt": 5, * "arrtime": 222, @@ -16,13 +16,14 @@ public class CityBusArrivalInfo/* implements Comparable */{ * "vehicletp": "일반차량" */ private Long arrprevstationcnt; // 남은 정거장 개수 - private Long arrtime; // 도착까지 남은 시간 - private String nodeid; - private String nodenm; - private String routeid; + private Long arrtime; // 도착까지 남은 시간 [초] + private String nodeid; // 정류소 id + private String nodenm; // 정류소명 + private String routeid; // 노선 id private Long routeno; // 버스 번호 - private String routetp; - private String vehicletp; + private String routetp; // 노선 유형 + private String vehicletp; // 차량 유형 (저상버스) + //TODO: record로 변환 가능한지 확인 public static CityBusArrivalInfo getEmpty(String nodeid) { return builder() From 0c16d2d7d605bef36a6de27b24173364a2274da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 18:45:40 +0900 Subject: [PATCH 061/123] =?UTF-8?q?refactor:=20record=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../domain/bus/model/CityBusArrivalInfo.java | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java index a1e655524..8db6cca11 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -1,30 +1,28 @@ package in.koreatech.koin.domain.bus.model; import lombok.Builder; -import lombok.Getter; -@Getter -public class CityBusArrivalInfo/* implements Comparable */ { - /** - * "arrprevstationcnt": 5, - * "arrtime": 222, - * "nodeid": "CAB285000405", - * "nodenm": "코리아텍", - * "routeid": "CAB285000147", - * "routeno": 402, - * "routetp": "일반버스", - * "vehicletp": "일반차량" - */ - private Long arrprevstationcnt; // 남은 정거장 개수 - private Long arrtime; // 도착까지 남은 시간 [초] - private String nodeid; // 정류소 id - private String nodenm; // 정류소명 - private String routeid; // 노선 id - private Long routeno; // 버스 번호 - private String routetp; // 노선 유형 - private String vehicletp; // 차량 유형 (저상버스) +/** + * "arrprevstationcnt": 5, + * "arrtime": 222, + * "nodeid": "CAB285000405", + * "nodenm": "코리아텍", + * "routeid": "CAB285000147", + * "routeno": 402, + * "routetp": "일반버스", + * "vehicletp": "일반차량" + */ - //TODO: record로 변환 가능한지 확인 +public record CityBusArrivalInfo( + Long arrprevstationcnt, // 남은 정거장 개수 + Long arrtime, // 도착까지 남은 시간 [초] + String nodeid, // 정류소 id + String nodenm, // 정류소명 + String routeid, // 노선 id + Long routeno, // 버스 번호 + String routetp, // 노선 유형 + String vehicletp // 차량 유형 (저상버스) +) { public static CityBusArrivalInfo getEmpty(String nodeid) { return builder() .nodeid(nodeid) @@ -33,16 +31,7 @@ public static CityBusArrivalInfo getEmpty(String nodeid) { } @Builder - private CityBusArrivalInfo(Long arrprevstationcnt, Long arrtime, String nodeid, String nodenm, String routeid, - Long routeno, String routetp, String vehicletp) { - this.arrprevstationcnt = arrprevstationcnt; - this.arrtime = arrtime; - this.nodeid = nodeid; - this.nodenm = nodenm; - this.routeid = routeid; - this.routeno = routeno; - this.routetp = routetp; - this.vehicletp = vehicletp; + public CityBusArrivalInfo { } /* @Override From 3d7f2c18639f4ebf550ab8feb0a3e77f1dd6b817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 18:52:05 +0900 Subject: [PATCH 062/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EC=BA=90=EC=8B=9C=20=EC=A0=80=EC=9E=A5=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koreatech/koin/domain/bus/model/CityBusCache.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java index 03e5ab950..e93faf533 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java @@ -20,22 +20,22 @@ public class CityBusCache { @Id private String id; - private final List remainTime = new ArrayList<>(); + private final List busArrivalInfos = new ArrayList<>(); @TimeToLive(unit = TimeUnit.MINUTES) private final Long expiration; @Builder - private CityBusCache(String id, List remainTime, Long expiration) { + private CityBusCache(String id, List busArrivalInfos, Long expiration) { this.id = id; - this.remainTime.addAll(remainTime); + this.busArrivalInfos.addAll(busArrivalInfos); this.expiration = expiration; } - public static CityBusCache create(String nodeId, List remainTime) { + public static CityBusCache create(String nodeId, List busArrivalInfos) { return CityBusCache.builder() .id(nodeId) - .remainTime(remainTime) + .busArrivalInfos(busArrivalInfos) .expiration(CACHE_EXPIRE_MINUTE) .build(); } From 5edcb712bef24f655aad0d7765b525b74824b29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 19:17:40 +0900 Subject: [PATCH 063/123] =?UTF-8?q?feat:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=84=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../domain/bus/dto/BusRemainTimeResponse.java | 19 +++++++++++----- .../koreatech/koin/domain/bus/model/Bus.java | 16 ++++++++++++++ .../koin/domain/bus/model/CityBus.java | 22 +++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Bus.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index a4dd54f4b..6000b0a32 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -7,8 +7,9 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.CityBus; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -17,7 +18,7 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.name().toLowerCase(), InnerBusResponse.of(remainTimes, 0, clock), @@ -31,12 +32,18 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse of(List remainTimes, int index, Clock clock) { - Long result = null; + public static InnerBusResponse of(List remainTimes, int index, Clock clock) { + Long busNumber = null; + Long remainTime = null; if (index < remainTimes.size()) { - result = remainTimes.get(index).getRemainSeconds(clock); + remainTime = remainTimes.get(index).getRemainTime().getRemainSeconds(clock); + + if (remainTimes.get(index) instanceof CityBus cityBus) { + busNumber = cityBus.getBusNumber(); + } } - return new InnerBusResponse(null, result); + + return new InnerBusResponse(busNumber, remainTime); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java new file mode 100644 index 000000000..c55e6f947 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java @@ -0,0 +1,16 @@ +package in.koreatech.koin.domain.bus.model; + +import lombok.Getter; + +@Getter +public class Bus { + BusRemainTime remainTime; + + public Bus(BusRemainTime remainTime) { + this.remainTime = remainTime; + } + + public static Bus from(BusRemainTime busRemainTime) { + return new Bus(busRemainTime); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java new file mode 100644 index 000000000..3413f756b --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java @@ -0,0 +1,22 @@ +package in.koreatech.koin.domain.bus.model; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class CityBus extends Bus { + private final Long busNumber; + + @Builder + private CityBus(Long busNumber, BusRemainTime busRemainTime) { + super(busRemainTime); + this.busNumber = busNumber; + } + + public static CityBus from(CityBusArrivalInfo busArrivalInfo) { + return CityBus.builder() + .busNumber(busArrivalInfo.routeno()) + .busRemainTime(BusRemainTime.from(busArrivalInfo.arrtime())) + .build(); + } +} From 36a61a61c7ceb455557e71d3fa19b1649fef9da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 19:18:57 +0900 Subject: [PATCH 064/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=8A=A4=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EB=B3=84=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EA=B8=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/service/BusService.java | 52 ++++++++++--------- .../domain/bus/util/BusOpenApiRequester.java | 11 ++++ .../util/IntercityBusOpenApiRequester.java | 25 +++++++++ 3 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 3e3f8d2e5..8bcca6005 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,20 +1,23 @@ package in.koreatech.koin.domain.bus.service; import java.time.Clock; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.exception.BusIllegalStationException; +import in.koreatech.koin.domain.bus.model.ApiType; +import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusDirection; -import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; -import in.koreatech.koin.domain.bus.util.BusOpenApiRequestor; +import in.koreatech.koin.domain.bus.util.BusOpenApiRequester; import lombok.RequiredArgsConstructor; @Service @@ -24,36 +27,37 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - private final BusOpenApiRequestor busOpenApiRequestor; + private final Map> busOpenApiRequesters; @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { + BusType busType = BusType.from(busTypeName); BusStation depart = BusStation.from(departName); BusStation arrival = BusStation.from(arrivalName); BusDirection direction = BusStation.getDirection(depart, arrival); - BusType busType = BusType.from(busTypeName); validateBusCourse(depart, arrival); - - - // ===================================== - - // List cityBusRemainTime = busOpenApiRequestor.getCityBusRemainTime(depart.getNodeId(direction)); - - // ===================================== - - List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); - List remainTimes = busCourses.stream() - .map(BusCourse::getRoutes) - .flatMap(routes -> - routes.stream() - .filter(route -> route.isRunning(clock)) - .filter(route -> route.isCorrectRoute(depart, arrival, clock)) - .map(route -> route.getRemainTime(depart)) - ) - .distinct() - .sorted() - .toList(); + List remainTimes = new ArrayList<>(); + switch (busType) { + case CITY, EXPRESS -> remainTimes = busOpenApiRequesters.get(ApiType.from(busType).getValue()) + .getBusRemainTime(depart.getNodeId(direction)).stream() + .toList(); + case SHUTTLE, COMMUTING -> { + List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); + remainTimes = busCourses.stream() + .map(BusCourse::getRoutes) + .flatMap(routes -> + routes.stream() + .filter(route -> route.isRunning(clock)) + .filter(route -> route.isCorrectRoute(depart, arrival, clock)) + .map(route -> route.getRemainTime(depart)) + .map(Bus::from) + ) + .distinct() + .sorted() + .toList(); + } + } return BusRemainTimeResponse.of(busType, remainTimes, clock); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java new file mode 100644 index 000000000..dc0c5acfa --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.bus.util; + +import java.util.List; + +import in.koreatech.koin.domain.bus.model.Bus; + +public abstract class BusOpenApiRequester { + + public abstract List getBusRemainTime(String nodeId); + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java new file mode 100644 index 000000000..8f178ebfd --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java @@ -0,0 +1,25 @@ +package in.koreatech.koin.domain.bus.util; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.domain.bus.model.Bus; +import lombok.RequiredArgsConstructor; + +/** + * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 + * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098530 + */ +@Component +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class IntercityBusOpenApiRequester extends BusOpenApiRequester { + + @Override + public List getBusRemainTime(String nodeId) { + return new ArrayList<>(); + } +} From c9a5023636890e671fdda1f40a0e5bea0d72bfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 22 Mar 2024 19:22:28 +0900 Subject: [PATCH 065/123] =?UTF-8?q?refactor:=20=EC=BA=90=EC=8B=B1=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 남은 시간만 캐싱 -> 버스 도착 정보 통째로 캐싱 - api url 수정 - 버전 정보 저장 - forEach -> for Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- ...stor.java => CityBusOpenApiRequester.java} | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/util/{BusOpenApiRequestor.java => CityBusOpenApiRequester.java} (80%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java similarity index 80% rename from src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java rename to src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 1fa80ee25..907660e77 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequestor.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -9,9 +9,11 @@ import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.URL; +import java.time.Clock; +import java.time.Duration; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -25,11 +27,13 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; -import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStationNode; +import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; import in.koreatech.koin.domain.bus.model.CityBusCache; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; +import in.koreatech.koin.domain.version.model.Version; +import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; import lombok.RequiredArgsConstructor; @@ -40,7 +44,7 @@ @Component @RequiredArgsConstructor @Transactional(readOnly = true) -public class BusOpenApiRequestor { +public class CityBusOpenApiRequester extends BusOpenApiRequester { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; @@ -53,11 +57,20 @@ public class BusOpenApiRequestor { private final VersionRepository versionRepository; private final CityBusCacheRepository cityBusCacheRepository; + private final Clock clock; + private static final Type arrivalInfoType = new TypeToken>() { }.getType(); - public List getCityBusRemainTime(String nodeId) { - if (cityBusCacheRepository.findById(nodeId).isPresent()) { + public List getBusRemainTime(String nodeId) { + Version version = versionRepository.getByType(VersionType.CITY); + + Duration duration = Duration.between(version.getUpdatedAt(), LocalTime.now(clock)); + + if (duration.toSeconds() < 60) { + if (cityBusCacheRepository.findById(nodeId).isEmpty()) { + return new ArrayList<>(); + } return getCityBusArrivalInfoByCache(nodeId); } return getCityBusArrivalInfoByOpenApi(nodeId); @@ -70,22 +83,17 @@ public List getCityBusRemainTime(String nodeId) { * 구상: mysql에 버전 최신화 시각 정보를 기준으로 판단한다. -> 정보가 없는건 저장하지 않아도 된다. -> 성능 향상 * * - * BusRemainTIme 대신 ArrivalInfo를 하는게 확장성에 유리 + * BusRemainTime 대신 ArrivalInfo를 하는게 확장성에 유리 */ - private List getCityBusArrivalInfoByCache(String nodeId) { + private List getCityBusArrivalInfoByCache(String nodeId) { CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); - return cityBusCache.getRemainTime().stream() - .map(BusRemainTime::from) - .collect(Collectors.toList()); + return cityBusCache.getBusArrivalInfos().stream().map(CityBus::from).toList(); } - private List getCityBusArrivalInfoByOpenApi(String nodeId) { + private List getCityBusArrivalInfoByOpenApi(String nodeId) { getAllCityBusArrivalInfoByOpenApi(); - CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); - return cityBusCache.getRemainTime().stream() - .map(BusRemainTime::from) - .toList(); + return getCityBusArrivalInfoByCache(nodeId); } private void getAllCityBusArrivalInfoByOpenApi() { @@ -94,17 +102,16 @@ private void getAllCityBusArrivalInfoByOpenApi() { .map(this::extractBusArrivalInfo) .toList(); - //TODO: 아래 내용 for문으로 수정(save는 forEach에서 하면 이슈 발생) - arrivalInfosList.forEach(arrivalInfos -> + for (List arrivalInfos : arrivalInfosList) { + if (arrivalInfos.isEmpty()) + continue; + cityBusCacheRepository.save( - CityBusCache.create( - arrivalInfos.get(0).getNodeid(), //TODO: 비어있는 정보는 저장 전에 걸러내야 할 거임 (안그러면 에러남) - arrivalInfos.stream() - .map(CityBusArrivalInfo::getArrtime) - .toList() - ) - ) - ); + CityBusCache.create(arrivalInfos.get(0).nodeid(), arrivalInfos) + ); + } + + versionRepository.getByType(VersionType.CITY).update(Clock.systemDefaultZone()); } private String getOpenApiResponse(String nodeId) { @@ -135,13 +142,14 @@ private String getOpenApiResponse(String nodeId) { } private String getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { - String url = "http://apis.data.go.kr/1613000/BusSttnInfoInqireService/getCrdntPrxmtSttnList"; + String url = "http://apis.data.go.kr/1613000/ArvlInfoInqireService/getSttnAcctoArvlPrearngeInfoList"; String contentCount = "30"; StringBuilder urlBuilder = new StringBuilder(url); urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(OPEN_API_KEY, ENCODE_TYPE)); urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); urlBuilder.append("&" + encode("cityCode", ENCODE_TYPE) + "=" + encode(cityCode, ENCODE_TYPE)); urlBuilder.append("&" + encode("nodeId", ENCODE_TYPE) + "=" + encode(nodeId, ENCODE_TYPE)); + urlBuilder.append("&_type=json"); return urlBuilder.toString(); } From 637ad947fd592e02fab002b073d442bf158d7e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sat, 23 Mar 2024 01:06:40 +0900 Subject: [PATCH 066/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 버스 캐싱 정보 수정 - 시내버스 남은 시간 계산 로직 추가 Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/model/BusInfoCache.java | 18 ++++++++++++++++++ .../koin/domain/bus/model/CityBus.java | 10 +++++++--- .../koin/domain/bus/model/CityBusCache.java | 10 +++++----- .../bus/util/CityBusOpenApiRequester.java | 17 +++++++++++++---- 4 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java new file mode 100644 index 000000000..1cab08c70 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java @@ -0,0 +1,18 @@ +package in.koreatech.koin.domain.bus.model; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +public record BusInfoCache( + Long busNumber, + LocalTime remainTime +) { + + public static BusInfoCache from(CityBusArrivalInfo busArrivalInfo, LocalDateTime updatedAt) { + return new BusInfoCache( + busArrivalInfo.routeno(), + updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() + ); + } + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java index 3413f756b..04cc84786 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java @@ -13,10 +13,14 @@ private CityBus(Long busNumber, BusRemainTime busRemainTime) { this.busNumber = busNumber; } - public static CityBus from(CityBusArrivalInfo busArrivalInfo) { + public static CityBus from(BusInfoCache busInfo) { return CityBus.builder() - .busNumber(busArrivalInfo.routeno()) - .busRemainTime(BusRemainTime.from(busArrivalInfo.arrtime())) + .busNumber(busInfo.busNumber()) + .busRemainTime( + BusRemainTime.builder() + .busArrivalTime(busInfo.remainTime()) + .build() + ) .build(); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java index e93faf533..3899d8e3f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java @@ -20,22 +20,22 @@ public class CityBusCache { @Id private String id; - private final List busArrivalInfos = new ArrayList<>(); + private final List busInfos = new ArrayList<>(); @TimeToLive(unit = TimeUnit.MINUTES) private final Long expiration; @Builder - private CityBusCache(String id, List busArrivalInfos, Long expiration) { + private CityBusCache(String id, List busInfos, Long expiration) { this.id = id; - this.busArrivalInfos.addAll(busArrivalInfos); + this.busInfos.addAll(busInfos); this.expiration = expiration; } - public static CityBusCache create(String nodeId, List busArrivalInfos) { + public static CityBusCache create(String nodeId, List busInfos) { return CityBusCache.builder() .id(nodeId) - .busArrivalInfos(busArrivalInfos) + .busInfos(busInfos) .expiration(CACHE_EXPIRE_MINUTE) .build(); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 907660e77..50c7f3041 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -27,6 +27,7 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; +import in.koreatech.koin.domain.bus.model.BusInfoCache; import in.koreatech.koin.domain.bus.model.BusStationNode; import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; @@ -65,7 +66,7 @@ public class CityBusOpenApiRequester extends BusOpenApiRequester { public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); - Duration duration = Duration.between(version.getUpdatedAt(), LocalTime.now(clock)); + Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); if (duration.toSeconds() < 60) { if (cityBusCacheRepository.findById(nodeId).isEmpty()) { @@ -88,7 +89,7 @@ public List getBusRemainTime(String nodeId) { private List getCityBusArrivalInfoByCache(String nodeId) { CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); - return cityBusCache.getBusArrivalInfos().stream().map(CityBus::from).toList(); + return cityBusCache.getBusInfos().stream().map(CityBus::from).toList(); } private List getCityBusArrivalInfoByOpenApi(String nodeId) { @@ -106,12 +107,20 @@ private void getAllCityBusArrivalInfoByOpenApi() { if (arrivalInfos.isEmpty()) continue; + Version version = versionRepository.getByType(VersionType.CITY); + version.update(Clock.systemDefaultZone()); + cityBusCacheRepository.save( - CityBusCache.create(arrivalInfos.get(0).nodeid(), arrivalInfos) + CityBusCache.create( + arrivalInfos.get(0).nodeid(), + arrivalInfos.stream() + .map(busArrivalInfo -> BusInfoCache.from(busArrivalInfo, version.getUpdatedAt())) + .toList() + ) ); } - versionRepository.getByType(VersionType.CITY).update(Clock.systemDefaultZone()); + // versionRepository.getByType(VersionType.CITY).update(Clock.systemDefaultZone()); } private String getOpenApiResponse(String nodeId) { From 268d91d582d05c5f1814d58fa01789f0f2c98a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sat, 23 Mar 2024 01:10:36 +0900 Subject: [PATCH 067/123] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존: "now_bus": {"bus_number": null, ...} - 수정: "now_bus": null Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/dto/BusRemainTimeResponse.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 6000b0a32..8de881629 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -33,17 +33,18 @@ private record InnerBusResponse( ) { public static InnerBusResponse of(List remainTimes, int index, Clock clock) { - Long busNumber = null; - Long remainTime = null; if (index < remainTimes.size()) { - remainTime = remainTimes.get(index).getRemainTime().getRemainSeconds(clock); + Long busNumber = null; + Long remainTime = remainTimes.get(index).getRemainTime().getRemainSeconds(clock); if (remainTimes.get(index) instanceof CityBus cityBus) { busNumber = cityBus.getBusNumber(); } + + return new InnerBusResponse(busNumber, remainTime); } - return new InnerBusResponse(busNumber, remainTime); + return null; } } } From d181251b5c9813836dc82267ad2c02b1b52085e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sat, 23 Mar 2024 01:26:11 +0900 Subject: [PATCH 068/123] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../in/koreatech/koin/domain/bus/model/BusInfoCache.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java index 1cab08c70..3029d3d2c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java @@ -8,6 +8,13 @@ public record BusInfoCache( LocalTime remainTime ) { + /** + *
+     * 남은 시간 = (캐시 저장 시각 + 저장된 남은시간) - 현재시각
+     * {@link BusRemainTime#getRemainSeconds}에서는 남은 시간을 (저장된 남은시간 - 현재시각)으로 계산중
+     * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로
+     * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌
+ * */ public static BusInfoCache from(CityBusArrivalInfo busArrivalInfo, LocalDateTime updatedAt) { return new BusInfoCache( busArrivalInfo.routeno(), From 56a815745b299898ff49e32598bda69903f21d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sun, 24 Mar 2024 21:50:45 +0900 Subject: [PATCH 069/123] =?UTF-8?q?refactor:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20null=20=EC=B2=B4=ED=81=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 8de881629..505568496 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -37,7 +37,7 @@ public static InnerBusResponse of(List remainTimes, int index, Cl Long busNumber = null; Long remainTime = remainTimes.get(index).getRemainTime().getRemainSeconds(clock); - if (remainTimes.get(index) instanceof CityBus cityBus) { + if (remainTime != null && remainTimes.get(index) instanceof CityBus cityBus) { busNumber = cityBus.getBusNumber(); } From 912edec6d308dba9db18c5e586a1c4dd94e2076c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 00:25:08 +0900 Subject: [PATCH 070/123] =?UTF-8?q?feat:=20test=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/model/BusRemainTime.java | 4 +- .../koreatech/koin/acceptance/BusApiTest.java | 78 ++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 86bf31f95..c154a0cd3 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -30,9 +30,9 @@ public static BusRemainTime from(String arrivalTime) { .build(); } - public static BusRemainTime from(Long remainTime) { + public static BusRemainTime from(Long remainTime, Clock clock) { return builder() - .busArrivalTime(LocalTime.now().plusSeconds(remainTime)) + .busArrivalTime(LocalTime.now(clock).plusSeconds(remainTime)) .build(); } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index a516f5de9..ac5be6927 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -15,11 +15,18 @@ import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.BusDirection; +import in.koreatech.koin.domain.bus.model.BusInfoCache; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; +import in.koreatech.koin.domain.bus.model.CityBusCache; import in.koreatech.koin.domain.bus.model.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; +import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; +import in.koreatech.koin.domain.version.model.Version; +import in.koreatech.koin.domain.version.repository.VersionRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; @@ -29,6 +36,12 @@ class BusApiTest extends AcceptanceTest { @Autowired private BusRepository busRepository; + @Autowired + private VersionRepository versionRepository; + + @Autowired + private CityBusCacheRepository cityBusCacheRepository; + @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { @@ -92,7 +105,8 @@ void getNextShuttleBusRemainTime() { SoftAssertions.assertSoftly( softly -> { - softly.assertThat(response.body().jsonPath().getString("bus_type")).isEqualTo(busType.name().toLowerCase()); + softly.assertThat(response.body().jsonPath().getString("bus_type")) + .isEqualTo(busType.name().toLowerCase()); softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull(); softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); @@ -101,4 +115,66 @@ void getNextShuttleBusRemainTime() { } ); } + + @Test + @DisplayName("다음 시내버스까지 남은 시간을 조회한다.") + void getNextCityBusRemainTime() { + final ZonedDateTime now = ZonedDateTime.now(); + final long remainTime = 600L; + final long busNumber = 400; + + when(clock.instant()).thenReturn(now.toInstant()); + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + + BusType busType = BusType.from("city"); + BusStation depart = BusStation.from("koreatech"); + BusStation arrival = BusStation.from("terminal"); + BusDirection direction = BusStation.getDirection(depart, arrival); + + Version version = versionRepository.save( + Version.builder() + .version("20240_1711255839") + .type("city_bus_timetable") + .build() + ); + + cityBusCacheRepository.save( + CityBusCache.create( + depart.getNodeId(direction), + List.of(BusInfoCache.from( + CityBusArrivalInfo.builder() + .routeno(busNumber) + .arrtime(remainTime) + .build(), + version.getUpdatedAt()) + ) + ) + ); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("bus_type", busType.name().toLowerCase()) + .param("depart", depart.name()) + .param("arrival", arrival.name()) + .get("/bus") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.body().jsonPath().getString("bus_type")) + .isEqualTo(busType.name().toLowerCase()); + softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber); + softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( + BusRemainTime.from(remainTime, clock).getRemainSeconds(clock)); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull(); + softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull(); + } + ); + } } From 358d8bf933b61e3c0365493a6b6f6c6fcc0b8d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 00:28:48 +0900 Subject: [PATCH 071/123] =?UTF-8?q?refactor:=20=EB=B9=88=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=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 Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../bus/util/CityBusOpenApiRequester.java | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 50c7f3041..a1cff559a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -69,27 +69,18 @@ public List getBusRemainTime(String nodeId) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); if (duration.toSeconds() < 60) { - if (cityBusCacheRepository.findById(nodeId).isEmpty()) { - return new ArrayList<>(); - } return getCityBusArrivalInfoByCache(nodeId); } return getCityBusArrivalInfoByOpenApi(nodeId); } - /** - * 당장 해야하는 것: 버전 정보 저장 - * - * 현재: 레디스에 각 노드별 정보를 비어있어도 전부 저장한다. - * 구상: mysql에 버전 최신화 시각 정보를 기준으로 판단한다. -> 정보가 없는건 저장하지 않아도 된다. -> 성능 향상 - * - * - * BusRemainTime 대신 ArrivalInfo를 하는게 확장성에 유리 - */ - private List getCityBusArrivalInfoByCache(String nodeId) { - CityBusCache cityBusCache = cityBusCacheRepository.getById(nodeId); - return cityBusCache.getBusInfos().stream().map(CityBus::from).toList(); + CityBusCache cityBusCache = cityBusCacheRepository.findById(nodeId).orElse(null); + if (cityBusCache != null) { + return cityBusCache.getBusInfos().stream().map(CityBus::from).toList(); + } + + return new ArrayList<>(); } private List getCityBusArrivalInfoByOpenApi(String nodeId) { @@ -103,13 +94,13 @@ private void getAllCityBusArrivalInfoByOpenApi() { .map(this::extractBusArrivalInfo) .toList(); + Version version = versionRepository.getByType(VersionType.CITY); + version.update(Clock.systemDefaultZone()); + for (List arrivalInfos : arrivalInfosList) { if (arrivalInfos.isEmpty()) continue; - Version version = versionRepository.getByType(VersionType.CITY); - version.update(Clock.systemDefaultZone()); - cityBusCacheRepository.save( CityBusCache.create( arrivalInfos.get(0).nodeid(), From 15b6f690e9807ce04766345269f1523c81983779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 00:29:33 +0900 Subject: [PATCH 072/123] =?UTF-8?q?refactor:=20=EB=8F=84=EC=B0=A9=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/service/BusService.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 8bcca6005..53d5e6d8f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -2,8 +2,10 @@ import java.time.Clock; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,8 +42,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN List remainTimes = new ArrayList<>(); switch (busType) { case CITY, EXPRESS -> remainTimes = busOpenApiRequesters.get(ApiType.from(busType).getValue()) - .getBusRemainTime(depart.getNodeId(direction)).stream() - .toList(); + .getBusRemainTime(depart.getNodeId(direction)); case SHUTTLE, COMMUTING -> { List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); remainTimes = busCourses.stream() @@ -59,7 +60,14 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN } } - return BusRemainTimeResponse.of(busType, remainTimes, clock); + return BusRemainTimeResponse.of( + busType, + remainTimes.stream() + .filter(Objects::nonNull) + .sorted(Comparator.comparing(Bus::getRemainTime)) + .toList(), + clock + ); } private void validateBusCourse(BusStation depart, BusStation arrival) { From fa21a8aa638337b98431a0cdde93e26838c47ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 10:19:18 +0900 Subject: [PATCH 073/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index a1cff559a..04a6d79ea 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -68,7 +68,7 @@ public List getBusRemainTime(String nodeId) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - if (duration.toSeconds() < 60) { + if (0 <= duration.toSeconds() && duration.toSeconds() < 60) { return getCityBusArrivalInfoByCache(nodeId); } return getCityBusArrivalInfoByOpenApi(nodeId); From eec796d41f9bcf1126bb8e77d2d2816152bc50f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 11:02:34 +0900 Subject: [PATCH 074/123] =?UTF-8?q?refactor:=20null=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../in/koreatech/koin/domain/bus/service/BusService.java | 3 +-- .../koin/domain/bus/util/CityBusOpenApiRequester.java | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 53d5e6d8f..380a803bb 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -5,7 +5,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -63,7 +62,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN return BusRemainTimeResponse.of( busType, remainTimes.stream() - .filter(Objects::nonNull) + .filter(bus -> bus.getRemainTime().getRemainSeconds(clock) != null) .sorted(Comparator.comparing(Bus::getRemainTime)) .toList(), clock diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 04a6d79ea..4ece659ff 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -14,6 +14,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -75,12 +76,10 @@ public List getBusRemainTime(String nodeId) { } private List getCityBusArrivalInfoByCache(String nodeId) { - CityBusCache cityBusCache = cityBusCacheRepository.findById(nodeId).orElse(null); - if (cityBusCache != null) { - return cityBusCache.getBusInfos().stream().map(CityBus::from).toList(); - } + Optional cityBusCache = cityBusCacheRepository.findById(nodeId); - return new ArrayList<>(); + return cityBusCache.map(busCache -> busCache.getBusInfos().stream().map(CityBus::from).toList()) + .orElseGet(ArrayList::new); } private List getCityBusArrivalInfoByOpenApi(String nodeId) { From 5c205a71fe198297ac204339c408fbdb608a466a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 25 Mar 2024 11:57:02 +0900 Subject: [PATCH 075/123] =?UTF-8?q?refactor:=20updated=5Fat=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/util/CityBusOpenApiRequester.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 4ece659ff..af1a0c011 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -11,6 +11,7 @@ import java.net.URL; import java.time.Clock; import java.time.Duration; +import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -93,8 +94,8 @@ private void getAllCityBusArrivalInfoByOpenApi() { .map(this::extractBusArrivalInfo) .toList(); - Version version = versionRepository.getByType(VersionType.CITY); - version.update(Clock.systemDefaultZone()); + Clock updatedClock = Clock.systemDefaultZone(); + LocalDateTime updatedAt = LocalDateTime.now(updatedClock); for (List arrivalInfos : arrivalInfosList) { if (arrivalInfos.isEmpty()) @@ -104,13 +105,13 @@ private void getAllCityBusArrivalInfoByOpenApi() { CityBusCache.create( arrivalInfos.get(0).nodeid(), arrivalInfos.stream() - .map(busArrivalInfo -> BusInfoCache.from(busArrivalInfo, version.getUpdatedAt())) + .map(busArrivalInfo -> BusInfoCache.from(busArrivalInfo, updatedAt)) .toList() ) ); } - // versionRepository.getByType(VersionType.CITY).update(Clock.systemDefaultZone()); + versionRepository.getByType(VersionType.CITY).update(updatedClock); } private String getOpenApiResponse(String nodeId) { From da80793cfe2b2f730b3ae4ddc42baa912b080622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:40:46 +0900 Subject: [PATCH 076/123] =?UTF-8?q?refactor:=20.log().all()=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- src/test/java/in/koreatech/koin/acceptance/BusApiTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index ac5be6927..938ddded6 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -161,7 +161,6 @@ void getNextCityBusRemainTime() { .param("arrival", arrival.name()) .get("/bus") .then() - .log().all() .statusCode(HttpStatus.OK.value()) .extract(); From a6d2db6d1efdbfc4450b81cd0f1d684f41d1b6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:43:39 +0900 Subject: [PATCH 077/123] =?UTF-8?q?refactor:=20switch=EB=AC=B8=20if?= =?UTF-8?q?=EB=AC=B8=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/service/BusService.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 380a803bb..f8bc9c888 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -39,24 +39,24 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN validateBusCourse(depart, arrival); List remainTimes = new ArrayList<>(); - switch (busType) { - case CITY, EXPRESS -> remainTimes = busOpenApiRequesters.get(ApiType.from(busType).getValue()) + if (busType == BusType.CITY || busType == BusType.EXPRESS) { + remainTimes = busOpenApiRequesters.get(ApiType.from(busType).getValue()) .getBusRemainTime(depart.getNodeId(direction)); - case SHUTTLE, COMMUTING -> { - List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); - remainTimes = busCourses.stream() - .map(BusCourse::getRoutes) - .flatMap(routes -> - routes.stream() - .filter(route -> route.isRunning(clock)) - .filter(route -> route.isCorrectRoute(depart, arrival, clock)) - .map(route -> route.getRemainTime(depart)) - .map(Bus::from) - ) - .distinct() - .sorted() - .toList(); - } + } + else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { + List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); + remainTimes = busCourses.stream() + .map(BusCourse::getRoutes) + .flatMap(routes -> + routes.stream() + .filter(route -> route.isRunning(clock)) + .filter(route -> route.isCorrectRoute(depart, arrival, clock)) + .map(route -> route.getRemainTime(depart)) + .map(Bus::from) + ) + .distinct() + .sorted() + .toList(); } return BusRemainTimeResponse.of( From 9c0df9a041e629a27a29e60a5f94d3311179de82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:47:26 +0900 Subject: [PATCH 078/123] =?UTF-8?q?refactor:=20=EB=B3=B5=EB=B6=99=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/exception/ApiTypeNotFoundException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java b/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java index 0ea5945a3..1a4c6c65c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java +++ b/src/main/java/in/koreatech/koin/domain/bus/exception/ApiTypeNotFoundException.java @@ -10,8 +10,8 @@ public ApiTypeNotFoundException(String message) { super(message); } - public static in.koreatech.koin.domain.version.exception.VersionTypeNotFoundException withDetail(String detail) { + public static ApiTypeNotFoundException withDetail(String detail) { String message = String.format("%s %s", DEFAULT_MESSAGE, detail); - return new in.koreatech.koin.domain.version.exception.VersionTypeNotFoundException(message); + return new ApiTypeNotFoundException(message); } } From 06fae299bcbefded4da65c83052aba2bb76cb20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:48:42 +0900 Subject: [PATCH 079/123] =?UTF-8?q?style:=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java index 0833e8cb5..9b324dd3a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java @@ -9,7 +9,9 @@ @Getter @AllArgsConstructor public enum ApiType { - CITY("cityBusOpenApiRequester"), EXPRESS("intercityBusOpenApiRequester"); + CITY("cityBusOpenApiRequester"), + EXPRESS("intercityBusOpenApiRequester") + ; private final String value; From 51fb0c7084f874d0210fd4cfb7c27e2c41c67ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:50:15 +0900 Subject: [PATCH 080/123] =?UTF-8?q?rename:=20BusInfoCache=20->=20BusCache?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../domain/bus/model/{BusInfoCache.java => BusCache.java} | 6 +++--- .../java/in/koreatech/koin/domain/bus/model/CityBus.java | 2 +- .../in/koreatech/koin/domain/bus/model/CityBusCache.java | 6 +++--- .../koin/domain/bus/util/CityBusOpenApiRequester.java | 4 ++-- src/test/java/in/koreatech/koin/acceptance/BusApiTest.java | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/{BusInfoCache.java => BusCache.java} (82%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java similarity index 82% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java rename to src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java index 3029d3d2c..88172f7ed 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusInfoCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java @@ -3,7 +3,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; -public record BusInfoCache( +public record BusCache( Long busNumber, LocalTime remainTime ) { @@ -15,8 +15,8 @@ public record BusInfoCache( * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로 * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌 * */ - public static BusInfoCache from(CityBusArrivalInfo busArrivalInfo, LocalDateTime updatedAt) { - return new BusInfoCache( + public static BusCache from(CityBusArrivalInfo busArrivalInfo, LocalDateTime updatedAt) { + return new BusCache( busArrivalInfo.routeno(), updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() ); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java index 04cc84786..7b3fc5ab4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java @@ -13,7 +13,7 @@ private CityBus(Long busNumber, BusRemainTime busRemainTime) { this.busNumber = busNumber; } - public static CityBus from(BusInfoCache busInfo) { + public static CityBus from(BusCache busInfo) { return CityBus.builder() .busNumber(busInfo.busNumber()) .busRemainTime( diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java index 3899d8e3f..12ee6abc6 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java @@ -20,19 +20,19 @@ public class CityBusCache { @Id private String id; - private final List busInfos = new ArrayList<>(); + private final List busInfos = new ArrayList<>(); @TimeToLive(unit = TimeUnit.MINUTES) private final Long expiration; @Builder - private CityBusCache(String id, List busInfos, Long expiration) { + private CityBusCache(String id, List busInfos, Long expiration) { this.id = id; this.busInfos.addAll(busInfos); this.expiration = expiration; } - public static CityBusCache create(String nodeId, List busInfos) { + public static CityBusCache create(String nodeId, List busInfos) { return CityBusCache.builder() .id(nodeId) .busInfos(busInfos) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index af1a0c011..06098634a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -29,7 +29,7 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; -import in.koreatech.koin.domain.bus.model.BusInfoCache; +import in.koreatech.koin.domain.bus.model.BusCache; import in.koreatech.koin.domain.bus.model.BusStationNode; import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; @@ -105,7 +105,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { CityBusCache.create( arrivalInfos.get(0).nodeid(), arrivalInfos.stream() - .map(busArrivalInfo -> BusInfoCache.from(busArrivalInfo, updatedAt)) + .map(busArrivalInfo -> BusCache.from(busArrivalInfo, updatedAt)) .toList() ) ); diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 938ddded6..13f74f41c 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -16,7 +16,7 @@ import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.domain.bus.model.BusCourse; import in.koreatech.koin.domain.bus.model.BusDirection; -import in.koreatech.koin.domain.bus.model.BusInfoCache; +import in.koreatech.koin.domain.bus.model.BusCache; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.BusStation; import in.koreatech.koin.domain.bus.model.BusType; @@ -141,7 +141,7 @@ void getNextCityBusRemainTime() { cityBusCacheRepository.save( CityBusCache.create( depart.getNodeId(direction), - List.of(BusInfoCache.from( + List.of(BusCache.from( CityBusArrivalInfo.builder() .routeno(busNumber) .arrtime(remainTime) From bd2227465bd5484a4f4adbfca2cc277819433ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Wed, 27 Mar 2024 21:51:51 +0900 Subject: [PATCH 081/123] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=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 Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../domain/bus/model/CityBusArrivalInfo.java | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java index 8db6cca11..995c7337b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -2,26 +2,15 @@ import lombok.Builder; -/** - * "arrprevstationcnt": 5, - * "arrtime": 222, - * "nodeid": "CAB285000405", - * "nodenm": "코리아텍", - * "routeid": "CAB285000147", - * "routeno": 402, - * "routetp": "일반버스", - * "vehicletp": "일반차량" - */ - public record CityBusArrivalInfo( - Long arrprevstationcnt, // 남은 정거장 개수 - Long arrtime, // 도착까지 남은 시간 [초] - String nodeid, // 정류소 id - String nodenm, // 정류소명 - String routeid, // 노선 id - Long routeno, // 버스 번호 - String routetp, // 노선 유형 - String vehicletp // 차량 유형 (저상버스) + Long arrprevstationcnt, // 남은 정거장 개수, 5 + Long arrtime, // 도착까지 남은 시간 [초], 222 + String nodeid, // 정류소 id, "CAB285000405" + String nodenm, // 정류소명, "코리아텍" + String routeid, // 노선 id, "CAB285000147" + Long routeno, // 버스 번호, 402 + String routetp, // 노선 유형, "일반 버스" + String vehicletp // 차량 유형 (저상버스), "일반차량" ) { public static CityBusArrivalInfo getEmpty(String nodeid) { return builder() From bace7e6064df75c0875fe3409ca75a1f163c5006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Thu, 28 Mar 2024 10:46:48 +0900 Subject: [PATCH 082/123] =?UTF-8?q?style:=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/domain/bus/model/ApiType.java | 6 ++-- .../domain/bus/model/CityBusArrivalInfo.java | 10 +------ .../repository/CityBusCacheRepository.java | 1 + .../bus/util/CityBusOpenApiRequester.java | 28 +++++++++++++------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java index 9b324dd3a..a20657dbc 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java @@ -3,11 +3,9 @@ import java.util.Arrays; import in.koreatech.koin.domain.bus.exception.ApiTypeNotFoundException; -import lombok.AllArgsConstructor; import lombok.Getter; @Getter -@AllArgsConstructor public enum ApiType { CITY("cityBusOpenApiRequester"), EXPRESS("intercityBusOpenApiRequester") @@ -15,6 +13,10 @@ public enum ApiType { private final String value; + ApiType(String bean) { + this.value = bean; + } + public static ApiType from(BusType value) { return Arrays.stream(values()) .filter(apiType -> apiType.toString().equalsIgnoreCase(value.toString())) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java index 995c7337b..0c8fa4fd8 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java @@ -2,6 +2,7 @@ import lombok.Builder; +@Builder public record CityBusArrivalInfo( Long arrprevstationcnt, // 남은 정거장 개수, 5 Long arrtime, // 도착까지 남은 시간 [초], 222 @@ -18,13 +19,4 @@ public static CityBusArrivalInfo getEmpty(String nodeid) { .arrtime(-1L) .build(); } - - @Builder - public CityBusArrivalInfo { - } - /* - @Override - public int compareTo(CityBusArrivalInfo o) { - return this.arrtime - o.arrtime; - }*/ } diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java index f5e3af728..4b9b4fe04 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java @@ -13,6 +13,7 @@ public interface CityBusCacheRepository extends Repository CityBusCache save(CityBusCache cityBusCache); List findAll(); + Optional findById(String nodeId); default CityBusCache getById(String nodeId) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 06098634a..2708172b7 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -38,14 +38,12 @@ import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; -import lombok.RequiredArgsConstructor; /** * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098530 */ @Component -@RequiredArgsConstructor @Transactional(readOnly = true) public class CityBusOpenApiRequester extends BusOpenApiRequester { @@ -53,21 +51,34 @@ public class CityBusOpenApiRequester extends BusOpenApiRequester { private static final String CHEONAN_CITY_CODE = "34010"; @Value("${OPEN_API_KEY}") - private String OPEN_API_KEY; + private final String openApiKey; private final Gson gson; + private final Clock clock; + private final VersionRepository versionRepository; private final CityBusCacheRepository cityBusCacheRepository; - private final Clock clock; - private static final Type arrivalInfoType = new TypeToken>() { }.getType(); + public CityBusOpenApiRequester( + @Value("${OPEN_API_KEY}") String openApiKey, + Gson gson, + Clock clock, + VersionRepository versionRepository, + CityBusCacheRepository cityBusCacheRepository + ) { + this.openApiKey = openApiKey; + this.gson = gson; + this.clock = clock; + this.versionRepository = versionRepository; + this.cityBusCacheRepository = cityBusCacheRepository; + } + public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); - Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); if (0 <= duration.toSeconds() && duration.toSeconds() < 60) { @@ -98,8 +109,9 @@ private void getAllCityBusArrivalInfoByOpenApi() { LocalDateTime updatedAt = LocalDateTime.now(updatedClock); for (List arrivalInfos : arrivalInfosList) { - if (arrivalInfos.isEmpty()) + if (arrivalInfos.isEmpty()) { continue; + } cityBusCacheRepository.save( CityBusCache.create( @@ -145,7 +157,7 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE String url = "http://apis.data.go.kr/1613000/ArvlInfoInqireService/getSttnAcctoArvlPrearngeInfoList"; String contentCount = "30"; StringBuilder urlBuilder = new StringBuilder(url); - urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(OPEN_API_KEY, ENCODE_TYPE)); + urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(openApiKey, ENCODE_TYPE)); urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); urlBuilder.append("&" + encode("cityCode", ENCODE_TYPE) + "=" + encode(cityCode, ENCODE_TYPE)); urlBuilder.append("&" + encode("nodeId", ENCODE_TYPE) + "=" + encode(nodeId, ENCODE_TYPE)); From 902ad53f103b4b7c05a42059c533e4fec780238d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Thu, 28 Mar 2024 18:45:49 +0900 Subject: [PATCH 083/123] =?UTF-8?q?refactor:=20.log().all()=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- src/test/java/in/koreatech/koin/acceptance/BusApiTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 13f74f41c..42d144da8 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -91,15 +91,12 @@ void getNextShuttleBusRemainTime() { busRepository.save(busCourse); ExtractableResponse response = RestAssured .given() - .log().all() .when() - .log().all() .param("bus_type", busType.name().toLowerCase()) .param("depart", depart.name()) .param("arrival", arrival.name()) .get("/bus") .then() - .log().all() .statusCode(HttpStatus.OK.value()) .extract(); @@ -153,9 +150,7 @@ void getNextCityBusRemainTime() { ExtractableResponse response = RestAssured .given() - .log().all() .when() - .log().all() .param("bus_type", busType.name().toLowerCase()) .param("depart", depart.name()) .param("arrival", arrival.name()) From 937284eeaac68790ee5600c32c65330d0ae12e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Thu, 28 Mar 2024 19:14:12 +0900 Subject: [PATCH 084/123] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../koin/domain/bus/dto/BusRemainTimeResponse.java | 2 +- .../koreatech/koin/domain/bus/model/CityBus.java | 1 + .../koin/domain/bus/model/{ => enums}/ApiType.java | 2 +- .../domain/bus/model/{ => enums}/BusDirection.java | 2 +- .../domain/bus/model/{ => enums}/BusStation.java | 2 +- .../bus/model/{ => enums}/BusStationNode.java | 6 +++--- .../koin/domain/bus/model/{ => enums}/BusType.java | 2 +- .../domain/bus/model/{ => mongo}/BusCourse.java | 3 ++- .../koin/domain/bus/model/{ => mongo}/Route.java | 4 +++- .../domain/bus/model/{ => redis}/BusCache.java | 5 ++++- .../domain/bus/model/{ => redis}/CityBusCache.java | 2 +- .../koin/domain/bus/repository/BusRepository.java | 2 +- .../bus/repository/CityBusCacheRepository.java | 2 +- .../koin/domain/bus/service/BusService.java | 10 +++++----- .../domain/bus/util/CityBusOpenApiRequester.java | 6 +++--- .../in/koreatech/koin/acceptance/BusApiTest.java | 14 +++++++------- 16 files changed, 36 insertions(+), 29 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => enums}/ApiType.java (92%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => enums}/BusDirection.java (92%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => enums}/BusStation.java (96%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => enums}/BusStationNode.java (82%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => enums}/BusType.java (91%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => mongo}/BusCourse.java (90%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => mongo}/Route.java (94%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => redis}/BusCache.java (83%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => redis}/CityBusCache.java (95%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 505568496..78483d1f5 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import in.koreatech.koin.domain.bus.model.Bus; -import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.enums.BusType; import in.koreatech.koin.domain.bus.model.CityBus; @JsonNaming(SnakeCaseStrategy.class) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java index 7b3fc5ab4..766d8d33d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.model; +import in.koreatech.koin.domain.bus.model.redis.BusCache; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java similarity index 92% rename from src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java index a20657dbc..dbcbe7345 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/ApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.enums; import java.util.Arrays; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusDirection.java similarity index 92% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/BusDirection.java index 00ba4f1b6..03597466b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusDirection.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusDirection.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.enums; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java similarity index 96% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java index 13f1017c4..c82047b15 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.enums; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStationNode.java similarity index 82% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStationNode.java index d6c0e3970..4edb945fe 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusStationNode.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStationNode.java @@ -1,7 +1,7 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.enums; -import static in.koreatech.koin.domain.bus.model.BusDirection.NORTH; -import static in.koreatech.koin.domain.bus.model.BusDirection.SOUTH; +import static in.koreatech.koin.domain.bus.model.enums.BusDirection.NORTH; +import static in.koreatech.koin.domain.bus.model.enums.BusDirection.SOUTH; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java similarity index 91% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusType.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java index b2cecd7aa..3a1d106e9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.enums; import java.util.Arrays; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java similarity index 90% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java rename to src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java index 542861d8a..ab503522c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.mongo; import java.util.ArrayList; import java.util.List; @@ -6,6 +6,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; +import in.koreatech.koin.domain.bus.model.mongo.Route; import jakarta.persistence.Id; import lombok.AccessLevel; import lombok.Builder; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java similarity index 94% rename from src/main/java/in/koreatech/koin/domain/bus/model/Route.java rename to src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java index 6e903dd9c..48aab2069 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.mongo; import java.time.Clock; import java.time.LocalDateTime; @@ -10,6 +10,8 @@ import org.springframework.data.mongodb.core.mapping.Field; import in.koreatech.koin.domain.bus.exception.BusArrivalNodeNotFoundException; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.enums.BusStation; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java similarity index 83% rename from src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java rename to src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java index 88172f7ed..b3cf29bbb 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java @@ -1,8 +1,11 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.redis; import java.time.LocalDateTime; import java.time.LocalTime; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; + public record BusCache( Long busNumber, LocalTime remainTime diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java similarity index 95% rename from src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java rename to src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java index 12ee6abc6..548afe5e6 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.redis; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java index f247054b9..123d6b599 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/BusRepository.java @@ -4,7 +4,7 @@ import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.bus.model.BusCourse; +import in.koreatech.koin.domain.bus.model.mongo.BusCourse; public interface BusRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java index 4b9b4fe04..091829dab 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java @@ -6,7 +6,7 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; -import in.koreatech.koin.domain.bus.model.CityBusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; public interface CityBusCacheRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index f8bc9c888..3075730d4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -11,12 +11,12 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.exception.BusIllegalStationException; -import in.koreatech.koin.domain.bus.model.ApiType; +import in.koreatech.koin.domain.bus.model.enums.ApiType; import in.koreatech.koin.domain.bus.model.Bus; -import in.koreatech.koin.domain.bus.model.BusCourse; -import in.koreatech.koin.domain.bus.model.BusDirection; -import in.koreatech.koin.domain.bus.model.BusStation; -import in.koreatech.koin.domain.bus.model.BusType; +import in.koreatech.koin.domain.bus.model.mongo.BusCourse; +import in.koreatech.koin.domain.bus.model.enums.BusDirection; +import in.koreatech.koin.domain.bus.model.enums.BusStation; +import in.koreatech.koin.domain.bus.model.enums.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.util.BusOpenApiRequester; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 2708172b7..273ff9d22 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -29,11 +29,11 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; -import in.koreatech.koin.domain.bus.model.BusCache; -import in.koreatech.koin.domain.bus.model.BusStationNode; +import in.koreatech.koin.domain.bus.model.redis.BusCache; +import in.koreatech.koin.domain.bus.model.enums.BusStationNode; import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; -import in.koreatech.koin.domain.bus.model.CityBusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 42d144da8..13e114962 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -14,15 +14,15 @@ import org.springframework.http.HttpStatus; import in.koreatech.koin.AcceptanceTest; -import in.koreatech.koin.domain.bus.model.BusCourse; -import in.koreatech.koin.domain.bus.model.BusDirection; -import in.koreatech.koin.domain.bus.model.BusCache; +import in.koreatech.koin.domain.bus.model.mongo.BusCourse; +import in.koreatech.koin.domain.bus.model.enums.BusDirection; +import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.BusStation; -import in.koreatech.koin.domain.bus.model.BusType; +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.CityBusArrivalInfo; -import in.koreatech.koin.domain.bus.model.CityBusCache; -import in.koreatech.koin.domain.bus.model.Route; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.mongo.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; From bdd7c015523db6b0a9028f3bc87023bc31fb3c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Thu, 28 Mar 2024 19:35:29 +0900 Subject: [PATCH 085/123] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98,=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../koin/domain/bus/model/Constant.java | 16 +++++++++++++ .../domain/bus/model/redis/CityBusCache.java | 4 ++-- .../domain/bus/util/BusOpenApiRequester.java | 12 ++++++++++ .../bus/util/CityBusOpenApiRequester.java | 24 +++++++++---------- 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Constant.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java new file mode 100644 index 000000000..69b4654a3 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java @@ -0,0 +1,16 @@ +package in.koreatech.koin.domain.bus.model; + +public final class Constant { + private Constant() { + throw new IllegalStateException("Utility class"); + } + + public static final long CACHE_EXPIRE_MINUTE = 1L; + + public static final String CODE_SERVICE_DISPOSE = "12"; + public static final String CODE_SERVICE_ACCESS_DENIED = "20"; + public static final String CODE_SERVICE_REQUEST_OVER = "22"; + public static final String CODE_KEY_UNREGISTERED = "30"; + public static final String CODE_SERVICE_KEY_EXPIRED = "31"; + public static final String CODE_SERVICE_SUCCESS = "00"; +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java index 548afe5e6..bfbc6eca7 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.bus.model.redis; +import static in.koreatech.koin.domain.bus.model.Constant.CACHE_EXPIRE_MINUTE; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -15,8 +17,6 @@ @RedisHash("CityBus") public class CityBusCache { - private static final long CACHE_EXPIRE_MINUTE = 1L; - @Id private String id; diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java index dc0c5acfa..fb195144a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java @@ -1,11 +1,23 @@ package in.koreatech.koin.domain.bus.util; +import static in.koreatech.koin.domain.bus.model.Constant.CACHE_EXPIRE_MINUTE; + +import java.time.Clock; +import java.time.Duration; +import java.time.LocalTime; import java.util.List; import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.version.model.Version; public abstract class BusOpenApiRequester { public abstract List getBusRemainTime(String nodeId); + public boolean isCacheExpired(Version version, Clock clock) { + Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); + + return 0 <= duration.toSeconds() && duration.toSeconds() < CACHE_EXPIRE_MINUTE * 60; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java index 273ff9d22..11b3cbc2e 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.util; +import static in.koreatech.koin.domain.bus.model.Constant.*; import static java.net.URLEncoder.encode; import java.io.BufferedReader; @@ -10,9 +11,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.time.Clock; -import java.time.Duration; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -29,10 +28,11 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; -import in.koreatech.koin.domain.bus.model.redis.BusCache; -import in.koreatech.koin.domain.bus.model.enums.BusStationNode; import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; +import in.koreatech.koin.domain.bus.model.Constant; +import in.koreatech.koin.domain.bus.model.enums.BusStationNode; +import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; @@ -79,11 +79,11 @@ public CityBusOpenApiRequester( public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); - Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - if (0 <= duration.toSeconds() && duration.toSeconds() < 60) { + if (isCacheExpired(version, clock)) { return getCityBusArrivalInfoByCache(nodeId); } + return getCityBusArrivalInfoByOpenApi(nodeId); } @@ -197,22 +197,22 @@ private void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); String errorMessage = ""; - if ("12".equals(resultCode)) { + if (CODE_SERVICE_DISPOSE.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; } - if ("20".equals(resultCode)) { + if (CODE_SERVICE_ACCESS_DENIED.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; } - if ("22".equals(resultCode)) { + if (CODE_SERVICE_REQUEST_OVER.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; } - if ("30".equals(resultCode)) { + if (CODE_KEY_UNREGISTERED.equals(resultCode)) { errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; } - if ("31".equals(resultCode)) { + if (CODE_SERVICE_KEY_EXPIRED.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; } - if (!"00".equals(resultCode)) { + if (!CODE_SERVICE_SUCCESS.equals(resultCode)) { String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } From 7d47fe4e8ab6086bf98d9cc3cf4d43750f8bcc6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Thu, 28 Mar 2024 19:47:22 +0900 Subject: [PATCH 086/123] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- ...usArrivalInfo.java => CityBusArrival.java} | 4 +-- .../enums/{ApiType.java => BusApiType.java} | 8 +++--- .../koin/domain/bus/model/redis/BusCache.java | 4 +-- .../koin/domain/bus/service/BusService.java | 8 +++--- ...piRequester.java => BusOpenApiClient.java} | 5 ++-- ...quester.java => CityBusOpenApiClient.java} | 28 ++++++++----------- ...er.java => IntercityBusOpenApiClient.java} | 2 +- .../koreatech/koin/acceptance/BusApiTest.java | 4 +-- 8 files changed, 28 insertions(+), 35 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/{CityBusArrivalInfo.java => CityBusArrival.java} (86%) rename src/main/java/in/koreatech/koin/domain/bus/model/enums/{ApiType.java => BusApiType.java} (71%) rename src/main/java/in/koreatech/koin/domain/bus/util/{BusOpenApiRequester.java => BusOpenApiClient.java} (72%) rename src/main/java/in/koreatech/koin/domain/bus/util/{CityBusOpenApiRequester.java => CityBusOpenApiClient.java} (89%) rename src/main/java/in/koreatech/koin/domain/bus/util/{IntercityBusOpenApiRequester.java => IntercityBusOpenApiClient.java} (89%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java similarity index 86% rename from src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java rename to src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java index 0c8fa4fd8..7fb5e39ea 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrivalInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java @@ -3,7 +3,7 @@ import lombok.Builder; @Builder -public record CityBusArrivalInfo( +public record CityBusArrival( Long arrprevstationcnt, // 남은 정거장 개수, 5 Long arrtime, // 도착까지 남은 시간 [초], 222 String nodeid, // 정류소 id, "CAB285000405" @@ -13,7 +13,7 @@ public record CityBusArrivalInfo( String routetp, // 노선 유형, "일반 버스" String vehicletp // 차량 유형 (저상버스), "일반차량" ) { - public static CityBusArrivalInfo getEmpty(String nodeid) { + public static CityBusArrival getEmpty(String nodeid) { return builder() .nodeid(nodeid) .arrtime(-1L) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java similarity index 71% rename from src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java rename to src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java index dbcbe7345..1affd489b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/ApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java @@ -6,20 +6,20 @@ import lombok.Getter; @Getter -public enum ApiType { +public enum BusApiType { CITY("cityBusOpenApiRequester"), EXPRESS("intercityBusOpenApiRequester") ; private final String value; - ApiType(String bean) { + BusApiType(String bean) { this.value = bean; } - public static ApiType from(BusType value) { + public static BusApiType from(BusType value) { return Arrays.stream(values()) - .filter(apiType -> apiType.toString().equalsIgnoreCase(value.toString())) + .filter(busApiType -> busApiType.toString().equalsIgnoreCase(value.toString())) .findAny() .orElseThrow(() -> ApiTypeNotFoundException.withDetail("apiType: " + value)); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java index b3cf29bbb..a48b59164 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java @@ -4,7 +4,7 @@ import java.time.LocalTime; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; +import in.koreatech.koin.domain.bus.model.CityBusArrival; public record BusCache( Long busNumber, @@ -18,7 +18,7 @@ public record BusCache( * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로 * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌 * */ - public static BusCache from(CityBusArrivalInfo busArrivalInfo, LocalDateTime updatedAt) { + public static BusCache from(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { return new BusCache( busArrivalInfo.routeno(), updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 3075730d4..ff2bc453c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -11,14 +11,14 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.exception.BusIllegalStationException; -import in.koreatech.koin.domain.bus.model.enums.ApiType; +import in.koreatech.koin.domain.bus.model.enums.BusApiType; import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.enums.BusDirection; import in.koreatech.koin.domain.bus.model.enums.BusStation; import in.koreatech.koin.domain.bus.model.enums.BusType; import in.koreatech.koin.domain.bus.repository.BusRepository; -import in.koreatech.koin.domain.bus.util.BusOpenApiRequester; +import in.koreatech.koin.domain.bus.util.BusOpenApiClient; import lombok.RequiredArgsConstructor; @Service @@ -28,7 +28,7 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - private final Map> busOpenApiRequesters; + private final Map> busOpenApiRequesters; @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { @@ -40,7 +40,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN List remainTimes = new ArrayList<>(); if (busType == BusType.CITY || busType == BusType.EXPRESS) { - remainTimes = busOpenApiRequesters.get(ApiType.from(busType).getValue()) + remainTimes = busOpenApiRequesters.get(BusApiType.from(busType).getValue()) .getBusRemainTime(depart.getNodeId(direction)); } else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java similarity index 72% rename from src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java rename to src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java index fb195144a..170ecfd65 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java @@ -8,16 +8,15 @@ import java.util.List; import in.koreatech.koin.domain.bus.model.Bus; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.version.model.Version; -public abstract class BusOpenApiRequester { +public abstract class BusOpenApiClient { public abstract List getBusRemainTime(String nodeId); public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return 0 <= duration.toSeconds() && duration.toSeconds() < CACHE_EXPIRE_MINUTE * 60; + return duration.toSeconds() < 0 || CACHE_EXPIRE_MINUTE * 60 < duration.toSeconds(); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java similarity index 89% rename from src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java rename to src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 11b3cbc2e..673bd49c8 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -29,8 +29,7 @@ import in.koreatech.koin.domain.bus.exception.BusOpenApiException; import in.koreatech.koin.domain.bus.model.CityBus; -import in.koreatech.koin.domain.bus.model.CityBusArrivalInfo; -import in.koreatech.koin.domain.bus.model.Constant; +import in.koreatech.koin.domain.bus.model.CityBusArrival; import in.koreatech.koin.domain.bus.model.enums.BusStationNode; import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; @@ -45,7 +44,7 @@ */ @Component @Transactional(readOnly = true) -public class CityBusOpenApiRequester extends BusOpenApiRequester { +public class CityBusOpenApiClient extends BusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; @@ -60,10 +59,10 @@ public class CityBusOpenApiRequester extends BusOpenApiRequester { private final VersionRepository versionRepository; private final CityBusCacheRepository cityBusCacheRepository; - private static final Type arrivalInfoType = new TypeToken>() { + private static final Type arrivalInfoType = new TypeToken>() { }.getType(); - public CityBusOpenApiRequester( + public CityBusOpenApiClient( @Value("${OPEN_API_KEY}") String openApiKey, Gson gson, Clock clock, @@ -81,10 +80,10 @@ public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); if (isCacheExpired(version, clock)) { - return getCityBusArrivalInfoByCache(nodeId); + getAllCityBusArrivalInfoByOpenApi(); } - return getCityBusArrivalInfoByOpenApi(nodeId); + return getCityBusArrivalInfoByCache(nodeId); } private List getCityBusArrivalInfoByCache(String nodeId) { @@ -94,13 +93,8 @@ private List getCityBusArrivalInfoByCache(String nodeId) { .orElseGet(ArrayList::new); } - private List getCityBusArrivalInfoByOpenApi(String nodeId) { - getAllCityBusArrivalInfoByOpenApi(); - return getCityBusArrivalInfoByCache(nodeId); - } - private void getAllCityBusArrivalInfoByOpenApi() { - List> arrivalInfosList = BusStationNode.getNodeIds().stream() + List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) .toList(); @@ -108,7 +102,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { Clock updatedClock = Clock.systemDefaultZone(); LocalDateTime updatedAt = LocalDateTime.now(updatedClock); - for (List arrivalInfos : arrivalInfosList) { + for (List arrivalInfos : arrivalInfosList) { if (arrivalInfos.isEmpty()) { continue; } @@ -165,8 +159,8 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE return urlBuilder.toString(); } - private List extractBusArrivalInfo(String jsonResponse) { - List result = new ArrayList<>(); + private List extractBusArrivalInfo(String jsonResponse) { + List result = new ArrayList<>(); try { JsonObject response = JsonParser.parseString(jsonResponse) @@ -185,7 +179,7 @@ private List extractBusArrivalInfo(String jsonResponse) { return gson.fromJson(item, arrivalInfoType); } if (item.isJsonObject()) { - result.add(gson.fromJson(item, CityBusArrivalInfo.class)); + result.add(gson.fromJson(item, CityBusArrival.class)); } return result; } catch (JsonSyntaxException e) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java similarity index 89% rename from src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java rename to src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java index 8f178ebfd..5a36e4c41 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiRequester.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java @@ -16,7 +16,7 @@ @Component @RequiredArgsConstructor @Transactional(readOnly = true) -public class IntercityBusOpenApiRequester extends BusOpenApiRequester { +public class IntercityBusOpenApiClient extends BusOpenApiClient { @Override public List getBusRemainTime(String nodeId) { diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 13e114962..ba56e0f05 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -20,7 +20,7 @@ import in.koreatech.koin.domain.bus.model.BusRemainTime; 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.CityBusArrivalInfo; +import in.koreatech.koin.domain.bus.model.CityBusArrival; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.model.mongo.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; @@ -139,7 +139,7 @@ void getNextCityBusRemainTime() { CityBusCache.create( depart.getNodeId(direction), List.of(BusCache.from( - CityBusArrivalInfo.builder() + CityBusArrival.builder() .routeno(busNumber) .arrtime(remainTime) .build(), From e34a799d3852e61147e8aec3e0e4375e9257797c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 29 Mar 2024 00:02:30 +0900 Subject: [PATCH 087/123] =?UTF-8?q?refactor:=20Bus.java=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EC=99=80=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../domain/bus/dto/BusRemainTimeResponse.java | 14 +++--- .../koreatech/koin/domain/bus/model/Bus.java | 16 ------- .../koin/domain/bus/model/BusRemainTime.java | 12 +++-- .../koin/domain/bus/model/CityBus.java | 27 ----------- .../domain/bus/model/CityBusRemainTime.java | 47 +++++++++++++++++++ .../domain/bus/model/enums/BusApiType.java | 4 +- .../koin/domain/bus/service/BusService.java | 11 ++--- .../domain/bus/util/BusOpenApiClient.java | 6 +-- .../domain/bus/util/CityBusOpenApiClient.java | 10 ++-- .../bus/util/IntercityBusOpenApiClient.java | 6 +-- .../version/repository/VersionRepository.java | 3 +- 11 files changed, 81 insertions(+), 75 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Bus.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 78483d1f5..e5a90e6f4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusType; -import in.koreatech.koin.domain.bus.model.CityBus; +import in.koreatech.koin.domain.bus.model.CityBusRemainTime; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -18,7 +18,7 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { + public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.name().toLowerCase(), InnerBusResponse.of(remainTimes, 0, clock), @@ -32,13 +32,13 @@ private record InnerBusResponse( Long remainTime ) { - public static InnerBusResponse of(List remainTimes, int index, Clock clock) { + public static InnerBusResponse of(List remainTimes, int index, Clock clock) { if (index < remainTimes.size()) { Long busNumber = null; - Long remainTime = remainTimes.get(index).getRemainTime().getRemainSeconds(clock); + Long remainTime = remainTimes.get(index).getRemainSeconds(clock); - if (remainTime != null && remainTimes.get(index) instanceof CityBus cityBus) { - busNumber = cityBus.getBusNumber(); + if (remainTime != null && remainTimes.get(index) instanceof CityBusRemainTime cityBusRemainTime) { + busNumber = cityBusRemainTime.getBusNumber(); } return new InnerBusResponse(busNumber, remainTime); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java b/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java deleted file mode 100644 index c55e6f947..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Bus.java +++ /dev/null @@ -1,16 +0,0 @@ -package in.koreatech.koin.domain.bus.model; - -import lombok.Getter; - -@Getter -public class Bus { - BusRemainTime remainTime; - - public Bus(BusRemainTime remainTime) { - this.remainTime = remainTime; - } - - public static Bus from(BusRemainTime busRemainTime) { - return new Bus(busRemainTime); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index c154a0cd3..28ceab955 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -7,8 +7,11 @@ import java.util.Objects; import in.koreatech.koin.domain.bus.exception.BusIllegalArrivalTime; -import lombok.Builder; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +@Getter +@SuperBuilder public class BusRemainTime implements Comparable { private final LocalTime busArrivalTime; @@ -30,9 +33,9 @@ public static BusRemainTime from(String arrivalTime) { .build(); } - public static BusRemainTime from(Long remainTime, Clock clock) { + public static BusRemainTime from(Long remainTime, LocalTime updatedAt) { return builder() - .busArrivalTime(LocalTime.now(clock).plusSeconds(remainTime)) + .busArrivalTime(updatedAt.plusSeconds(remainTime)) .build(); } @@ -45,8 +48,7 @@ private static LocalTime toLocalTime(String arrivalTime) { } } - @Builder - private BusRemainTime(LocalTime busArrivalTime) { + public BusRemainTime(LocalTime busArrivalTime) { this.busArrivalTime = busArrivalTime; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java deleted file mode 100644 index 766d8d33d..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBus.java +++ /dev/null @@ -1,27 +0,0 @@ -package in.koreatech.koin.domain.bus.model; - -import in.koreatech.koin.domain.bus.model.redis.BusCache; -import lombok.Builder; -import lombok.Getter; - -@Getter -public class CityBus extends Bus { - private final Long busNumber; - - @Builder - private CityBus(Long busNumber, BusRemainTime busRemainTime) { - super(busRemainTime); - this.busNumber = busNumber; - } - - public static CityBus from(BusCache busInfo) { - return CityBus.builder() - .busNumber(busInfo.busNumber()) - .busRemainTime( - BusRemainTime.builder() - .busArrivalTime(busInfo.remainTime()) - .build() - ) - .build(); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java new file mode 100644 index 000000000..21c7dbb28 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java @@ -0,0 +1,47 @@ +package in.koreatech.koin.domain.bus.model; + +import java.time.LocalTime; +import java.util.Objects; + +import in.koreatech.koin.domain.bus.model.redis.BusCache; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +public class CityBusRemainTime extends BusRemainTime { + private final Long busNumber; + + public CityBusRemainTime(Long busNumber, LocalTime busArrivalTime) { + super(busArrivalTime); + this.busNumber = busNumber; + } + + public static CityBusRemainTime from(BusCache busInfo) { + return builder() + .busNumber(busInfo.busNumber()) + .busArrivalTime(busInfo.remainTime()) + .build(); + } + + @Override + public int hashCode() { + return Objects.hash(getBusArrivalTime()); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + CityBusRemainTime that = (CityBusRemainTime)o; + return Objects.equals(getBusArrivalTime(), that.getBusArrivalTime()) + && Objects.equals(busNumber, that.busNumber); + } + + @Override + public int compareTo(BusRemainTime o) { + return getBusArrivalTime().compareTo(o.getBusArrivalTime()); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java index 1affd489b..9f7ce35e3 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java @@ -7,8 +7,8 @@ @Getter public enum BusApiType { - CITY("cityBusOpenApiRequester"), - EXPRESS("intercityBusOpenApiRequester") + CITY("cityBusOpenApiClient"), + EXPRESS("intercityBusOpenApiClient") ; private final String value; diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index ff2bc453c..0a3b5cd11 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -11,8 +11,8 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.exception.BusIllegalStationException; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusApiType; -import in.koreatech.koin.domain.bus.model.Bus; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.enums.BusDirection; import in.koreatech.koin.domain.bus.model.enums.BusStation; @@ -28,7 +28,7 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - private final Map> busOpenApiRequesters; + private final Map> busOpenApiRequesters; @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { @@ -38,7 +38,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN BusDirection direction = BusStation.getDirection(depart, arrival); validateBusCourse(depart, arrival); - List remainTimes = new ArrayList<>(); + List remainTimes = new ArrayList<>(); if (busType == BusType.CITY || busType == BusType.EXPRESS) { remainTimes = busOpenApiRequesters.get(BusApiType.from(busType).getValue()) .getBusRemainTime(depart.getNodeId(direction)); @@ -52,7 +52,6 @@ else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { .filter(route -> route.isRunning(clock)) .filter(route -> route.isCorrectRoute(depart, arrival, clock)) .map(route -> route.getRemainTime(depart)) - .map(Bus::from) ) .distinct() .sorted() @@ -62,8 +61,8 @@ else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { return BusRemainTimeResponse.of( busType, remainTimes.stream() - .filter(bus -> bus.getRemainTime().getRemainSeconds(clock) != null) - .sorted(Comparator.comparing(Bus::getRemainTime)) + .filter(bus -> bus.getRemainSeconds(clock) != null) + .sorted(Comparator.naturalOrder()) .toList(), clock ); diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java index 170ecfd65..78587bfe9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java @@ -7,16 +7,16 @@ import java.time.LocalTime; import java.util.List; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.version.model.Version; -public abstract class BusOpenApiClient { +public abstract class BusOpenApiClient { public abstract List getBusRemainTime(String nodeId); public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || CACHE_EXPIRE_MINUTE * 60 < duration.toSeconds(); + return duration.toSeconds() < 0 || CACHE_EXPIRE_MINUTE * 60 <= duration.toSeconds(); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 673bd49c8..613e9cf1d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -28,8 +28,8 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.exception.BusOpenApiException; -import in.koreatech.koin.domain.bus.model.CityBus; import in.koreatech.koin.domain.bus.model.CityBusArrival; +import in.koreatech.koin.domain.bus.model.CityBusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusStationNode; import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; @@ -44,7 +44,7 @@ */ @Component @Transactional(readOnly = true) -public class CityBusOpenApiClient extends BusOpenApiClient { +public class CityBusOpenApiClient extends BusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; @@ -76,7 +76,7 @@ public CityBusOpenApiClient( this.cityBusCacheRepository = cityBusCacheRepository; } - public List getBusRemainTime(String nodeId) { + public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); if (isCacheExpired(version, clock)) { @@ -86,10 +86,10 @@ public List getBusRemainTime(String nodeId) { return getCityBusArrivalInfoByCache(nodeId); } - private List getCityBusArrivalInfoByCache(String nodeId) { + private List getCityBusArrivalInfoByCache(String nodeId) { Optional cityBusCache = cityBusCacheRepository.findById(nodeId); - return cityBusCache.map(busCache -> busCache.getBusInfos().stream().map(CityBus::from).toList()) + return cityBusCache.map(busCache -> busCache.getBusInfos().stream().map(CityBusRemainTime::from).toList()) .orElseGet(ArrayList::new); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java index 5a36e4c41..6674299fe 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import in.koreatech.koin.domain.bus.model.Bus; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import lombok.RequiredArgsConstructor; /** @@ -16,10 +16,10 @@ @Component @RequiredArgsConstructor @Transactional(readOnly = true) -public class IntercityBusOpenApiClient extends BusOpenApiClient { +public class IntercityBusOpenApiClient extends BusOpenApiClient { @Override - public List getBusRemainTime(String nodeId) { + public List getBusRemainTime(String nodeId) { return new ArrayList<>(); } } diff --git a/src/main/java/in/koreatech/koin/domain/version/repository/VersionRepository.java b/src/main/java/in/koreatech/koin/domain/version/repository/VersionRepository.java index e8818bb28..58a60f344 100644 --- a/src/main/java/in/koreatech/koin/domain/version/repository/VersionRepository.java +++ b/src/main/java/in/koreatech/koin/domain/version/repository/VersionRepository.java @@ -15,6 +15,7 @@ public interface VersionRepository extends Repository { Optional findByType(String type); default Version getByType(VersionType type) { - return this.findByType(type.getValue()).orElseThrow(() -> VersionTypeNotFoundException.withDetail("versionType: " + type)); + return this.findByType(type.getValue()) + .orElseThrow(() -> VersionTypeNotFoundException.withDetail("versionType: " + type)); } } From f62ab3760016b0844562cfd9c0e430546c7320fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 29 Mar 2024 00:03:53 +0900 Subject: [PATCH 088/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20-=20Redis?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../koin/global/config/JpaConfiguration.java | 2 + .../in/koreatech/koin/AcceptanceTest.java | 3 +- .../koreatech/koin/acceptance/BusApiTest.java | 44 +++++++++++++------ .../koin/config/TestJpaConfiguration.java | 19 ++++++++ 4 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java diff --git a/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java b/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java index aa1acd063..46ebc0cba 100644 --- a/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java +++ b/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java @@ -1,10 +1,12 @@ package in.koreatech.koin.global.config; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing +@Profile("!test") public class JpaConfiguration { } diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index 43299fdfd..4186bb3dc 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -18,12 +18,13 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.utility.DockerImageName; +import in.koreatech.koin.config.TestJpaConfiguration; import in.koreatech.koin.domain.owner.model.OwnerEventListener; import in.koreatech.koin.support.DBInitializer; import io.restassured.RestAssured; @SpringBootTest(webEnvironment = RANDOM_PORT) -@Import(DBInitializer.class) +@Import({DBInitializer.class, TestJpaConfiguration.class}) @ActiveProfiles("test") public abstract class AcceptanceTest { diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index ba56e0f05..ea7aeff11 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -4,6 +4,7 @@ import static org.mockito.Mockito.when; import java.time.Clock; +import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.List; @@ -12,17 +13,19 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.support.TransactionTemplate; import in.koreatech.koin.AcceptanceTest; -import in.koreatech.koin.domain.bus.model.mongo.BusCourse; -import in.koreatech.koin.domain.bus.model.enums.BusDirection; -import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.CityBusArrival; +import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.CityBusArrival; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.mongo.Route; +import in.koreatech.koin.domain.bus.model.redis.BusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; @@ -42,6 +45,9 @@ class BusApiTest extends AcceptanceTest { @Autowired private CityBusCacheRepository cityBusCacheRepository; + @Autowired + private TransactionTemplate transactionTemplate; + @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { @@ -114,18 +120,18 @@ void getNextShuttleBusRemainTime() { } @Test - @DisplayName("다음 시내버스까지 남은 시간을 조회한다.") + @DisplayName("다음 시내버스까지 남은 시간을 조회한다. - Redis") void getNextCityBusRemainTime() { - final ZonedDateTime now = ZonedDateTime.now(); final long remainTime = 600L; final long busNumber = 400; - when(clock.instant()).thenReturn(now.toInstant()); + when(clock.instant()).thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); BusType busType = BusType.from("city"); - BusStation depart = BusStation.from("koreatech"); - BusStation arrival = BusStation.from("terminal"); + BusStation depart = BusStation.from("terminal"); + BusStation arrival = BusStation.from("koreatech"); BusDirection direction = BusStation.getDirection(depart, arrival); Version version = versionRepository.save( @@ -135,6 +141,9 @@ void getNextCityBusRemainTime() { .build() ); + ReflectionTestUtils.setField(version, "updatedAt", LocalDateTime.of(2024, 2, 21, 18, 0, 0, 0)); + versionRepository.save(version); + cityBusCacheRepository.save( CityBusCache.create( depart.getNodeId(direction), @@ -164,11 +173,18 @@ void getNextCityBusRemainTime() { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber); - softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( - BusRemainTime.from(remainTime, clock).getRemainSeconds(clock)); - softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull(); - softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull(); + softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time")) + .isEqualTo( + BusRemainTime.from(remainTime, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + softly.assertThat(response.body().jsonPath().getObject("next_bus.bus_number", Long.class)).isNull(); + softly.assertThat(response.body().jsonPath().getObject("next_bus.remain_time", Long.class)).isNull(); } ); } } + + + +// 터미널 -> 코리아텍 +// 686 +// {"response":{"header":{"resultCode":"00","resultMsg":"NORMAL SERVICE."},"body":{"items":{"item":[{"arrprevstationcnt":5,"arrtime":560,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000222","routeno":700,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":3,"arrtime":263,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000007","routeno":11,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":17,"arrtime":1272,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000105","routeno":200,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":27,"arrtime":1696,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000107","routeno":201,"routetp":"일반버스","vehicletp":"일반차량"}]},"numOfRows":30,"pageNo":1,"totalCount":4}}} diff --git a/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java b/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java new file mode 100644 index 000000000..56c8954f1 --- /dev/null +++ b/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.config; + +import java.time.Clock; +import java.time.Instant; +import java.util.Optional; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@TestConfiguration +@EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider") +public class TestJpaConfiguration { + @Bean(name = "myAuditingDateTimeProvider") + public DateTimeProvider dateTimeProvider(Clock clock) { + return () -> Optional.of(Instant.now(clock)); + } +} From c47ff9d4fa52785dc96284355235e9584c093527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 29 Mar 2024 12:13:20 +0900 Subject: [PATCH 089/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20-=20OpenA?= =?UTF-8?q?pi=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A4=91=EA=B0=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../domain/bus/util/CityBusOpenApiClient.java | 7 +- .../koreatech/koin/acceptance/BusApiTest.java | 126 +++++++++++++++--- 2 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 613e9cf1d..41bd5eedc 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -49,7 +49,6 @@ public class CityBusOpenApiClient extends BusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; - @Value("${OPEN_API_KEY}") private final String openApiKey; private final Gson gson; @@ -120,7 +119,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { versionRepository.getByType(VersionType.CITY).update(updatedClock); } - private String getOpenApiResponse(String nodeId) { + public String getOpenApiResponse(String nodeId) { try { URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); @@ -142,13 +141,13 @@ private String getOpenApiResponse(String nodeId) { input.close(); conn.disconnect(); return response.toString(); - } catch (IOException e) { + } catch (IOException | NullPointerException e) { return null; } } private String getRequestURL(String cityCode, String nodeId) throws UnsupportedEncodingException { - String url = "http://apis.data.go.kr/1613000/ArvlInfoInqireService/getSttnAcctoArvlPrearngeInfoList"; + String url = "https://apis.data.go.kr/1613000/ArvlInfoInqireService/getSttnAcctoArvlPrearngeInfoList"; String contentCount = "30"; StringBuilder urlBuilder = new StringBuilder(url); urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(openApiKey, ENCODE_TYPE)); diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index ea7aeff11..3b227db8e 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -4,17 +4,16 @@ import static org.mockito.Mockito.when; import java.time.Clock; -import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.List; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.transaction.support.TransactionTemplate; import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.domain.bus.model.BusRemainTime; @@ -45,9 +44,6 @@ class BusApiTest extends AcceptanceTest { @Autowired private CityBusCacheRepository cityBusCacheRepository; - @Autowired - private TransactionTemplate transactionTemplate; - @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { @@ -121,14 +117,18 @@ void getNextShuttleBusRemainTime() { @Test @DisplayName("다음 시내버스까지 남은 시간을 조회한다. - Redis") - void getNextCityBusRemainTime() { + void getNextCityBusRemainTimeRedis() { final long remainTime = 600L; final long busNumber = 400; - when(clock.instant()).thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + when(clock.instant()) + .thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant() + ); + BusType busType = BusType.from("city"); BusStation depart = BusStation.from("terminal"); BusStation arrival = BusStation.from("koreatech"); @@ -141,8 +141,11 @@ void getNextCityBusRemainTime() { .build() ); - ReflectionTestUtils.setField(version, "updatedAt", LocalDateTime.of(2024, 2, 21, 18, 0, 0, 0)); - versionRepository.save(version); + when(clock.instant()) + .thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant() + ); cityBusCacheRepository.save( CityBusCache.create( @@ -181,10 +184,103 @@ void getNextCityBusRemainTime() { } ); } -} + @Test + @DisplayName("다음 시내버스까지 남은 시간을 조회한다. - OpenApi") + void getNextCityBusRemainTimeOpenApi() { + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + + when(clock.instant()) + .thenReturn( + ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant() + ); + + BusType busType = BusType.from("city"); + BusStation depart = BusStation.from("terminal"); + BusStation arrival = BusStation.from("koreatech"); + BusDirection direction = BusStation.getDirection(depart, arrival); + + Version version = versionRepository.save( + Version.builder() + .version("20240_1711255839") + .type("city_bus_timetable") + .build() + ); + + when(clock.instant()) + .thenReturn( + ZonedDateTime.parse("2024-02-21 21:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant() + ); + + String json = """ + { + "response": { + "header": { + "resultCode": "00", + "resultMsg": "NORMAL SERVICE." + }, + "body": { + "items": { + "item": [ + { + "arrprevstationcnt": 3, + "arrtime": 600, + "nodeid": "CAB285000686", + "nodenm": "종합터미널", + "routeid": "CAB285000003", + "routeno": 400, + "routetp": "일반버스", + "vehicletp": "저상버스" + }, + { + "arrprevstationcnt": 10, + "arrtime": 800, + "nodeid": "CAB285000686", + "nodenm": "종합터미널", + "routeid": "CAB285000024", + "routeno": 405, + "routetp": "일반버스", + "vehicletp": "일반차량" + } + ] + }, + "numOfRows": 30, + "pageNo": 1, + "totalCount": 2 + } + } + } + """; + // TODO CityBusOpenApiClient.getOpenApiResponse() Mocking + // when(cityBusOpenApiClient.getOpenApiResponse(depart.getNodeId(direction))).thenReturn(json); -// 터미널 -> 코리아텍 -// 686 -// {"response":{"header":{"resultCode":"00","resultMsg":"NORMAL SERVICE."},"body":{"items":{"item":[{"arrprevstationcnt":5,"arrtime":560,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000222","routeno":700,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":3,"arrtime":263,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000007","routeno":11,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":17,"arrtime":1272,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000105","routeno":200,"routetp":"일반버스","vehicletp":"일반차량"},{"arrprevstationcnt":27,"arrtime":1696,"nodeid":"CAB285000686","nodenm":"종합터미널","routeid":"CAB285000107","routeno":201,"routetp":"일반버스","vehicletp":"일반차량"}]},"numOfRows":30,"pageNo":1,"totalCount":4}}} + ExtractableResponse response = RestAssured + .given() + .when() + .param("bus_type", busType.name().toLowerCase()) + .param("depart", depart.name()) + .param("arrival", arrival.name()) + .get("/bus") + .then() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.body().jsonPath().getString("bus_type")) + .isEqualTo(busType.name().toLowerCase()); + softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400); + softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time")) + .isEqualTo( + BusRemainTime.from(600L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405); + softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.remain_time")) + .isEqualTo( + BusRemainTime.from(800L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + } + ); + } +} From 627d3b0494b262fdf39e1ccddcf545958b458cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 29 Mar 2024 12:43:13 +0900 Subject: [PATCH 090/123] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../java/in/koreatech/koin/domain/bus/service/BusService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 0a3b5cd11..37b0b1257 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -28,7 +28,7 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - private final Map> busOpenApiRequesters; + private final Map> busOpenApiClient; @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { @@ -40,7 +40,7 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN List remainTimes = new ArrayList<>(); if (busType == BusType.CITY || busType == BusType.EXPRESS) { - remainTimes = busOpenApiRequesters.get(BusApiType.from(busType).getValue()) + remainTimes = busOpenApiClient.get(BusApiType.from(busType).getValue()) .getBusRemainTime(depart.getNodeId(direction)); } else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { From b8d4c9a9875206d0777f1be0195b5641043b1322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Fri, 29 Mar 2024 21:27:39 +0900 Subject: [PATCH 091/123] =?UTF-8?q?feat:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=82=A8=EC=9D=80=20=EC=8B=9C=EA=B0=84=20-=20OpenA?= =?UTF-8?q?pi=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../domain/bus/util/CityBusOpenApiClient.java | 5 ++--- .../koin/global/domain/BaseEntity.java | 2 +- .../koreatech/koin/acceptance/BusApiTest.java | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 41bd5eedc..0bdebfd46 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -98,8 +98,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { .map(this::extractBusArrivalInfo) .toList(); - Clock updatedClock = Clock.systemDefaultZone(); - LocalDateTime updatedAt = LocalDateTime.now(updatedClock); + LocalDateTime updatedAt = LocalDateTime.now(clock); for (List arrivalInfos : arrivalInfosList) { if (arrivalInfos.isEmpty()) { @@ -116,7 +115,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { ); } - versionRepository.getByType(VersionType.CITY).update(updatedClock); + versionRepository.getByType(VersionType.CITY).update(clock); } public String getOpenApiResponse(String nodeId) { diff --git a/src/main/java/in/koreatech/koin/global/domain/BaseEntity.java b/src/main/java/in/koreatech/koin/global/domain/BaseEntity.java index 222ede643..58c5e082f 100644 --- a/src/main/java/in/koreatech/koin/global/domain/BaseEntity.java +++ b/src/main/java/in/koreatech/koin/global/domain/BaseEntity.java @@ -24,6 +24,6 @@ public abstract class BaseEntity { @NotNull @LastModifiedDate - @Column(name = "updated_at", nullable = false, updatable = false) + @Column(name = "updated_at", nullable = false, updatable = true) private LocalDateTime updatedAt; } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 3b227db8e..d7b7c2897 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -4,15 +4,15 @@ import static org.mockito.Mockito.when; import java.time.Clock; +import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.List; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.HttpStatus; import in.koreatech.koin.AcceptanceTest; @@ -27,7 +27,9 @@ import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; +import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient; import in.koreatech.koin.domain.version.model.Version; +import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -44,6 +46,9 @@ class BusApiTest extends AcceptanceTest { @Autowired private CityBusCacheRepository cityBusCacheRepository; + @SpyBean + private CityBusOpenApiClient cityBusOpenApiClient; + @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { @@ -201,7 +206,7 @@ void getNextCityBusRemainTimeOpenApi() { BusStation arrival = BusStation.from("koreatech"); BusDirection direction = BusStation.getDirection(depart, arrival); - Version version = versionRepository.save( + versionRepository.save( Version.builder() .version("20240_1711255839") .type("city_bus_timetable") @@ -254,8 +259,8 @@ void getNextCityBusRemainTimeOpenApi() { } """; - // TODO CityBusOpenApiClient.getOpenApiResponse() Mocking - // when(cityBusOpenApiClient.getOpenApiResponse(depart.getNodeId(direction))).thenReturn(json); + String nodeId = depart.getNodeId(direction); + when(cityBusOpenApiClient.getOpenApiResponse(nodeId)).thenReturn(json); ExtractableResponse response = RestAssured .given() @@ -268,6 +273,8 @@ void getNextCityBusRemainTimeOpenApi() { .statusCode(HttpStatus.OK.value()) .extract(); + Version version = versionRepository.getByType(VersionType.CITY); + SoftAssertions.assertSoftly( softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) From 4c1305ed1ab765ed48ce96ec56759fb198b9dee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sat, 30 Mar 2024 21:44:33 +0900 Subject: [PATCH 092/123] =?UTF-8?q?refactor:=20=EC=8B=9C=EB=82=B4=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 Co-authored-by: Choon0414 --- .../koin/global/config/JpaConfiguration.java | 2 - .../in/koreatech/koin/AcceptanceTest.java | 16 ++++- .../koreatech/koin/acceptance/BusApiTest.java | 69 ++++++++++--------- .../koin/config/TestJpaConfiguration.java | 19 ----- 4 files changed, 50 insertions(+), 56 deletions(-) delete mode 100644 src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java diff --git a/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java b/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java index 46ebc0cba..aa1acd063 100644 --- a/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java +++ b/src/main/java/in/koreatech/koin/global/config/JpaConfiguration.java @@ -1,12 +1,10 @@ package in.koreatech.koin.global.config; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing -@Profile("!test") public class JpaConfiguration { } diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index 4186bb3dc..7103acb8f 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -8,8 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -18,13 +21,13 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.utility.DockerImageName; -import in.koreatech.koin.config.TestJpaConfiguration; +import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient; import in.koreatech.koin.domain.owner.model.OwnerEventListener; import in.koreatech.koin.support.DBInitializer; import io.restassured.RestAssured; @SpringBootTest(webEnvironment = RANDOM_PORT) -@Import({DBInitializer.class, TestJpaConfiguration.class}) +@Import({DBInitializer.class}) @ActiveProfiles("test") public abstract class AcceptanceTest { @@ -37,6 +40,15 @@ public abstract class AcceptanceTest { @MockBean protected Clock clock; + @MockBean + protected DateTimeProvider dateTimeProvider; + + @SpyBean + protected CityBusOpenApiClient cityBusOpenApiClient; + + @SpyBean + protected AuditingHandler handler; + @MockBean protected OwnerEventListener ownerEventListener; diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index d7b7c2897..0f10b2d29 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -4,15 +4,17 @@ import static org.mockito.Mockito.when; import java.time.Clock; -import java.time.LocalDateTime; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.HttpStatus; import in.koreatech.koin.AcceptanceTest; @@ -27,7 +29,6 @@ import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; -import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; @@ -46,18 +47,30 @@ class BusApiTest extends AcceptanceTest { @Autowired private CityBusCacheRepository cityBusCacheRepository; - @SpyBean - private CityBusOpenApiClient cityBusOpenApiClient; + private final Instant UPDATED_AT = ZonedDateTime.parse( + "2024-02-21 18:00:00 KST", + ofPattern("yyyy-MM-dd " + "HH:mm:ss z") + ) + .toInstant(); + + @BeforeEach + void start() { + when(clock.instant()).thenReturn(UPDATED_AT); + when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); + + handler.setDateTimeProvider(dateTimeProvider); + } + + @AfterEach + void end() { + handler.setDateTimeProvider(null); + } @Test @DisplayName("다음 셔틀버스까지 남은 시간을 조회한다.") void getNextShuttleBusRemainTime() { final String arrivalTime = "18:10"; - when(clock.instant()).thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")).toInstant()); - when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); - BusType busType = BusType.from("shuttle"); BusStation depart = BusStation.from("koreatech"); BusStation arrival = BusStation.from("terminal"); @@ -126,13 +139,7 @@ void getNextCityBusRemainTimeRedis() { final long remainTime = 600L; final long busNumber = 400; - when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); - - when(clock.instant()) - .thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant() - ); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); BusType busType = BusType.from("city"); BusStation depart = BusStation.from("terminal"); @@ -145,12 +152,14 @@ void getNextCityBusRemainTimeRedis() { .type("city_bus_timetable") .build() ); + System.out.println("=========="); + System.out.println(version.getUpdatedAt()); + + Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant(); - when(clock.instant()) - .thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant() - ); + when(clock.instant()).thenReturn(requestedAt); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); cityBusCacheRepository.save( CityBusCache.create( @@ -193,13 +202,7 @@ void getNextCityBusRemainTimeRedis() { @Test @DisplayName("다음 시내버스까지 남은 시간을 조회한다. - OpenApi") void getNextCityBusRemainTimeOpenApi() { - when(clock.getZone()).thenReturn(Clock.systemDefaultZone().getZone()); - - when(clock.instant()) - .thenReturn( - ZonedDateTime.parse("2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant() - ); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); BusType busType = BusType.from("city"); BusStation depart = BusStation.from("terminal"); @@ -213,11 +216,11 @@ void getNextCityBusRemainTimeOpenApi() { .build() ); - when(clock.instant()) - .thenReturn( - ZonedDateTime.parse("2024-02-21 21:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant() - ); + Instant requestedAt = ZonedDateTime.parse("2024-02-21 21:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant(); + + when(clock.instant()).thenReturn(requestedAt); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); String json = """ { diff --git a/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java b/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java deleted file mode 100644 index 56c8954f1..000000000 --- a/src/test/java/in/koreatech/koin/config/TestJpaConfiguration.java +++ /dev/null @@ -1,19 +0,0 @@ -package in.koreatech.koin.config; - -import java.time.Clock; -import java.time.Instant; -import java.util.Optional; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.data.auditing.DateTimeProvider; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; - -@TestConfiguration -@EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider") -public class TestJpaConfiguration { - @Bean(name = "myAuditingDateTimeProvider") - public DateTimeProvider dateTimeProvider(Clock clock) { - return () -> Optional.of(Instant.now(clock)); - } -} From 6f04ed0fb7b08d9ddac5ea573f222e1b7d225a65 Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Sun, 31 Mar 2024 15:47:59 +0900 Subject: [PATCH 093/123] =?UTF-8?q?refactor:=20Constant=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20enum=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/Constant.java | 27 +++++++++++-------- .../domain/bus/util/CityBusOpenApiClient.java | 12 ++++----- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java index 69b4654a3..aafa1f36c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java @@ -1,16 +1,21 @@ package in.koreatech.koin.domain.bus.model; -public final class Constant { - private Constant() { - throw new IllegalStateException("Utility class"); - } +public enum Constant { + CODE_SERVICE_DISPOSE("12"), + CODE_SERVICE_ACCESS_DENIED("20"), + CODE_SERVICE_REQUEST_OVER("22"), + CODE_KEY_UNREGISTERED("30"), + CODE_SERVICE_KEY_EXPIRED("31"), + CODE_SERVICE_SUCCESS("00"), + ; + + private final String code; - public static final long CACHE_EXPIRE_MINUTE = 1L; + private Constant(String code){ + this.code = code; + } - public static final String CODE_SERVICE_DISPOSE = "12"; - public static final String CODE_SERVICE_ACCESS_DENIED = "20"; - public static final String CODE_SERVICE_REQUEST_OVER = "22"; - public static final String CODE_KEY_UNREGISTERED = "30"; - public static final String CODE_SERVICE_KEY_EXPIRED = "31"; - public static final String CODE_SERVICE_SUCCESS = "00"; + public String getCode(){ + return code; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 0bdebfd46..75991eec0 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -189,22 +189,22 @@ private void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); String errorMessage = ""; - if (CODE_SERVICE_DISPOSE.equals(resultCode)) { + if (CODE_SERVICE_DISPOSE.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; } - if (CODE_SERVICE_ACCESS_DENIED.equals(resultCode)) { + if (CODE_SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; } - if (CODE_SERVICE_REQUEST_OVER.equals(resultCode)) { + if (CODE_SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; } - if (CODE_KEY_UNREGISTERED.equals(resultCode)) { + if (CODE_KEY_UNREGISTERED.getCode().equals(resultCode)) { errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; } - if (CODE_SERVICE_KEY_EXPIRED.equals(resultCode)) { + if (CODE_SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; } - if (!CODE_SERVICE_SUCCESS.equals(resultCode)) { + if (!CODE_SERVICE_SUCCESS.getCode().equals(resultCode)) { String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } From 38c03cb463dad32898893a85ba63f181cb7ebbf1 Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Sun, 31 Mar 2024 15:48:39 +0900 Subject: [PATCH 094/123] =?UTF-8?q?refactor:=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/redis/CityBusCache.java | 14 ++++++++++++-- .../koin/domain/bus/util/BusOpenApiClient.java | 5 ++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java index bfbc6eca7..45d2694ec 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java @@ -1,7 +1,5 @@ package in.koreatech.koin.domain.bus.model.redis; -import static in.koreatech.koin.domain.bus.model.Constant.CACHE_EXPIRE_MINUTE; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -17,6 +15,10 @@ @RedisHash("CityBus") public class CityBusCache { + private static final long CACHE_EXPIRE_MINUTE = 1L; + + private static final long CACHE_EXPIRE_SECONDS = 60L; + @Id private String id; @@ -39,4 +41,12 @@ public static CityBusCache create(String nodeId, List busInfos) { .expiration(CACHE_EXPIRE_MINUTE) .build(); } + + public static long getCacheExpireMinute() { + return CACHE_EXPIRE_MINUTE; + } + + public static long getCacheExpireSeconds() { + return CACHE_EXPIRE_SECONDS; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java index 78587bfe9..11e7e23f9 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java @@ -1,13 +1,12 @@ package in.koreatech.koin.domain.bus.util; -import static in.koreatech.koin.domain.bus.model.Constant.CACHE_EXPIRE_MINUTE; - import java.time.Clock; import java.time.Duration; import java.time.LocalTime; import java.util.List; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.version.model.Version; public abstract class BusOpenApiClient { @@ -17,6 +16,6 @@ public abstract class BusOpenApiClient { public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || CACHE_EXPIRE_MINUTE * 60 <= duration.toSeconds(); + return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); } } From e510c6e7f0e0628f64c6294df2a572705d7d928a Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Sun, 31 Mar 2024 15:49:59 +0900 Subject: [PATCH 095/123] =?UTF-8?q?Revert=20"refactor:=20Constant=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20enum=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5dcbf975cccbbd276dfa5e8dbbb52864eca6c209. --- .../koin/domain/bus/model/Constant.java | 27 ++++++++----------- .../domain/bus/util/CityBusOpenApiClient.java | 12 ++++----- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java index aafa1f36c..69b4654a3 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java @@ -1,21 +1,16 @@ package in.koreatech.koin.domain.bus.model; -public enum Constant { - CODE_SERVICE_DISPOSE("12"), - CODE_SERVICE_ACCESS_DENIED("20"), - CODE_SERVICE_REQUEST_OVER("22"), - CODE_KEY_UNREGISTERED("30"), - CODE_SERVICE_KEY_EXPIRED("31"), - CODE_SERVICE_SUCCESS("00"), - ; - - private final String code; - - private Constant(String code){ - this.code = code; +public final class Constant { + private Constant() { + throw new IllegalStateException("Utility class"); } - public String getCode(){ - return code; - } + public static final long CACHE_EXPIRE_MINUTE = 1L; + + public static final String CODE_SERVICE_DISPOSE = "12"; + public static final String CODE_SERVICE_ACCESS_DENIED = "20"; + public static final String CODE_SERVICE_REQUEST_OVER = "22"; + public static final String CODE_KEY_UNREGISTERED = "30"; + public static final String CODE_SERVICE_KEY_EXPIRED = "31"; + public static final String CODE_SERVICE_SUCCESS = "00"; } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 75991eec0..0bdebfd46 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -189,22 +189,22 @@ private void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); String errorMessage = ""; - if (CODE_SERVICE_DISPOSE.getCode().equals(resultCode)) { + if (CODE_SERVICE_DISPOSE.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; } - if (CODE_SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { + if (CODE_SERVICE_ACCESS_DENIED.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; } - if (CODE_SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { + if (CODE_SERVICE_REQUEST_OVER.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; } - if (CODE_KEY_UNREGISTERED.getCode().equals(resultCode)) { + if (CODE_KEY_UNREGISTERED.equals(resultCode)) { errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; } - if (CODE_SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { + if (CODE_SERVICE_KEY_EXPIRED.equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; } - if (!CODE_SERVICE_SUCCESS.getCode().equals(resultCode)) { + if (!CODE_SERVICE_SUCCESS.equals(resultCode)) { String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } From 523b435429854210f91e9ae38347564832cd1edb Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Sun, 31 Mar 2024 15:50:33 +0900 Subject: [PATCH 096/123] =?UTF-8?q?Revert=20"Revert=20"refactor:=20Constan?= =?UTF-8?q?t=20=ED=81=B4=EB=9E=98=EC=8A=A4=20enum=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f321b7eb60f0d5426a1fef4d839c4861a08198a2. --- .../koin/domain/bus/model/Constant.java | 27 +++++++++++-------- .../domain/bus/util/CityBusOpenApiClient.java | 12 ++++----- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java index 69b4654a3..aafa1f36c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java @@ -1,16 +1,21 @@ package in.koreatech.koin.domain.bus.model; -public final class Constant { - private Constant() { - throw new IllegalStateException("Utility class"); - } +public enum Constant { + CODE_SERVICE_DISPOSE("12"), + CODE_SERVICE_ACCESS_DENIED("20"), + CODE_SERVICE_REQUEST_OVER("22"), + CODE_KEY_UNREGISTERED("30"), + CODE_SERVICE_KEY_EXPIRED("31"), + CODE_SERVICE_SUCCESS("00"), + ; + + private final String code; - public static final long CACHE_EXPIRE_MINUTE = 1L; + private Constant(String code){ + this.code = code; + } - public static final String CODE_SERVICE_DISPOSE = "12"; - public static final String CODE_SERVICE_ACCESS_DENIED = "20"; - public static final String CODE_SERVICE_REQUEST_OVER = "22"; - public static final String CODE_KEY_UNREGISTERED = "30"; - public static final String CODE_SERVICE_KEY_EXPIRED = "31"; - public static final String CODE_SERVICE_SUCCESS = "00"; + public String getCode(){ + return code; + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 0bdebfd46..75991eec0 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -189,22 +189,22 @@ private void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); String errorMessage = ""; - if (CODE_SERVICE_DISPOSE.equals(resultCode)) { + if (CODE_SERVICE_DISPOSE.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; } - if (CODE_SERVICE_ACCESS_DENIED.equals(resultCode)) { + if (CODE_SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; } - if (CODE_SERVICE_REQUEST_OVER.equals(resultCode)) { + if (CODE_SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; } - if (CODE_KEY_UNREGISTERED.equals(resultCode)) { + if (CODE_KEY_UNREGISTERED.getCode().equals(resultCode)) { errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; } - if (CODE_SERVICE_KEY_EXPIRED.equals(resultCode)) { + if (CODE_SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; } - if (!CODE_SERVICE_SUCCESS.equals(resultCode)) { + if (!CODE_SERVICE_SUCCESS.getCode().equals(resultCode)) { String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } From 5b6a4c5d0ecaf43fb51408650cf8546be270bfcb Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Sun, 31 Mar 2024 15:55:36 +0900 Subject: [PATCH 097/123] =?UTF-8?q?refactor:=20Constant=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/Constant.java | 21 ------------------- .../bus/model/enums/OpenApiResultCode.java | 21 +++++++++++++++++++ .../domain/bus/util/CityBusOpenApiClient.java | 14 ++++++------- 3 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/Constant.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java b/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java deleted file mode 100644 index aafa1f36c..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/Constant.java +++ /dev/null @@ -1,21 +0,0 @@ -package in.koreatech.koin.domain.bus.model; - -public enum Constant { - CODE_SERVICE_DISPOSE("12"), - CODE_SERVICE_ACCESS_DENIED("20"), - CODE_SERVICE_REQUEST_OVER("22"), - CODE_KEY_UNREGISTERED("30"), - CODE_SERVICE_KEY_EXPIRED("31"), - CODE_SERVICE_SUCCESS("00"), - ; - - private final String code; - - private Constant(String code){ - this.code = code; - } - - public String getCode(){ - return code; - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java new file mode 100644 index 000000000..e99cee518 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java @@ -0,0 +1,21 @@ +package in.koreatech.koin.domain.bus.model.enums; + +public enum OpenApiResultCode { + SERVICE_DISPOSE("12"), + SERVICE_ACCESS_DENIED("20"), + SERVICE_REQUEST_OVER("22"), + KEY_UNREGISTERED("30"), + SERVICE_KEY_EXPIRED("31"), + SERVICE_SUCCESS("00"), + ; + + private final String code; + + private OpenApiResultCode(String code){ + this.code = code; + } + + public String getCode(){ + return code; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 75991eec0..f744c3616 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -1,6 +1,6 @@ package in.koreatech.koin.domain.bus.util; -import static in.koreatech.koin.domain.bus.model.Constant.*; +import static in.koreatech.koin.domain.bus.model.enums.OpenApiResultCode.*; import static java.net.URLEncoder.encode; import java.io.BufferedReader; @@ -189,22 +189,22 @@ private void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); String errorMessage = ""; - if (CODE_SERVICE_DISPOSE.getCode().equals(resultCode)) { + if (SERVICE_DISPOSE.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; } - if (CODE_SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { + if (SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; } - if (CODE_SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { + if (SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; } - if (CODE_KEY_UNREGISTERED.getCode().equals(resultCode)) { + if (KEY_UNREGISTERED.getCode().equals(resultCode)) { errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; } - if (CODE_SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { + if (SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; } - if (!CODE_SERVICE_SUCCESS.getCode().equals(resultCode)) { + if (!SERVICE_SUCCESS.getCode().equals(resultCode)) { String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } From 5de1d7053b960130e138e660cd1a459652c780a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sun, 31 Mar 2024 16:56:01 +0900 Subject: [PATCH 098/123] =?UTF-8?q?feat:=20=EB=B2=84=EC=8A=A4=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 400, 402, 405 Co-authored-by: dradnats1012 --- .../koin/domain/bus/util/CityBusOpenApiClient.java | 7 +++++++ .../in/koreatech/koin/acceptance/BusApiTest.java | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index f744c3616..3b27248b7 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -14,6 +14,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.springframework.beans.factory.annotation.Value; @@ -49,6 +50,8 @@ public class CityBusOpenApiClient extends BusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; + private static final List AVAILABLE_CITY_BUS = List.of(400L, 402L, 405L); + private final String openApiKey; private final Gson gson; @@ -96,6 +99,10 @@ private void getAllCityBusArrivalInfoByOpenApi() { List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) + .map(cityBusArrivals -> cityBusArrivals + .stream() + .filter(cityBusArrival -> AVAILABLE_CITY_BUS.stream().anyMatch(busNumber -> Objects.equals(busNumber, + cityBusArrival.routeno()))).toList()) .toList(); LocalDateTime updatedAt = LocalDateTime.now(clock); diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 0f10b2d29..fdd9c35c6 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -251,12 +251,22 @@ void getNextCityBusRemainTimeOpenApi() { "routeno": 405, "routetp": "일반버스", "vehicletp": "일반차량" + }, + { + "arrprevstationcnt": 10, + "arrtime": 700, + "nodeid": "CAB285000686", + "nodenm": "종합터미널", + "routeid": "CAB285000024", + "routeno": 200, + "routetp": "일반버스", + "vehicletp": "일반차량" } ] }, "numOfRows": 30, "pageNo": 1, - "totalCount": 2 + "totalCount": 3 } } } From 0dd5e0d79b0d4e3185a1199880228420960ab1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sun, 31 Mar 2024 17:16:08 +0900 Subject: [PATCH 099/123] =?UTF-8?q?chore:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../koreatech/koin/global/exception/GlobalExceptionHandler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index b1c9d9000..3c8bf5f63 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -2,8 +2,6 @@ import java.time.format.DateTimeParseException; -import lombok.extern.slf4j.Slf4j; - import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; From 10ff45fc3588a8430bc721035e65d7ba0992c0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sun, 31 Mar 2024 17:19:41 +0900 Subject: [PATCH 100/123] =?UTF-8?q?style:=20=EC=A4=84=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- .../koin/domain/bus/util/CityBusOpenApiClient.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 3b27248b7..9ca9ce782 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -99,11 +99,12 @@ private void getAllCityBusArrivalInfoByOpenApi() { List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) - .map(cityBusArrivals -> cityBusArrivals - .stream() - .filter(cityBusArrival -> AVAILABLE_CITY_BUS.stream().anyMatch(busNumber -> Objects.equals(busNumber, - cityBusArrival.routeno()))).toList()) - .toList(); + .map(cityBusArrivals -> cityBusArrivals.stream() + .filter(cityBusArrival -> + AVAILABLE_CITY_BUS.stream().anyMatch(busNumber -> + Objects.equals(busNumber, cityBusArrival.routeno())) + ).toList() + ).toList(); LocalDateTime updatedAt = LocalDateTime.now(clock); From 18ed4e253d0a4d3abfd00208b68d6ba6117b336f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Sun, 31 Mar 2024 17:31:09 +0900 Subject: [PATCH 101/123] =?UTF-8?q?refactor:=20=EC=A4=91=EA=B4=84=ED=98=B8?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 --- src/test/java/in/koreatech/koin/AcceptanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java index 1bca0c25a..f1cfbc91e 100644 --- a/src/test/java/in/koreatech/koin/AcceptanceTest.java +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -27,7 +27,7 @@ import io.restassured.RestAssured; @SpringBootTest(webEnvironment = RANDOM_PORT) -@Import({DBInitializer.class}) +@Import(DBInitializer.class) @ActiveProfiles("test") public abstract class AcceptanceTest { From 4ebf08d88af4cef51a1c0ba9a7162ac639bcf888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 1 Apr 2024 10:58:48 +0900 Subject: [PATCH 102/123] =?UTF-8?q?test:=20OPEN=5FAPI=5FKEY=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 9626f3031..6e343d471 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -54,3 +54,5 @@ s3: koin: admin: url: https://admin-url-path.com + +OPEN_API_KEY: From 1a839ca05b7942a949e7c8e2e83839e2079a0ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EB=B9=88?= Date: Mon, 1 Apr 2024 14:17:30 +0900 Subject: [PATCH 103/123] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/model/enums/BusApiType.java | 2 +- .../bus/model/enums/BusOpenApiResultCode.java | 47 +++++++++++++++++++ .../bus/model/enums/OpenApiResultCode.java | 21 --------- .../domain/bus/model/redis/CityBusCache.java | 1 - .../domain/bus/util/BusOpenApiClient.java | 1 - .../domain/bus/util/CityBusOpenApiClient.java | 39 ++------------- .../exception/GlobalExceptionHandler.java | 2 +- .../koreatech/koin/acceptance/BusApiTest.java | 4 +- 8 files changed, 55 insertions(+), 62 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java index 9f7ce35e3..f2a974325 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java @@ -8,7 +8,7 @@ @Getter public enum BusApiType { CITY("cityBusOpenApiClient"), - EXPRESS("intercityBusOpenApiClient") + EXPRESS("intercityBusOpenApiClient"), ; private final String value; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java new file mode 100644 index 000000000..f2a3ee19b --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java @@ -0,0 +1,47 @@ +package in.koreatech.koin.domain.bus.model.enums; + +import java.util.Arrays; +import java.util.Optional; + +import com.google.gson.JsonObject; + +import in.koreatech.koin.domain.bus.exception.BusOpenApiException; +import lombok.Getter; + +@Getter +public enum BusOpenApiResultCode { + SERVICE_DISPOSE("12", "버스도착정보 공공 API 서비스가 폐기되었습니다."), + SERVICE_ACCESS_DENIED("20", "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."), + SERVICE_REQUEST_OVER("22", "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."), + KEY_UNREGISTERED("30", "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."), + SERVICE_KEY_EXPIRED("31", "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."), + SERVICE_SUCCESS("00", "NORMAL SERVICE."), + ; + + private final String code; + private final String message; + + BusOpenApiResultCode(String code, String message) { + this.code = code; + this.message = message; + } + + public static void validateResponse(JsonObject response) { + String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); + + String errorMessage = ""; + + if (!resultCode.equals(SERVICE_SUCCESS.code)) { + Optional code = Arrays.stream(BusOpenApiResultCode.values()) + .filter(busOpenApiResultCode -> busOpenApiResultCode.code.equals(resultCode)) + .findFirst(); + + if (code.isPresent()) { + errorMessage = code.get().message; + } + + String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); + throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); + } + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java deleted file mode 100644 index e99cee518..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/OpenApiResultCode.java +++ /dev/null @@ -1,21 +0,0 @@ -package in.koreatech.koin.domain.bus.model.enums; - -public enum OpenApiResultCode { - SERVICE_DISPOSE("12"), - SERVICE_ACCESS_DENIED("20"), - SERVICE_REQUEST_OVER("22"), - KEY_UNREGISTERED("30"), - SERVICE_KEY_EXPIRED("31"), - SERVICE_SUCCESS("00"), - ; - - private final String code; - - private OpenApiResultCode(String code){ - this.code = code; - } - - public String getCode(){ - return code; - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java index 45d2694ec..d1911c577 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java @@ -16,7 +16,6 @@ public class CityBusCache { private static final long CACHE_EXPIRE_MINUTE = 1L; - private static final long CACHE_EXPIRE_SECONDS = 60L; @Id diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java index 11e7e23f9..0f80262c1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java @@ -15,7 +15,6 @@ public abstract class BusOpenApiClient { public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 9ca9ce782..f8a0ec273 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -1,6 +1,5 @@ package in.koreatech.koin.domain.bus.util; -import static in.koreatech.koin.domain.bus.model.enums.OpenApiResultCode.*; import static java.net.URLEncoder.encode; import java.io.BufferedReader; @@ -28,9 +27,9 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import in.koreatech.koin.domain.bus.exception.BusOpenApiException; import in.koreatech.koin.domain.bus.model.CityBusArrival; import in.koreatech.koin.domain.bus.model.CityBusRemainTime; +import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStationNode; import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; @@ -49,21 +48,16 @@ public class CityBusOpenApiClient extends BusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; - private static final List AVAILABLE_CITY_BUS = List.of(400L, 402L, 405L); + private static final Type arrivalInfoType = new TypeToken>() { + }.getType(); private final String openApiKey; - private final Gson gson; - private final Clock clock; - private final VersionRepository versionRepository; private final CityBusCacheRepository cityBusCacheRepository; - private static final Type arrivalInfoType = new TypeToken>() { - }.getType(); - public CityBusOpenApiClient( @Value("${OPEN_API_KEY}") String openApiKey, Gson gson, @@ -173,7 +167,7 @@ private List extractBusArrivalInfo(String jsonResponse) { .getAsJsonObject() .get("response") .getAsJsonObject(); - validateResponse(response); + BusOpenApiResultCode.validateResponse(response); JsonObject body = response.get("body").getAsJsonObject(); if (body.get("totalCount").getAsLong() == 0) { @@ -192,29 +186,4 @@ private List extractBusArrivalInfo(String jsonResponse) { return result; } } - - private void validateResponse(JsonObject response) { - String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); - - String errorMessage = ""; - if (SERVICE_DISPOSE.getCode().equals(resultCode)) { - errorMessage = "버스도착정보 공공 API 서비스가 폐기되었습니다."; - } - if (SERVICE_ACCESS_DENIED.getCode().equals(resultCode)) { - errorMessage = "버스도착정보 공공 API 서비스가 접근 거부 상태입니다."; - } - if (SERVICE_REQUEST_OVER.getCode().equals(resultCode)) { - errorMessage = "버스도착정보 공공 API 서비스의 요청 제한 횟수가 초과되었습니다."; - } - if (KEY_UNREGISTERED.getCode().equals(resultCode)) { - errorMessage = "등록되지 않은 버스도착정보 공공 API 서비스 키입니다."; - } - if (SERVICE_KEY_EXPIRED.getCode().equals(resultCode)) { - errorMessage = "버스도착정보 공공 API 서비스 키의 활용 기간이 만료되었습니다."; - } - if (!SERVICE_SUCCESS.getCode().equals(resultCode)) { - String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); - throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); - } - } } diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index 3c8bf5f63..e132ffb88 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -61,7 +61,7 @@ public ResponseEntity handleDataNotFoundException(DuplicationExce public ResponseEntity handleExternalServiceException(ExternalServiceException e) { log.warn(e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ErrorResponse.from("외부 서비스에 접속할 수 없습니다.")); + .body(ErrorResponse.from("외부 API 호출 과정에서 문제가 발생했습니다.")); } @ExceptionHandler diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index fdd9c35c6..f11dad223 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -222,7 +222,7 @@ void getNextCityBusRemainTimeOpenApi() { when(clock.instant()).thenReturn(requestedAt); when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); - String json = """ + String busApiReturnValue = """ { "response": { "header": { @@ -273,7 +273,7 @@ void getNextCityBusRemainTimeOpenApi() { """; String nodeId = depart.getNodeId(direction); - when(cityBusOpenApiClient.getOpenApiResponse(nodeId)).thenReturn(json); + when(cityBusOpenApiClient.getOpenApiResponse(nodeId)).thenReturn(busApiReturnValue); ExtractableResponse response = RestAssured .given() From 4fad0230498c0bebfed81a680fdd2afecdd99445 Mon Sep 17 00:00:00 2001 From: dradnats1012 Date: Fri, 5 Apr 2024 00:05:57 +0900 Subject: [PATCH 104/123] =?UTF-8?q?feat=20:=20=EC=9D=B8=EC=88=98=EC=9D=B8?= =?UTF-8?q?=EA=B3=84=EC=9A=A9=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/model/CityBusRemainTime.java | 4 +- .../domain/bus/model/IntercityBusArrival.java | 16 ++ .../domain/bus/model/IntercityBusRoute.java | 15 ++ .../model/enums/IntercityBusStationNode.java | 35 ++++ .../domain/bus/model/redis/CityBusCache.java | 6 +- .../{BusCache.java => CityBusCacheInfo.java} | 7 +- .../bus/model/redis/IntercityBusCache.java | 47 +++++ .../model/redis/IntercityBusCacheInfo.java | 22 +++ .../IntercityBusCacheRepository.java | 19 ++ .../koin/domain/bus/service/BusService.java | 22 ++- .../domain/bus/util/BusOpenApiClient.java | 11 +- .../domain/bus/util/CityBusOpenApiClient.java | 11 +- .../bus/util/IntercityBusOpenApiClient.java | 182 +++++++++++++++++- .../koreatech/koin/acceptance/BusApiTest.java | 4 +- 14 files changed, 362 insertions(+), 39 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java rename src/main/java/in/koreatech/koin/domain/bus/model/redis/{BusCache.java => CityBusCacheInfo.java} (83%) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java index 21c7dbb28..ad1da782c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java @@ -3,7 +3,7 @@ import java.time.LocalTime; import java.util.Objects; -import in.koreatech.koin.domain.bus.model.redis.BusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -17,7 +17,7 @@ public CityBusRemainTime(Long busNumber, LocalTime busArrivalTime) { this.busNumber = busNumber; } - public static CityBusRemainTime from(BusCache busInfo) { + public static CityBusRemainTime from(CityBusCacheInfo busInfo) { return builder() .busNumber(busInfo.busNumber()) .busArrivalTime(busInfo.remainTime()) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java b/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java new file mode 100644 index 000000000..31ce53e1a --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java @@ -0,0 +1,16 @@ +package in.koreatech.koin.domain.bus.model; + +import lombok.Builder; + +@Builder +public record IntercityBusArrival( + String arrPlaceNm, // 도착지 + String arrPlandTime, // 도착 시간 + String depPlaceNm, // 출발지 + String depPlandTime, // 출발 시간 + int charge, // 운임 요금 + String gradeNm, // 버스 등급 + String routeId // 노선 id +) { + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java b/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java new file mode 100644 index 000000000..cdacf8982 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java @@ -0,0 +1,15 @@ +package in.koreatech.koin.domain.bus.model; + +import in.koreatech.koin.domain.bus.model.enums.IntercityBusStationNode; + +public record IntercityBusRoute( + String depTerminalId, // 출발지 + String arrTerminalId // 도착지 +) { + public static IntercityBusRoute from(IntercityBusArrival intercityBusArrival) { + return new IntercityBusRoute( + IntercityBusStationNode.getId(intercityBusArrival.depPlaceNm()), + IntercityBusStationNode.getId(intercityBusArrival.arrPlaceNm()) + ); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java new file mode 100644 index 000000000..f6e2fb238 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java @@ -0,0 +1,35 @@ +package in.koreatech.koin.domain.bus.model.enums; + +import java.util.ArrayList; +import java.util.List; + +import in.koreatech.koin.domain.bus.model.IntercityBusRoute; +import lombok.Getter; + +/** + * OpenApi 상세: 국토교통부_전국 버스정류장 위치정보 (버스 정류장 노드 ID) + * https://www.data.go.kr/data/15067528/fileData.do + */ +@Getter +public enum IntercityBusStationNode { + KOREATECH("NAI3125301"), // 코리아텍 + TERMINAL("NAI3112001"), // 종합터미널 + ; + + private String stationId; + + IntercityBusStationNode(String stationId) { + this.stationId = stationId; + } + + public static String getId(String stationName){ + return valueOf(stationName).getStationId(); + } + + public static List getIds() { + List routeList = new ArrayList<>(); + routeList.add(new IntercityBusRoute(KOREATECH.getStationId(), TERMINAL.getStationId())); + routeList.add(new IntercityBusRoute(TERMINAL.getStationId(), KOREATECH.getStationId())); + return routeList; + } +} \ No newline at end of file diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java index d1911c577..ee0720a23 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java @@ -21,19 +21,19 @@ public class CityBusCache { @Id private String id; - private final List busInfos = new ArrayList<>(); + private final List busInfos = new ArrayList<>(); @TimeToLive(unit = TimeUnit.MINUTES) private final Long expiration; @Builder - private CityBusCache(String id, List busInfos, Long expiration) { + private CityBusCache(String id, List busInfos, Long expiration) { this.id = id; this.busInfos.addAll(busInfos); this.expiration = expiration; } - public static CityBusCache create(String nodeId, List busInfos) { + public static CityBusCache create(String nodeId, List busInfos) { return CityBusCache.builder() .id(nodeId) .busInfos(busInfos) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java similarity index 83% rename from src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java rename to src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java index a48b59164..5de65810e 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java @@ -6,7 +6,7 @@ import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.CityBusArrival; -public record BusCache( +public record CityBusCacheInfo( Long busNumber, LocalTime remainTime ) { @@ -18,11 +18,10 @@ public record BusCache( * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로 * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌 * */ - public static BusCache from(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { - return new BusCache( + public static CityBusCacheInfo from(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { + return new CityBusCacheInfo( busArrivalInfo.routeno(), updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() ); } - } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java new file mode 100644 index 000000000..288e6ba9f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java @@ -0,0 +1,47 @@ +package in.koreatech.koin.domain.bus.model.redis; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +import in.koreatech.koin.domain.bus.model.IntercityBusRoute; +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; + +@Getter +@RedisHash("intercityBus") +public class IntercityBusCache { + + private static final long CACHE_EXPIRE_HOUR = 1L; + + @Id + private String id; + + private final List busInfos = new ArrayList<>(); + + @TimeToLive(unit = TimeUnit.HOURS) + private final Long expiration; + + @Builder + private IntercityBusCache(String id, List busInfos, Long expiration){ + this.id = id; + this.busInfos.addAll(busInfos); + this.expiration = expiration; + } + + public static IntercityBusCache create(IntercityBusRoute route, List busInfos){ + return IntercityBusCache.builder() + .id(route.depTerminalId() + route.arrTerminalId()) + .busInfos(busInfos) + .expiration(CACHE_EXPIRE_HOUR) + .build(); + } + + public static long getCacheExpireHour() { + return CACHE_EXPIRE_HOUR; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java new file mode 100644 index 000000000..f2b0a8d05 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java @@ -0,0 +1,22 @@ +package in.koreatech.koin.domain.bus.model.redis; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +import in.koreatech.koin.domain.bus.model.IntercityBusArrival; +import in.koreatech.koin.domain.bus.model.IntercityBusRoute; + +public record IntercityBusCacheInfo ( + IntercityBusRoute intercityBusRoute, + LocalTime remainTime +) { + public static IntercityBusCacheInfo from( + IntercityBusArrival busArrivalInfo, + LocalDateTime updatedAt + ) { + return new IntercityBusCacheInfo( + IntercityBusRoute.from(busArrivalInfo), + updatedAt.plusSeconds(busArrivalInfo).toLocalTime() + ); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java new file mode 100644 index 000000000..f5640733a --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.domain.bus.repository; + +import java.util.Optional; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; +import in.koreatech.koin.domain.bus.model.redis.IntercityBusCache; + +public interface IntercityBusCacheRepository extends Repository { + + IntercityBusCache save(IntercityBusCache intercityBusCache); + + Optional findById(String busRoute); + + default IntercityBusCache getById(String busRoute){ + return findById(busRoute).orElseThrow(() -> BusCacheNotFoundException.withDetail("busRoute: " + busRoute)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 37b0b1257..3fc714d99 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,13 +11,14 @@ import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; import in.koreatech.koin.domain.bus.exception.BusIllegalStationException; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.enums.BusApiType; -import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.IntercityBusStationNode; +import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.repository.BusRepository; -import in.koreatech.koin.domain.bus.util.BusOpenApiClient; +import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient; +import in.koreatech.koin.domain.bus.util.IntercityBusOpenApiClient; import lombok.RequiredArgsConstructor; @Service @@ -28,7 +28,8 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; - private final Map> busOpenApiClient; + private final CityBusOpenApiClient cityBusOpenApiClient; + private final IntercityBusOpenApiClient intercityBusOpenApiClient; @Transactional public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { @@ -39,11 +40,12 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN validateBusCourse(depart, arrival); List remainTimes = new ArrayList<>(); - if (busType == BusType.CITY || busType == BusType.EXPRESS) { - remainTimes = busOpenApiClient.get(BusApiType.from(busType).getValue()) - .getBusRemainTime(depart.getNodeId(direction)); - } - else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { + if (busType == BusType.CITY) { + remainTimes = cityBusOpenApiClient.getBusRemainTime(depart.getNodeId(direction)); + } else if (busType == BusType.EXPRESS) { + remainTimes = intercityBusOpenApiClient.getBusRemainTime(IntercityBusStationNode.getId(departName), + IntercityBusStationNode.getId(arrivalName)); + } else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); remainTimes = busCourses.stream() .map(BusCourse::getRoutes) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java index 0f80262c1..119822ae5 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java @@ -1,20 +1,11 @@ package in.koreatech.koin.domain.bus.util; import java.time.Clock; -import java.time.Duration; -import java.time.LocalTime; -import java.util.List; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; import in.koreatech.koin.domain.version.model.Version; public abstract class BusOpenApiClient { - public abstract List getBusRemainTime(String nodeId); - - public boolean isCacheExpired(Version version, Clock clock) { - Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); - } + public abstract boolean isCacheExpired(Version version, Clock clock); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index f8a0ec273..d9d7c6861 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -10,7 +10,9 @@ import java.net.HttpURLConnection; import java.net.URL; import java.time.Clock; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -31,8 +33,8 @@ import in.koreatech.koin.domain.bus.model.CityBusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStationNode; -import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; @@ -111,7 +113,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { CityBusCache.create( arrivalInfos.get(0).nodeid(), arrivalInfos.stream() - .map(busArrivalInfo -> BusCache.from(busArrivalInfo, updatedAt)) + .map(busArrivalInfo -> CityBusCacheInfo.from(busArrivalInfo, updatedAt)) .toList() ) ); @@ -186,4 +188,9 @@ private List extractBusArrivalInfo(String jsonResponse) { return result; } } + + public boolean isCacheExpired(Version version, Clock clock) { + Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); + return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); + } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java index 6674299fe..756c7b530 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java @@ -1,25 +1,195 @@ package in.koreatech.koin.domain.bus.util; +import static java.net.URLEncoder.encode; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.Clock; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + import in.koreatech.koin.domain.bus.model.BusRemainTime; -import lombok.RequiredArgsConstructor; +import in.koreatech.koin.domain.bus.model.IntercityBusArrival; +import in.koreatech.koin.domain.bus.model.IntercityBusRoute; +import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; +import in.koreatech.koin.domain.bus.model.enums.IntercityBusStationNode; +import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.redis.IntercityBusCache; +import in.koreatech.koin.domain.bus.model.redis.IntercityBusCacheInfo; +import in.koreatech.koin.domain.bus.repository.IntercityBusCacheRepository; +import in.koreatech.koin.domain.version.model.Version; +import in.koreatech.koin.domain.version.model.VersionType; +import in.koreatech.koin.domain.version.repository.VersionRepository; /** * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 - * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098530 + * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098541 */ @Component -@RequiredArgsConstructor @Transactional(readOnly = true) public class IntercityBusOpenApiClient extends BusOpenApiClient { - @Override - public List getBusRemainTime(String nodeId) { - return new ArrayList<>(); + private static final String ENCODE_TYPE = "UTF-8"; + private static final Type arrivalInfoType = new TypeToken>() { + }.getType(); + + private final VersionRepository versionRepository; + private final IntercityBusCacheRepository intercityBusCacheRepository; + private final String openApiKey; + private final Gson gson; + private final Clock clock; + + public IntercityBusOpenApiClient( + @Value("${OPEN_API_KEY}") String openApiKey, + VersionRepository versionRepository, + Gson gson, + Clock clock, + IntercityBusCacheRepository interCityBusCacheRepository + ) { + this.openApiKey = openApiKey; + this.versionRepository = versionRepository; + this.gson = gson; + this.clock = clock; + this.intercityBusCacheRepository = interCityBusCacheRepository; + } + + public List getBusRemainTime(String depTerminalId, String arrTerminalId) { + Version version = versionRepository.getByType(VersionType.EXPRESS); + + if(isCacheExpired(version, clock)){ + getIntercityBusArrivalInfoByOpenApi(); + } + + return getInterCityBusArrivalInfoByCache(depTerminalId, arrTerminalId); + } + + private List getInterCityBusArrivalInfoByCache(String depTerminalId, String arrTerminalId){ + String busRoute = depTerminalId + ":" + arrTerminalId; + Optional intercityBusCache = intercityBusCacheRepository.findById(busRoute); + return intercityBusCache.map(busCache -> busCache.getBusInfos().stream().map(IntercityBusRemainTime::from).toLIst()) + .orElseGet(ArrayList::new); + } + + private void getIntercityBusArrivalInfoByOpenApi() { + List> arrivalInfosList = IntercityBusStationNode.getIds().stream() + .map(routeList -> getOpenApiResponse(routeList.depTerminalId(), routeList.depTerminalId())) + .map(this::extractBusArrivalInfo) + .toList(); + + LocalDateTime updatedAt = LocalDateTime.now(clock); + + for (List arrivalInfos : arrivalInfosList ) { + if (arrivalInfos.isEmpty()){ + continue; + } + intercityBusCacheRepository.save( + IntercityBusCache.create( + new IntercityBusRoute(arrivalInfos.get(0).depPlaceNm(), arrivalInfos.get(0).arrPlaceNm()), + arrivalInfos.stream() + .map(busArrivalInfo -> IntercityBusCacheInfo.from(busArrivalInfo, updatedAt)) + .toList() + ) + ); + } + + versionRepository.getByType(VersionType.EXPRESS).update(clock); + } + + public String getOpenApiResponse(String depTerminalId, String arrTerminalId){ + try { + URL url = new URL(getRequestURL(depTerminalId, arrTerminalId)); + HttpURLConnection con = (HttpURLConnection)url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Content-type", "application/json"); + + BufferedReader input; + if(con.getResponseCode() >= 200 && con.getResponseCode() <= 300) { + input = new BufferedReader(new InputStreamReader(con.getInputStream())); + } else { + input = new BufferedReader(new InputStreamReader(con.getErrorStream())); + } + + StringBuilder response = new StringBuilder(); + String line; + while((line = input.readLine()) != null) { + response.append(line); + } + input.close(); + con.disconnect(); + return response.toString(); + } catch (IOException | NullPointerException e) { + return null; + } + } + + private String getRequestURL(String depTerminalId, String arrTerminalId) throws UnsupportedEncodingException { + String url = "http://apis.data.go.kr/1613000/SuburbsBusInfoService/getStrtpntAlocFndSuberbsBusInfo"; + String contentCount = "30"; + StringBuilder urlBuilder = new StringBuilder(url); + urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(openApiKey, ENCODE_TYPE)); + urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); + urlBuilder.append("&" + encode("depTerminalId", ENCODE_TYPE) + "=" + encode(depTerminalId, ENCODE_TYPE)); + urlBuilder.append("&" + encode("arrTerminalId", ENCODE_TYPE) + "=" + encode(arrTerminalId, ENCODE_TYPE)); + urlBuilder.append( + "&" + encode("depPlandTime", ENCODE_TYPE) + "=" + ZonedDateTime.now(ZoneId.of("Asia/Seoul")).format( + DateTimeFormatter.ofPattern("yyyyMMdd"))); + urlBuilder.append("&_type=json"); + return urlBuilder.toString(); + } + + private List extractBusArrivalInfo(String jsonResponse) { + List result = new ArrayList<>(); + + try { + JsonObject response = JsonParser.parseString(jsonResponse) + .getAsJsonObject() + .get("response") + .getAsJsonObject(); + BusOpenApiResultCode.validateResponse(response); + JsonObject body = response.get("body").getAsJsonObject(); + + if (body.get("totalCount").getAsLong() == 0) { + return result; + } + + JsonElement item = body.get("items").getAsJsonObject().get("item"); + if (item.isJsonArray()) { + return gson.fromJson(item, arrivalInfoType); + } + if (item.isJsonObject()) { + result.add(gson.fromJson(item, IntercityBusArrival.class)); + } + return result; + } catch (JsonSyntaxException e) { + return result; + } + } + + public boolean isCacheExpired(Version version, Clock clock) { // 1시간 단위로 수정 필요 + Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); + return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); } } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index f11dad223..c37c4093d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -25,8 +25,8 @@ import in.koreatech.koin.domain.bus.model.enums.BusType; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.mongo.Route; -import in.koreatech.koin.domain.bus.model.redis.BusCache; import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; @@ -164,7 +164,7 @@ void getNextCityBusRemainTimeRedis() { cityBusCacheRepository.save( CityBusCache.create( depart.getNodeId(direction), - List.of(BusCache.from( + List.of(CityBusCacheInfo.from( CityBusArrival.builder() .routeno(busNumber) .arrtime(remainTime) From 3e4949afee51689ba73d75a2fd5fe762afd6561a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 04:06:05 +0900 Subject: [PATCH 105/123] =?UTF-8?q?feat:=20=EC=8B=9C=EC=99=B8=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 , Choon0414 --- .../koin/domain/bus/controller/BusApi.java | 8 +- .../domain/bus/controller/BusController.java | 8 +- .../controller/BusStationEnumConverter.java | 19 ++ .../bus/controller/BusTypeEnumConverter.java | 19 ++ .../domain/bus/dto/BusRemainTimeRequest.java | 11 + .../domain/bus/dto/BusRemainTimeResponse.java | 9 +- .../domain/bus/dto/ExpressBusRemainTime.java | 22 ++ .../domain/bus/dto/ExpressBusTimeTable.java | 29 +++ .../domain/bus/model/IntercityBusRoute.java | 15 -- .../bus/model/{ => city}/CityBusArrival.java | 2 +- .../model/{redis => city}/CityBusCache.java | 2 +- .../{redis => city}/CityBusCacheInfo.java | 3 +- .../model/{ => city}/CityBusRemainTime.java | 4 +- .../domain/bus/model/enums/BusApiType.java | 26 -- .../bus/model/enums/BusOpenApiResultCode.java | 4 - .../domain/bus/model/enums/BusStation.java | 3 + .../koin/domain/bus/model/enums/BusType.java | 3 + .../model/enums/IntercityBusStationNode.java | 35 --- .../ExpressBusArrival.java} | 4 +- .../bus/model/express/ExpressBusCache.java | 48 ++++ .../model/express/ExpressBusCacheInfo.java | 11 + .../bus/model/express/ExpressBusRoute.java | 8 + .../model/express/ExpressBusStationNode.java | 30 +++ .../bus/model/redis/IntercityBusCache.java | 47 ---- .../model/redis/IntercityBusCacheInfo.java | 22 -- .../repository/CityBusCacheRepository.java | 2 +- ...ry.java => ExpressBusCacheRepository.java} | 10 +- .../koin/domain/bus/service/BusService.java | 41 ++-- .../domain/bus/util/CityBusOpenApiClient.java | 22 +- .../bus/util/ExpressBusOpenApiClient.java | 226 ++++++++++++++++++ .../bus/util/IntercityBusOpenApiClient.java | 195 --------------- .../koin/global/config/WebConfig.java | 4 + .../koreatech/koin/acceptance/BusApiTest.java | 26 +- 33 files changed, 512 insertions(+), 406 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusStationEnumConverter.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/controller/BusTypeEnumConverter.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java rename src/main/java/in/koreatech/koin/domain/bus/model/{ => city}/CityBusArrival.java (93%) rename src/main/java/in/koreatech/koin/domain/bus/model/{redis => city}/CityBusCache.java (96%) rename src/main/java/in/koreatech/koin/domain/bus/model/{redis => city}/CityBusCacheInfo.java (89%) rename src/main/java/in/koreatech/koin/domain/bus/model/{ => city}/CityBusRemainTime.java (91%) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java rename src/main/java/in/koreatech/koin/domain/bus/model/{IntercityBusArrival.java => express/ExpressBusArrival.java} (80%) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCacheInfo.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusRoute.java create mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusStationNode.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java rename src/main/java/in/koreatech/koin/domain/bus/repository/{IntercityBusCacheRepository.java => ExpressBusCacheRepository.java} (50%) create mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java index ab749d584..6f60c9fda 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java @@ -5,6 +5,8 @@ import org.springframework.web.bind.annotation.RequestParam; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.model.enums.BusStation; +import in.koreatech.koin.domain.bus.model.enums.BusType; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -25,8 +27,8 @@ public interface BusApi { @Operation(summary = "이번 / 다음 버스 남은 시간 조회") @GetMapping("/bus") ResponseEntity getBusRemainTime( - @Parameter(description = "버스 종류(city, express, shuttle, commuting)") @RequestParam(value = "bus_type") String busType, - @Parameter(description = "koreatech, station, terminal") @RequestParam String depart, - @Parameter(description = "koreatech, station, terminal") @RequestParam String arrival + @Parameter(description = "버스 종류(city, express, shuttle, commuting)") @RequestParam(value = "bus_type") BusType busType, + @Parameter(description = "koreatech, station, terminal") @RequestParam BusStation depart, + @Parameter(description = "koreatech, station, terminal") @RequestParam BusStation arrival ); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java index 4f4f60d8f..a6f968943 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java @@ -6,6 +6,8 @@ import org.springframework.web.bind.annotation.RestController; import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse; +import in.koreatech.koin.domain.bus.model.enums.BusStation; +import in.koreatech.koin.domain.bus.model.enums.BusType; import in.koreatech.koin.domain.bus.service.BusService; import lombok.RequiredArgsConstructor; @@ -17,9 +19,9 @@ public class BusController implements BusApi { @GetMapping("/bus") public ResponseEntity getBusRemainTime( - @RequestParam(value = "bus_type") String busType, - @RequestParam String depart, - @RequestParam String arrival + @RequestParam(value = "bus_type") BusType busType, + @RequestParam BusStation depart, + @RequestParam BusStation arrival ) { BusRemainTimeResponse busRemainTime = busService.getBusRemainTime(busType, depart, arrival); return ResponseEntity.ok().body(busRemainTime); diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusStationEnumConverter.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusStationEnumConverter.java new file mode 100644 index 000000000..7cab37ea2 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusStationEnumConverter.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.domain.bus.controller; + +import java.util.Arrays; + +import org.springframework.core.convert.converter.Converter; + +import in.koreatech.koin.domain.bus.model.enums.BusStation; +import in.koreatech.koin.global.domain.upload.exception.ImageUploadDomainNotFoundException; + +public class BusStationEnumConverter implements Converter { + + @Override + public BusStation convert(String source) { + return Arrays.stream(BusStation.values()) + .filter(it -> it.name().equalsIgnoreCase(source)) + .findAny() + .orElseThrow(() -> ImageUploadDomainNotFoundException.withDetail("source: " + source)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/controller/BusTypeEnumConverter.java b/src/main/java/in/koreatech/koin/domain/bus/controller/BusTypeEnumConverter.java new file mode 100644 index 000000000..ec747e504 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/controller/BusTypeEnumConverter.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.domain.bus.controller; + +import java.util.Arrays; + +import org.springframework.core.convert.converter.Converter; + +import in.koreatech.koin.domain.bus.model.enums.BusType; +import in.koreatech.koin.global.domain.upload.exception.ImageUploadDomainNotFoundException; + +public class BusTypeEnumConverter implements Converter { + + @Override + public BusType convert(String source) { + return Arrays.stream(BusType.values()) + .filter(it -> it.name().equalsIgnoreCase(source)) + .findAny() + .orElseThrow(() -> ImageUploadDomainNotFoundException.withDetail("source: " + source)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java new file mode 100644 index 000000000..5ebbadc26 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.bus.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(SnakeCaseStrategy.class) +public record BusRemainTimeRequest( + +) { + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index e5a90e6f4..5fcd2f887 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.city.CityBusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusType; -import in.koreatech.koin.domain.bus.model.CityBusRemainTime; @JsonNaming(SnakeCaseStrategy.class) public record BusRemainTimeResponse( @@ -18,6 +18,13 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { + public BusRemainTimeResponse(BusType busType, ExpressBusRemainTime remainTimes) { + this( + busType.name().toLowerCase(), + InnerBusResponse.of(List.of(remainTimes), 0, null), + null); + } + public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.name().toLowerCase(), diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java new file mode 100644 index 000000000..d0c460fd4 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java @@ -0,0 +1,22 @@ +package in.koreatech.koin.domain.bus.dto; + +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +public class ExpressBusRemainTime extends BusRemainTime { + + private final String busType; + private final InnerRemainTime nextBus; + private final InnerRemainTime nowBus; + + public record InnerRemainTime( + Long busNumber, + Long remainTime + ) { + + } + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java new file mode 100644 index 000000000..d4b225573 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java @@ -0,0 +1,29 @@ +package in.koreatech.koin.domain.bus.dto; + +import static java.time.format.DateTimeFormatter.ofPattern; + +import java.time.LocalTime; + +import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; +import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; + +public record ExpressBusTimeTable( + LocalTime departure, + LocalTime arrival, + int charge +) { + + public static ExpressBusTimeTable from(ExpressBusArrival expressBusArrival) { + LocalTime departure = LocalTime.parse(expressBusArrival.depPlandTime(), ofPattern("yyyyMMddHHmm")); + LocalTime arrival = LocalTime.parse(expressBusArrival.arrPlandTime(), ofPattern("yyyyMMddHHmm")); + int charge = expressBusArrival.charge(); + return new ExpressBusTimeTable(departure, arrival, charge); + } + + public static ExpressBusTimeTable from(ExpressBusCacheInfo expressBusCacheInfo) { + LocalTime departure = expressBusCacheInfo.departureTime(); + LocalTime arrival = expressBusCacheInfo.arrivalTime(); + int charge = expressBusCacheInfo.charge(); + return new ExpressBusTimeTable(departure, arrival, charge); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java b/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java deleted file mode 100644 index cdacf8982..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusRoute.java +++ /dev/null @@ -1,15 +0,0 @@ -package in.koreatech.koin.domain.bus.model; - -import in.koreatech.koin.domain.bus.model.enums.IntercityBusStationNode; - -public record IntercityBusRoute( - String depTerminalId, // 출발지 - String arrTerminalId // 도착지 -) { - public static IntercityBusRoute from(IntercityBusArrival intercityBusArrival) { - return new IntercityBusRoute( - IntercityBusStationNode.getId(intercityBusArrival.depPlaceNm()), - IntercityBusStationNode.getId(intercityBusArrival.arrPlaceNm()) - ); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java similarity index 93% rename from src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java rename to src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java index 7fb5e39ea..495961d5a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusArrival.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.city; import lombok.Builder; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java similarity index 96% rename from src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java rename to src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java index ee0720a23..6759598c6 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.bus.model.redis; +package in.koreatech.koin.domain.bus.model.city; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java similarity index 89% rename from src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java rename to src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java index 5de65810e..d2d70ff29 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/CityBusCacheInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java @@ -1,10 +1,9 @@ -package in.koreatech.koin.domain.bus.model.redis; +package in.koreatech.koin.domain.bus.model.city; import java.time.LocalDateTime; import java.time.LocalTime; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.CityBusArrival; public record CityBusCacheInfo( Long busNumber, diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java similarity index 91% rename from src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java rename to src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java index ad1da782c..4f7ba7c4f 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/CityBusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java @@ -1,9 +1,9 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.city; import java.time.LocalTime; import java.util.Objects; -import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; +import in.koreatech.koin.domain.bus.model.BusRemainTime; import lombok.Getter; import lombok.experimental.SuperBuilder; diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java deleted file mode 100644 index f2a974325..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java +++ /dev/null @@ -1,26 +0,0 @@ -package in.koreatech.koin.domain.bus.model.enums; - -import java.util.Arrays; - -import in.koreatech.koin.domain.bus.exception.ApiTypeNotFoundException; -import lombok.Getter; - -@Getter -public enum BusApiType { - CITY("cityBusOpenApiClient"), - EXPRESS("intercityBusOpenApiClient"), - ; - - private final String value; - - BusApiType(String bean) { - this.value = bean; - } - - public static BusApiType from(BusType value) { - return Arrays.stream(values()) - .filter(busApiType -> busApiType.toString().equalsIgnoreCase(value.toString())) - .findAny() - .orElseThrow(() -> ApiTypeNotFoundException.withDetail("apiType: " + value)); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java index f2a3ee19b..fdc8f5bd8 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusOpenApiResultCode.java @@ -28,18 +28,14 @@ public enum BusOpenApiResultCode { public static void validateResponse(JsonObject response) { String resultCode = response.get("header").getAsJsonObject().get("resultCode").getAsString(); - String errorMessage = ""; - if (!resultCode.equals(SERVICE_SUCCESS.code)) { Optional code = Arrays.stream(BusOpenApiResultCode.values()) .filter(busOpenApiResultCode -> busOpenApiResultCode.code.equals(resultCode)) .findFirst(); - if (code.isPresent()) { errorMessage = code.get().message; } - String resultMessage = response.get("header").getAsJsonObject().get("resultMsg").getAsString(); throw BusOpenApiException.withDetail(errorMessage + " resultMsg: " + resultMessage); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java index c82047b15..289030ad0 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusStation.java @@ -3,6 +3,8 @@ import java.util.Arrays; import java.util.List; +import com.fasterxml.jackson.annotation.JsonCreator; + import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; import lombok.Getter; @@ -21,6 +23,7 @@ public enum BusStation { this.node = node; } + @JsonCreator public static BusStation from(String busStationName) { return Arrays.stream(values()) .filter(busStation -> busStation.name().equalsIgnoreCase(busStationName)) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java index 3a1d106e9..3bb4d017c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusType.java @@ -2,6 +2,8 @@ import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonCreator; + import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException; import lombok.Getter; @@ -13,6 +15,7 @@ public enum BusType { COMMUTING, ; + @JsonCreator public static BusType from(String busTypeName) { return Arrays.stream(values()) .filter(busType -> busType.name().equalsIgnoreCase(busTypeName)) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java deleted file mode 100644 index f6e2fb238..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/IntercityBusStationNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package in.koreatech.koin.domain.bus.model.enums; - -import java.util.ArrayList; -import java.util.List; - -import in.koreatech.koin.domain.bus.model.IntercityBusRoute; -import lombok.Getter; - -/** - * OpenApi 상세: 국토교통부_전국 버스정류장 위치정보 (버스 정류장 노드 ID) - * https://www.data.go.kr/data/15067528/fileData.do - */ -@Getter -public enum IntercityBusStationNode { - KOREATECH("NAI3125301"), // 코리아텍 - TERMINAL("NAI3112001"), // 종합터미널 - ; - - private String stationId; - - IntercityBusStationNode(String stationId) { - this.stationId = stationId; - } - - public static String getId(String stationName){ - return valueOf(stationName).getStationId(); - } - - public static List getIds() { - List routeList = new ArrayList<>(); - routeList.add(new IntercityBusRoute(KOREATECH.getStationId(), TERMINAL.getStationId())); - routeList.add(new IntercityBusRoute(TERMINAL.getStationId(), KOREATECH.getStationId())); - return routeList; - } -} \ No newline at end of file diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java similarity index 80% rename from src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java rename to src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java index 31ce53e1a..84e1c1e58 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/IntercityBusArrival.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java @@ -1,9 +1,9 @@ -package in.koreatech.koin.domain.bus.model; +package in.koreatech.koin.domain.bus.model.express; import lombok.Builder; @Builder -public record IntercityBusArrival( +public record ExpressBusArrival( String arrPlaceNm, // 도착지 String arrPlandTime, // 도착 시간 String depPlaceNm, // 출발지 diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java new file mode 100644 index 000000000..16af64df7 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java @@ -0,0 +1,48 @@ +package in.koreatech.koin.domain.bus.model.express; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; + +@Getter +@RedisHash("expressBus") +public class ExpressBusCache { + + private static final long CACHE_EXPIRE_HOUR = 1L; + + @Id + private String id; + + private List busInfos; + + @TimeToLive(unit = TimeUnit.HOURS) + private final Long expiration; + + @Builder + private ExpressBusCache(String id, List busInfos) { + this.id = id; + this.busInfos = busInfos; + this.expiration = CACHE_EXPIRE_HOUR; + } + + public static ExpressBusCache create(ExpressBusRoute route, List busInfos) { + return ExpressBusCache.builder() + .id(generateId(route)) + .busInfos(busInfos) + .build(); + } + + public static long getCacheExpireHour() { + return CACHE_EXPIRE_HOUR; + } + + public static String generateId(ExpressBusRoute route) { + return String.format("%s:%s", route.depTerminalName(), route.arrTerminalName()); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCacheInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCacheInfo.java new file mode 100644 index 000000000..f831ed9be --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCacheInfo.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.bus.model.express; + +import java.time.LocalTime; + +public record ExpressBusCacheInfo( + LocalTime departureTime, + LocalTime arrivalTime, + int charge +) { + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusRoute.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusRoute.java new file mode 100644 index 000000000..925da3fd3 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusRoute.java @@ -0,0 +1,8 @@ +package in.koreatech.koin.domain.bus.model.express; + +public record ExpressBusRoute( + String depTerminalName, // 출발지 + String arrTerminalName // 도착지 +) { + +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusStationNode.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusStationNode.java new file mode 100644 index 000000000..b810bb4ce --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusStationNode.java @@ -0,0 +1,30 @@ +package in.koreatech.koin.domain.bus.model.express; + +import java.util.Arrays; + +import in.koreatech.koin.domain.bus.exception.BusStationNotFoundException; +import lombok.Getter; + +/** + * OpenApi 상세: 국토교통부_전국 버스정류장 위치정보 (버스 정류장 노드 ID) + * https://www.data.go.kr/data/15067528/fileData.do + */ +@Getter +public enum ExpressBusStationNode { + KOREATECH("NAI3125301"), // 코리아텍 + TERMINAL("NAI3112001"), // 종합터미널 + ; + + private final String stationId; + + ExpressBusStationNode(String stationId) { + this.stationId = stationId; + } + + public static ExpressBusStationNode from(String stationName) { + return Arrays.stream(values()). + filter(it -> it.name().equalsIgnoreCase(stationName)) + .findAny() + .orElseThrow(() -> BusStationNotFoundException.withDetail("stationName: " + stationName)); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java deleted file mode 100644 index 288e6ba9f..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCache.java +++ /dev/null @@ -1,47 +0,0 @@ -package in.koreatech.koin.domain.bus.model.redis; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.TimeToLive; - -import in.koreatech.koin.domain.bus.model.IntercityBusRoute; -import jakarta.persistence.Id; -import lombok.Builder; -import lombok.Getter; - -@Getter -@RedisHash("intercityBus") -public class IntercityBusCache { - - private static final long CACHE_EXPIRE_HOUR = 1L; - - @Id - private String id; - - private final List busInfos = new ArrayList<>(); - - @TimeToLive(unit = TimeUnit.HOURS) - private final Long expiration; - - @Builder - private IntercityBusCache(String id, List busInfos, Long expiration){ - this.id = id; - this.busInfos.addAll(busInfos); - this.expiration = expiration; - } - - public static IntercityBusCache create(IntercityBusRoute route, List busInfos){ - return IntercityBusCache.builder() - .id(route.depTerminalId() + route.arrTerminalId()) - .busInfos(busInfos) - .expiration(CACHE_EXPIRE_HOUR) - .build(); - } - - public static long getCacheExpireHour() { - return CACHE_EXPIRE_HOUR; - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java deleted file mode 100644 index f2b0a8d05..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/IntercityBusCacheInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package in.koreatech.koin.domain.bus.model.redis; - -import java.time.LocalDateTime; -import java.time.LocalTime; - -import in.koreatech.koin.domain.bus.model.IntercityBusArrival; -import in.koreatech.koin.domain.bus.model.IntercityBusRoute; - -public record IntercityBusCacheInfo ( - IntercityBusRoute intercityBusRoute, - LocalTime remainTime -) { - public static IntercityBusCacheInfo from( - IntercityBusArrival busArrivalInfo, - LocalDateTime updatedAt - ) { - return new IntercityBusCacheInfo( - IntercityBusRoute.from(busArrivalInfo), - updatedAt.plusSeconds(busArrivalInfo).toLocalTime() - ); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java index 091829dab..fa3bb1acf 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/CityBusCacheRepository.java @@ -6,7 +6,7 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; +import in.koreatech.koin.domain.bus.model.city.CityBusCache; public interface CityBusCacheRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java similarity index 50% rename from src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java rename to src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java index f5640733a..70fd50db8 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/IntercityBusCacheRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java @@ -5,15 +5,15 @@ import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; -import in.koreatech.koin.domain.bus.model.redis.IntercityBusCache; +import in.koreatech.koin.domain.bus.model.express.ExpressBusCache; -public interface IntercityBusCacheRepository extends Repository { +public interface ExpressBusCacheRepository extends Repository { - IntercityBusCache save(IntercityBusCache intercityBusCache); + ExpressBusCache save(ExpressBusCache expressBusCache); - Optional findById(String busRoute); + Optional findById(String busRoute); - default IntercityBusCache getById(String busRoute){ + default ExpressBusCache getById(String busRoute){ return findById(busRoute).orElseThrow(() -> BusCacheNotFoundException.withDetail("busRoute: " + busRoute)); } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index 3fc714d99..d318d7447 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -1,7 +1,6 @@ package in.koreatech.koin.domain.bus.service; import java.time.Clock; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -14,11 +13,10 @@ import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.IntercityBusStationNode; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient; -import in.koreatech.koin.domain.bus.util.IntercityBusOpenApiClient; +import in.koreatech.koin.domain.bus.util.ExpressBusOpenApiClient; import lombok.RequiredArgsConstructor; @Service @@ -29,25 +27,28 @@ public class BusService { private final Clock clock; private final BusRepository busRepository; private final CityBusOpenApiClient cityBusOpenApiClient; - private final IntercityBusOpenApiClient intercityBusOpenApiClient; + private final ExpressBusOpenApiClient expressBusOpenApiClient; @Transactional - public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departName, String arrivalName) { - BusType busType = BusType.from(busTypeName); - BusStation depart = BusStation.from(departName); - BusStation arrival = BusStation.from(arrivalName); - BusDirection direction = BusStation.getDirection(depart, arrival); + public BusRemainTimeResponse getBusRemainTime(BusType busType, BusStation depart, BusStation arrival) { + // 출발지 == 도착지면 예외 validateBusCourse(depart, arrival); - List remainTimes = new ArrayList<>(); if (busType == BusType.CITY) { - remainTimes = cityBusOpenApiClient.getBusRemainTime(depart.getNodeId(direction)); - } else if (busType == BusType.EXPRESS) { - remainTimes = intercityBusOpenApiClient.getBusRemainTime(IntercityBusStationNode.getId(departName), - IntercityBusStationNode.getId(arrivalName)); - } else if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { + // 시내버스에서 상행, 하행 구분할때 사용하는 로직 + BusDirection direction = BusStation.getDirection(depart, arrival); + var remainTimes = cityBusOpenApiClient.getBusRemainTime(depart.getNodeId(direction)); + return toResponse(busType, remainTimes); + } + + if (busType == BusType.EXPRESS) { + var remainTimes = expressBusOpenApiClient.getBusRemainTime(depart.name(), arrival.name()); + return new BusRemainTimeResponse(busType, remainTimes); + } + + if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { List busCourses = busRepository.findByBusType(busType.name().toLowerCase()); - remainTimes = busCourses.stream() + var remainTimes = busCourses.stream() .map(BusCourse::getRoutes) .flatMap(routes -> routes.stream() @@ -58,8 +59,13 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN .distinct() .sorted() .toList(); + return toResponse(busType, remainTimes); } + throw new IllegalArgumentException("Invalid bus type: " + busType); + } + + private BusRemainTimeResponse toResponse(BusType busType, List remainTimes) { return BusRemainTimeResponse.of( busType, remainTimes.stream() @@ -72,7 +78,8 @@ public BusRemainTimeResponse getBusRemainTime(String busTypeName, String departN private void validateBusCourse(BusStation depart, BusStation arrival) { if (depart.equals(arrival)) { - throw BusIllegalStationException.withDetail("depart: " + depart.name() + ", arrival: " + arrival.name()); + throw BusIllegalStationException.withDetail( + "depart: " + depart.name() + ", arrivalTime: " + arrival.name()); } } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index d9d7c6861..1a5c7289a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -14,6 +14,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -29,12 +30,12 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import in.koreatech.koin.domain.bus.model.CityBusArrival; -import in.koreatech.koin.domain.bus.model.CityBusRemainTime; +import in.koreatech.koin.domain.bus.model.city.CityBusArrival; +import in.koreatech.koin.domain.bus.model.city.CityBusCache; +import in.koreatech.koin.domain.bus.model.city.CityBusCacheInfo; +import in.koreatech.koin.domain.bus.model.city.CityBusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStationNode; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; -import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; @@ -78,7 +79,7 @@ public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); if (isCacheExpired(version, clock)) { - getAllCityBusArrivalInfoByOpenApi(); + storeAllCityBusArrivalInfoByOpenApi(); } return getCityBusArrivalInfoByCache(nodeId); @@ -86,12 +87,13 @@ public List getBusRemainTime(String nodeId) { private List getCityBusArrivalInfoByCache(String nodeId) { Optional cityBusCache = cityBusCacheRepository.findById(nodeId); - - return cityBusCache.map(busCache -> busCache.getBusInfos().stream().map(CityBusRemainTime::from).toList()) - .orElseGet(ArrayList::new); + return cityBusCache.map(busCache -> busCache.getBusInfos().stream() + .map(CityBusRemainTime::from) + .toList()) + .orElseGet(Collections::emptyList); } - private void getAllCityBusArrivalInfoByOpenApi() { + private void storeAllCityBusArrivalInfoByOpenApi() { List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) @@ -125,7 +127,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { public String getOpenApiResponse(String nodeId) { try { URL url = new URL(getRequestURL(CHEONAN_CITY_CODE, nodeId)); - HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-type", "application/json"); diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java new file mode 100644 index 000000000..42735d133 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -0,0 +1,226 @@ +package in.koreatech.koin.domain.bus.util; + +import static in.koreatech.koin.domain.bus.model.enums.BusType.EXPRESS; +import static java.time.format.DateTimeFormatter.ofPattern; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.lang.reflect.Type; +import java.time.Clock; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime; +import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime.InnerRemainTime; +import in.koreatech.koin.domain.bus.dto.ExpressBusTimeTable; +import in.koreatech.koin.domain.bus.model.BusRemainTime; +import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; +import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; +import in.koreatech.koin.domain.bus.model.express.ExpressBusCache; +import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; +import in.koreatech.koin.domain.bus.model.express.ExpressBusRoute; +import in.koreatech.koin.domain.bus.model.express.ExpressBusStationNode; +import in.koreatech.koin.domain.bus.repository.ExpressBusCacheRepository; +import in.koreatech.koin.domain.version.model.Version; +import in.koreatech.koin.domain.version.model.VersionType; +import in.koreatech.koin.domain.version.repository.VersionRepository; + +/** + * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 + * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098541 + */ +@Component +@Transactional(readOnly = true) +public class ExpressBusOpenApiClient extends BusOpenApiClient { + + private static final String OPEN_API_URL = "https://apis.data.go.kr/1613000/SuburbsBusInfoService/getStrtpntAlocFndSuberbsBusInfo"; + private static final Type ARRIVAL_INFO_TYPE = new TypeToken>() { + }.getType(); + + private final RestTemplate restTemplate; + private final VersionRepository versionRepository; + private final ExpressBusCacheRepository expressBusCacheRepository; + private final String openApiKey; + private final Gson gson; + private final Clock clock; + + public ExpressBusOpenApiClient( + @Value("${OPEN_API_KEY}") String openApiKey, + VersionRepository versionRepository, + Gson gson, + Clock clock, + ExpressBusCacheRepository expressBusCacheRepository, + RestTemplate restTemplate + ) { + this.openApiKey = openApiKey; + this.versionRepository = versionRepository; + this.gson = gson; + this.clock = clock; + this.expressBusCacheRepository = expressBusCacheRepository; + this.restTemplate = restTemplate; + } + + public ExpressBusRemainTime getBusRemainTime(String departName, String arrivalName) { + Version version = versionRepository.getByType(VersionType.EXPRESS); + if (isCacheExpired(version, clock)) { + storeRemainTimeByOpenApi(departName, arrivalName); + } + return getStoredRemainTime(departName, arrivalName); + } + + private ExpressBusRemainTime storeRemainTimeByOpenApi(String departName, String arrivalName) { + JsonObject busApiResponse = getBusApiResponse(departName, arrivalName); + List busArrivals = extractBusArrivalInfo(busApiResponse); + expressBusCacheRepository.save( + ExpressBusCache.create( + new ExpressBusRoute(departName, arrivalName), + busArrivals.stream() + .map(it -> new ExpressBusCacheInfo( + LocalTime.parse( + LocalDateTime.parse(it.depPlandTime(), ofPattern("yyyyMMddHHmm")) + .format(ofPattern("HH:mm")) + ), + LocalTime.parse( + LocalDateTime.parse(it.arrPlandTime(), ofPattern("yyyyMMddHHmm")) + .format(ofPattern("HH:mm")) + ), + it.charge() + )) + .toList() + )); + versionRepository.getByType(VersionType.EXPRESS).update(clock); + var now = LocalDateTime.now(clock); + return getExpressBusRemainTime( + busArrivals + .stream() + .map(ExpressBusTimeTable::from), + now + ); + } + + private JsonObject getBusApiResponse(String departName, String arrivalName) { + ExpressBusStationNode departNode = ExpressBusStationNode.from(departName); + ExpressBusStationNode arrivalNode = ExpressBusStationNode.from(arrivalName); + String contentCount = "30"; + var parameters = Map.of("serviceKey", openApiKey, + "numOfRows", contentCount, + "depTerminalName", departNode.getStationId(), + "arrTerminalName", arrivalNode.getStationId(), + "depPlandTime", LocalDateTime.now(clock).format(ofPattern("yyyyMMdd")), + "_type", "json" + ); + ResponseEntity forEntity = restTemplate.getForEntity(OPEN_API_URL, String.class, parameters); + return JsonParser.parseString(Objects.requireNonNull(forEntity.getBody())) + .getAsJsonObject(); + } + + private List extractBusArrivalInfo(JsonObject jsonObject) { + try { + var response = jsonObject.get("response").getAsJsonObject(); + BusOpenApiResultCode.validateResponse(response); + JsonObject body = response.get("body").getAsJsonObject(); + if (body.get("totalCount").getAsLong() == 0) { + return Collections.emptyList(); + } + JsonElement item = body.get("items").getAsJsonObject().get("item"); + List result = new ArrayList<>(); + if (item.isJsonArray()) { + return gson.fromJson(item, ARRIVAL_INFO_TYPE); + } + if (item.isJsonObject()) { + result.add(gson.fromJson(item, ExpressBusArrival.class)); + } + return result; + } catch (JsonSyntaxException e) { + return Collections.emptyList(); + } + } + + private ExpressBusRemainTime getStoredRemainTime(String departName, String arrivalName) { + String busCacheId = ExpressBusCache.generateId(new ExpressBusRoute(departName, arrivalName)); + ExpressBusCache expressBusCache = expressBusCacheRepository.getById(busCacheId); + if (Objects.isNull(expressBusCache)) { + return ExpressBusRemainTime.builder() + .busType(EXPRESS.name()) + .build(); + } + List busArrivals = expressBusCache.getBusInfos(); + LocalDateTime now = LocalDateTime.now(clock); + + return getExpressBusRemainTime( + busArrivals + .stream() + .map(ExpressBusTimeTable::from) + , now); + } + + private ExpressBusRemainTime getExpressBusRemainTime( + Stream busArrivals, + LocalDateTime now + ) { + List busTimes = busArrivals.toList(); + List closestFutureTimes = findClosestFutureTimes(LocalDateTime.now(clock), busTimes); + if (closestFutureTimes.isEmpty()) { + return + ExpressBusRemainTime.builder() + .busType(EXPRESS.name()) + .build(); + } else if (closestFutureTimes.size() == 1) { + return + ExpressBusRemainTime.builder() + .busType(EXPRESS.name()) + .nowBus(new InnerRemainTime(null, SECONDS.between(now, closestFutureTimes.get(0)))) + .nextBus(null) + .build(); + } + + LocalTime nowDepartureTime = closestFutureTimes.get(0); + LocalTime nextDepartureTime = closestFutureTimes.get(1); + + return ExpressBusRemainTime.builder() + .busType(EXPRESS.name()) + .nowBus(new InnerRemainTime(null, SECONDS.between(now, nowDepartureTime))) + .nextBus(new InnerRemainTime(null, SECONDS.between(now, nextDepartureTime))) + .build(); + } + + private List findClosestFutureTimes(LocalDateTime now, List arrivals) { + // 현재 시간 이후의 시간만 필터링 + List futureTimes = arrivals.stream() + .map(ExpressBusTimeTable::departure) + .filter(it -> it.isAfter(LocalTime.from(now))) + // 필터링된 시간을 현재 시간과의 차이(절대값)에 따라 정렬 + .sorted(Comparator.comparingLong(time -> MILLIS.between(now, time))) + .toList(); + + // 결과 리스트의 크기가 2보다 작을 수 있으므로, 실제 크기와 2 중 더 작은 값을 상한으로 사용 + return futureTimes.subList(0, 2); + } + + @Override + public boolean isCacheExpired(Version version, Clock clock) { + Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); + return duration.toSeconds() < 0 || ExpressBusCache.getCacheExpireHour() <= duration.toHours(); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java deleted file mode 100644 index 756c7b530..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/util/IntercityBusOpenApiClient.java +++ /dev/null @@ -1,195 +0,0 @@ -package in.koreatech.koin.domain.bus.util; - -import static java.net.URLEncoder.encode; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; -import java.net.HttpURLConnection; -import java.net.URL; -import java.time.Clock; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; - -import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.IntercityBusArrival; -import in.koreatech.koin.domain.bus.model.IntercityBusRoute; -import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; -import in.koreatech.koin.domain.bus.model.enums.IntercityBusStationNode; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; -import in.koreatech.koin.domain.bus.model.redis.IntercityBusCache; -import in.koreatech.koin.domain.bus.model.redis.IntercityBusCacheInfo; -import in.koreatech.koin.domain.bus.repository.IntercityBusCacheRepository; -import in.koreatech.koin.domain.version.model.Version; -import in.koreatech.koin.domain.version.model.VersionType; -import in.koreatech.koin.domain.version.repository.VersionRepository; - -/** - * OpenApi 상세: 국토교통부_(TAGO)_버스도착정보 - * https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15098541 - */ -@Component -@Transactional(readOnly = true) -public class IntercityBusOpenApiClient extends BusOpenApiClient { - - private static final String ENCODE_TYPE = "UTF-8"; - private static final Type arrivalInfoType = new TypeToken>() { - }.getType(); - - private final VersionRepository versionRepository; - private final IntercityBusCacheRepository intercityBusCacheRepository; - private final String openApiKey; - private final Gson gson; - private final Clock clock; - - public IntercityBusOpenApiClient( - @Value("${OPEN_API_KEY}") String openApiKey, - VersionRepository versionRepository, - Gson gson, - Clock clock, - IntercityBusCacheRepository interCityBusCacheRepository - ) { - this.openApiKey = openApiKey; - this.versionRepository = versionRepository; - this.gson = gson; - this.clock = clock; - this.intercityBusCacheRepository = interCityBusCacheRepository; - } - - public List getBusRemainTime(String depTerminalId, String arrTerminalId) { - Version version = versionRepository.getByType(VersionType.EXPRESS); - - if(isCacheExpired(version, clock)){ - getIntercityBusArrivalInfoByOpenApi(); - } - - return getInterCityBusArrivalInfoByCache(depTerminalId, arrTerminalId); - } - - private List getInterCityBusArrivalInfoByCache(String depTerminalId, String arrTerminalId){ - String busRoute = depTerminalId + ":" + arrTerminalId; - Optional intercityBusCache = intercityBusCacheRepository.findById(busRoute); - return intercityBusCache.map(busCache -> busCache.getBusInfos().stream().map(IntercityBusRemainTime::from).toLIst()) - .orElseGet(ArrayList::new); - } - - private void getIntercityBusArrivalInfoByOpenApi() { - List> arrivalInfosList = IntercityBusStationNode.getIds().stream() - .map(routeList -> getOpenApiResponse(routeList.depTerminalId(), routeList.depTerminalId())) - .map(this::extractBusArrivalInfo) - .toList(); - - LocalDateTime updatedAt = LocalDateTime.now(clock); - - for (List arrivalInfos : arrivalInfosList ) { - if (arrivalInfos.isEmpty()){ - continue; - } - intercityBusCacheRepository.save( - IntercityBusCache.create( - new IntercityBusRoute(arrivalInfos.get(0).depPlaceNm(), arrivalInfos.get(0).arrPlaceNm()), - arrivalInfos.stream() - .map(busArrivalInfo -> IntercityBusCacheInfo.from(busArrivalInfo, updatedAt)) - .toList() - ) - ); - } - - versionRepository.getByType(VersionType.EXPRESS).update(clock); - } - - public String getOpenApiResponse(String depTerminalId, String arrTerminalId){ - try { - URL url = new URL(getRequestURL(depTerminalId, arrTerminalId)); - HttpURLConnection con = (HttpURLConnection)url.openConnection(); - con.setRequestMethod("GET"); - con.setRequestProperty("Content-type", "application/json"); - - BufferedReader input; - if(con.getResponseCode() >= 200 && con.getResponseCode() <= 300) { - input = new BufferedReader(new InputStreamReader(con.getInputStream())); - } else { - input = new BufferedReader(new InputStreamReader(con.getErrorStream())); - } - - StringBuilder response = new StringBuilder(); - String line; - while((line = input.readLine()) != null) { - response.append(line); - } - input.close(); - con.disconnect(); - return response.toString(); - } catch (IOException | NullPointerException e) { - return null; - } - } - - private String getRequestURL(String depTerminalId, String arrTerminalId) throws UnsupportedEncodingException { - String url = "http://apis.data.go.kr/1613000/SuburbsBusInfoService/getStrtpntAlocFndSuberbsBusInfo"; - String contentCount = "30"; - StringBuilder urlBuilder = new StringBuilder(url); - urlBuilder.append("?" + encode("serviceKey", ENCODE_TYPE) + "=" + encode(openApiKey, ENCODE_TYPE)); - urlBuilder.append("&" + encode("numOfRows", ENCODE_TYPE) + "=" + encode(contentCount, ENCODE_TYPE)); - urlBuilder.append("&" + encode("depTerminalId", ENCODE_TYPE) + "=" + encode(depTerminalId, ENCODE_TYPE)); - urlBuilder.append("&" + encode("arrTerminalId", ENCODE_TYPE) + "=" + encode(arrTerminalId, ENCODE_TYPE)); - urlBuilder.append( - "&" + encode("depPlandTime", ENCODE_TYPE) + "=" + ZonedDateTime.now(ZoneId.of("Asia/Seoul")).format( - DateTimeFormatter.ofPattern("yyyyMMdd"))); - urlBuilder.append("&_type=json"); - return urlBuilder.toString(); - } - - private List extractBusArrivalInfo(String jsonResponse) { - List result = new ArrayList<>(); - - try { - JsonObject response = JsonParser.parseString(jsonResponse) - .getAsJsonObject() - .get("response") - .getAsJsonObject(); - BusOpenApiResultCode.validateResponse(response); - JsonObject body = response.get("body").getAsJsonObject(); - - if (body.get("totalCount").getAsLong() == 0) { - return result; - } - - JsonElement item = body.get("items").getAsJsonObject().get("item"); - if (item.isJsonArray()) { - return gson.fromJson(item, arrivalInfoType); - } - if (item.isJsonObject()) { - result.add(gson.fromJson(item, IntercityBusArrival.class)); - } - return result; - } catch (JsonSyntaxException e) { - return result; - } - } - - public boolean isCacheExpired(Version version, Clock clock) { // 1시간 단위로 수정 필요 - Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); - } -} diff --git a/src/main/java/in/koreatech/koin/global/config/WebConfig.java b/src/main/java/in/koreatech/koin/global/config/WebConfig.java index 0d9f78fc1..adbc905ac 100644 --- a/src/main/java/in/koreatech/koin/global/config/WebConfig.java +++ b/src/main/java/in/koreatech/koin/global/config/WebConfig.java @@ -9,6 +9,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import in.koreatech.koin.domain.bus.controller.BusStationEnumConverter; +import in.koreatech.koin.domain.bus.controller.BusTypeEnumConverter; import in.koreatech.koin.global.auth.AuthArgumentResolver; import in.koreatech.koin.global.auth.ExtractAuthenticationInterceptor; import in.koreatech.koin.global.auth.UserIdArgumentResolver; @@ -46,6 +48,8 @@ public void addArgumentResolvers(List resolvers) @Override public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new BusTypeEnumConverter()); + registry.addConverter(new BusStationEnumConverter()); registry.addConverter(new ImageUploadDomainEnumConverter()); } } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index c37c4093d..5c3a3253d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -19,14 +19,14 @@ import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.bus.model.CityBusArrival; +import in.koreatech.koin.domain.bus.model.city.CityBusArrival; +import in.koreatech.koin.domain.bus.model.city.CityBusCache; +import in.koreatech.koin.domain.bus.model.city.CityBusCacheInfo; import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.mongo.Route; -import in.koreatech.koin.domain.bus.model.redis.CityBusCache; -import in.koreatech.koin.domain.bus.model.redis.CityBusCacheInfo; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; @@ -124,11 +124,11 @@ void getNextShuttleBusRemainTime() { softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); - softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull(); + softly.assertThat((Long) response.body().jsonPath().get("now_bus.bus_number")).isNull(); softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); - softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull(); - softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull(); + softly.assertThat((Long) response.body().jsonPath().get("next_bus.bus_number")).isNull(); + softly.assertThat((Long) response.body().jsonPath().get("next_bus.remain_time")).isNull(); } ); } @@ -152,8 +152,6 @@ void getNextCityBusRemainTimeRedis() { .type("city_bus_timetable") .build() ); - System.out.println("=========="); - System.out.println(version.getUpdatedAt()); Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) .toInstant(); @@ -189,8 +187,8 @@ void getNextCityBusRemainTimeRedis() { softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); - softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber); - softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time")) + softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber); + softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time")) .isEqualTo( BusRemainTime.from(remainTime, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); softly.assertThat(response.body().jsonPath().getObject("next_bus.bus_number", Long.class)).isNull(); @@ -292,12 +290,12 @@ void getNextCityBusRemainTimeOpenApi() { softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); - softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400); - softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time")) + softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400); + softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time")) .isEqualTo( BusRemainTime.from(600L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); - softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405); - softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.remain_time")) + softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405); + softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.remain_time")) .isEqualTo( BusRemainTime.from(800L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); } From bbfcb231b8807ae99a42735acf7e31e35138795a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 04:53:36 +0900 Subject: [PATCH 106/123] =?UTF-8?q?feat:=20=EC=8B=9C=EC=99=B8=EB=B2=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dradnats1012 , Choon0414 --- .../domain/bus/dto/BusRemainTimeResponse.java | 10 --- .../domain/bus/dto/ExpressBusRemainTime.java | 11 ++- .../bus/model/express/ExpressBusCache.java | 10 +-- .../repository/ExpressBusCacheRepository.java | 7 +- .../koin/domain/bus/service/BusService.java | 4 +- .../bus/util/ExpressBusOpenApiClient.java | 67 +++------------- .../koreatech/koin/acceptance/BusApiTest.java | 76 ++++++++++++++++++- 7 files changed, 99 insertions(+), 86 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java index 5fcd2f887..4dab48b6c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeResponse.java @@ -18,13 +18,6 @@ public record BusRemainTimeResponse( InnerBusResponse nextBus ) { - public BusRemainTimeResponse(BusType busType, ExpressBusRemainTime remainTimes) { - this( - busType.name().toLowerCase(), - InnerBusResponse.of(List.of(remainTimes), 0, null), - null); - } - public static BusRemainTimeResponse of(BusType busType, List remainTimes, Clock clock) { return new BusRemainTimeResponse( busType.name().toLowerCase(), @@ -43,14 +36,11 @@ public static InnerBusResponse of(List remainTimes, int if (index < remainTimes.size()) { Long busNumber = null; Long remainTime = remainTimes.get(index).getRemainSeconds(clock); - if (remainTime != null && remainTimes.get(index) instanceof CityBusRemainTime cityBusRemainTime) { busNumber = cityBusRemainTime.getBusNumber(); } - return new InnerBusResponse(busNumber, remainTime); } - return null; } } diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java index d0c460fd4..e6b88a4a3 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java @@ -1,16 +1,19 @@ package in.koreatech.koin.domain.bus.dto; +import java.time.LocalTime; + import in.koreatech.koin.domain.bus.model.BusRemainTime; import lombok.Getter; -import lombok.experimental.SuperBuilder; @Getter -@SuperBuilder public class ExpressBusRemainTime extends BusRemainTime { private final String busType; - private final InnerRemainTime nextBus; - private final InnerRemainTime nowBus; + + public ExpressBusRemainTime(LocalTime busArrivalTime, String busType) { + super(busArrivalTime); + this.busType = busType; + } public record InnerRemainTime( Long busNumber, diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java index 16af64df7..9d780773a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java @@ -1,17 +1,15 @@ package in.koreatech.koin.domain.bus.model.express; import java.util.List; -import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.TimeToLive; import jakarta.persistence.Id; import lombok.Builder; import lombok.Getter; @Getter -@RedisHash("expressBus") +@RedisHash(value = "expressBus", timeToLive = 3600L) public class ExpressBusCache { private static final long CACHE_EXPIRE_HOUR = 1L; @@ -19,16 +17,12 @@ public class ExpressBusCache { @Id private String id; - private List busInfos; - - @TimeToLive(unit = TimeUnit.HOURS) - private final Long expiration; + private final List busInfos; @Builder private ExpressBusCache(String id, List busInfos) { this.id = id; this.busInfos = busInfos; - this.expiration = CACHE_EXPIRE_HOUR; } public static ExpressBusCache create(ExpressBusRoute route, List busInfos) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java b/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java index 70fd50db8..c256ee3e8 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java +++ b/src/main/java/in/koreatech/koin/domain/bus/repository/ExpressBusCacheRepository.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.bus.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; @@ -7,13 +8,15 @@ import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException; import in.koreatech.koin.domain.bus.model.express.ExpressBusCache; -public interface ExpressBusCacheRepository extends Repository { +public interface ExpressBusCacheRepository extends Repository { ExpressBusCache save(ExpressBusCache expressBusCache); Optional findById(String busRoute); - default ExpressBusCache getById(String busRoute){ + default ExpressBusCache getById(String busRoute) { return findById(busRoute).orElseThrow(() -> BusCacheNotFoundException.withDetail("busRoute: " + busRoute)); } + + List findAll(); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index d318d7447..71fa3ec1e 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -42,8 +42,8 @@ public BusRemainTimeResponse getBusRemainTime(BusType busType, BusStation depart } if (busType == BusType.EXPRESS) { - var remainTimes = expressBusOpenApiClient.getBusRemainTime(depart.name(), arrival.name()); - return new BusRemainTimeResponse(busType, remainTimes); + var remainTimes = expressBusOpenApiClient.getBusRemainTime(depart.name().toLowerCase(), arrival.name().toLowerCase()); + return BusRemainTimeResponse.of(busType, remainTimes, clock); } if (busType == BusType.SHUTTLE || busType == BusType.COMMUTING) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index 42735d133..a8a4fb0c6 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -2,8 +2,6 @@ import static in.koreatech.koin.domain.bus.model.enums.BusType.EXPRESS; import static java.time.format.DateTimeFormatter.ofPattern; -import static java.time.temporal.ChronoUnit.MILLIS; -import static java.time.temporal.ChronoUnit.SECONDS; import java.lang.reflect.Type; import java.time.Clock; @@ -12,11 +10,9 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -32,7 +28,6 @@ import com.google.gson.reflect.TypeToken; import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime; -import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime.InnerRemainTime; import in.koreatech.koin.domain.bus.dto.ExpressBusTimeTable; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; @@ -81,7 +76,7 @@ public ExpressBusOpenApiClient( this.restTemplate = restTemplate; } - public ExpressBusRemainTime getBusRemainTime(String departName, String arrivalName) { + public List getBusRemainTime(String departName, String arrivalName) { Version version = versionRepository.getByType(VersionType.EXPRESS); if (isCacheExpired(version, clock)) { storeRemainTimeByOpenApi(departName, arrivalName); @@ -89,7 +84,7 @@ public ExpressBusRemainTime getBusRemainTime(String departName, String arrivalNa return getStoredRemainTime(departName, arrivalName); } - private ExpressBusRemainTime storeRemainTimeByOpenApi(String departName, String arrivalName) { + private List storeRemainTimeByOpenApi(String departName, String arrivalName) { JsonObject busApiResponse = getBusApiResponse(departName, arrivalName); List busArrivals = extractBusArrivalInfo(busApiResponse); expressBusCacheRepository.save( @@ -110,12 +105,11 @@ private ExpressBusRemainTime storeRemainTimeByOpenApi(String departName, String .toList() )); versionRepository.getByType(VersionType.EXPRESS).update(clock); - var now = LocalDateTime.now(clock); return getExpressBusRemainTime( busArrivals .stream() - .map(ExpressBusTimeTable::from), - now + .map(ExpressBusTimeTable::from) + .toList() ); } @@ -157,65 +151,26 @@ private List extractBusArrivalInfo(JsonObject jsonObject) { } } - private ExpressBusRemainTime getStoredRemainTime(String departName, String arrivalName) { + private List getStoredRemainTime(String departName, String arrivalName) { String busCacheId = ExpressBusCache.generateId(new ExpressBusRoute(departName, arrivalName)); ExpressBusCache expressBusCache = expressBusCacheRepository.getById(busCacheId); if (Objects.isNull(expressBusCache)) { - return ExpressBusRemainTime.builder() - .busType(EXPRESS.name()) - .build(); + return Collections.emptyList(); } List busArrivals = expressBusCache.getBusInfos(); - LocalDateTime now = LocalDateTime.now(clock); - return getExpressBusRemainTime( busArrivals .stream() .map(ExpressBusTimeTable::from) - , now); + .toList()); } - private ExpressBusRemainTime getExpressBusRemainTime( - Stream busArrivals, - LocalDateTime now + private List getExpressBusRemainTime( + List busArrivals ) { - List busTimes = busArrivals.toList(); - List closestFutureTimes = findClosestFutureTimes(LocalDateTime.now(clock), busTimes); - if (closestFutureTimes.isEmpty()) { - return - ExpressBusRemainTime.builder() - .busType(EXPRESS.name()) - .build(); - } else if (closestFutureTimes.size() == 1) { - return - ExpressBusRemainTime.builder() - .busType(EXPRESS.name()) - .nowBus(new InnerRemainTime(null, SECONDS.between(now, closestFutureTimes.get(0)))) - .nextBus(null) - .build(); - } - - LocalTime nowDepartureTime = closestFutureTimes.get(0); - LocalTime nextDepartureTime = closestFutureTimes.get(1); - - return ExpressBusRemainTime.builder() - .busType(EXPRESS.name()) - .nowBus(new InnerRemainTime(null, SECONDS.between(now, nowDepartureTime))) - .nextBus(new InnerRemainTime(null, SECONDS.between(now, nextDepartureTime))) - .build(); - } - - private List findClosestFutureTimes(LocalDateTime now, List arrivals) { - // 현재 시간 이후의 시간만 필터링 - List futureTimes = arrivals.stream() - .map(ExpressBusTimeTable::departure) - .filter(it -> it.isAfter(LocalTime.from(now))) - // 필터링된 시간을 현재 시간과의 차이(절대값)에 따라 정렬 - .sorted(Comparator.comparingLong(time -> MILLIS.between(now, time))) + return busArrivals.stream() + .map(it -> new ExpressBusRemainTime(it.arrival(), EXPRESS.name().toLowerCase())) .toList(); - - // 결과 리스트의 크기가 2보다 작을 수 있으므로, 실제 크기와 2 중 더 작은 값을 상한으로 사용 - return futureTimes.subList(0, 2); } @Override diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 5c3a3253d..aaa886362 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -1,15 +1,17 @@ package in.koreatech.koin.acceptance; import static java.time.format.DateTimeFormatter.ofPattern; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.Mockito.when; import java.time.Clock; import java.time.Instant; +import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -25,10 +27,14 @@ import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.express.ExpressBusCache; +import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; +import in.koreatech.koin.domain.bus.model.express.ExpressBusRoute; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.mongo.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; +import in.koreatech.koin.domain.bus.repository.ExpressBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; @@ -47,6 +53,9 @@ class BusApiTest extends AcceptanceTest { @Autowired private CityBusCacheRepository cityBusCacheRepository; + @Autowired + private ExpressBusCacheRepository expressBusCacheRepository; + private final Instant UPDATED_AT = ZonedDateTime.parse( "2024-02-21 18:00:00 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z") @@ -120,7 +129,7 @@ void getNextShuttleBusRemainTime() { .statusCode(HttpStatus.OK.value()) .extract(); - SoftAssertions.assertSoftly( + assertSoftly( softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); @@ -183,7 +192,7 @@ void getNextCityBusRemainTimeRedis() { .statusCode(HttpStatus.OK.value()) .extract(); - SoftAssertions.assertSoftly( + assertSoftly( softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); @@ -286,7 +295,7 @@ void getNextCityBusRemainTimeOpenApi() { Version version = versionRepository.getByType(VersionType.CITY); - SoftAssertions.assertSoftly( + assertSoftly( softly -> { softly.assertThat(response.body().jsonPath().getString("bus_type")) .isEqualTo(busType.name().toLowerCase()); @@ -301,4 +310,63 @@ void getNextCityBusRemainTimeOpenApi() { } ); } + + @Test + @DisplayName("다음 시외버스까지 남은 시간을 조회한다. - Redis") + void getNextExpressBusRemainTimeRedis() { + final long remainTime = 600L; + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); + + BusType busType = BusType.from("express"); + BusStation depart = BusStation.from("terminal"); + BusStation arrival = BusStation.from("koreatech"); + + Version version = versionRepository.save( + Version.builder() + .version("20240_1711255839") + .type("express_bus_timetable") + .build() + ); + + Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant(); + + when(clock.instant()).thenReturn(requestedAt); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); + + expressBusCacheRepository.save( + ExpressBusCache.create( + new ExpressBusRoute("terminal", "koreatech"), + List.of( + new ExpressBusCacheInfo( + LocalTime.of(18, 10), + LocalTime.of(18, 20), + 1900 + ) + ) + ) + ); + ExtractableResponse response = RestAssured + .given() + .when() + .param("bus_type", busType.name().toLowerCase()) + .param("depart", depart.name()) + .param("arrival", arrival.name()) + .get("/bus") + .then() + .statusCode(HttpStatus.OK.value()) + .extract(); + + assertThat(response.asPrettyString()) + .isEqualTo(""" + { + "bus_type": "express", + "now_bus": { + "bus_number": null, + "remain_time": 1170 + }, + "next_bus": null + }"""); + } } From 39db36d043b595c8916553ab9b09de4dfdf339af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 05:32:15 +0900 Subject: [PATCH 107/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 Co-authored-by: dradnats1012 , Choon0414 --- .../in/koreatech/koin/acceptance/BusApiTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 65ed426dc..1c1b823f0 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -8,6 +8,7 @@ import java.time.Clock; import java.time.Instant; import java.time.LocalTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -316,9 +317,12 @@ void getNextCityBusRemainTimeOpenApi() { @Test @DisplayName("다음 시외버스까지 남은 시간을 조회한다. - Redis") void getNextExpressBusRemainTimeRedis() { - final long remainTime = 600L; - when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); + Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant(); + when(clock.getZone()).thenReturn(ZoneId.of("Asia/Seoul")); + when(clock.instant()).thenReturn(requestedAt); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); BusType busType = BusType.from("express"); BusStation depart = BusStation.from("terminal"); @@ -331,12 +335,6 @@ void getNextExpressBusRemainTimeRedis() { .build() ); - Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant(); - - when(clock.instant()).thenReturn(requestedAt); - when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); - expressBusCacheRepository.save( ExpressBusCache.create( new ExpressBusRoute("terminal", "koreatech"), From 80bb4a9e7987579aadd078089cfd18eabf363766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 05:47:53 +0900 Subject: [PATCH 108/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 Co-authored-by: dradnats1012 , Choon0414 --- .../koreatech/koin/acceptance/BusApiTest.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 1c1b823f0..2bc084e2f 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -1,5 +1,7 @@ package in.koreatech.koin.acceptance; +import static in.koreatech.koin.domain.version.model.VersionType.CITY; +import static in.koreatech.koin.domain.version.model.VersionType.EXPRESS; import static java.time.format.DateTimeFormatter.ofPattern; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -8,7 +10,6 @@ import java.time.Clock; import java.time.Instant; import java.time.LocalTime; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -38,7 +39,6 @@ import in.koreatech.koin.domain.bus.repository.CityBusCacheRepository; import in.koreatech.koin.domain.bus.repository.ExpressBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; -import in.koreatech.koin.domain.version.model.VersionType; import in.koreatech.koin.domain.version.repository.VersionRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -296,7 +296,7 @@ void getNextCityBusRemainTimeOpenApi() { .statusCode(HttpStatus.OK.value()) .extract(); - Version version = versionRepository.getByType(VersionType.CITY); + Version version = versionRepository.getByType(CITY); assertSoftly( softly -> { @@ -317,21 +317,16 @@ void getNextCityBusRemainTimeOpenApi() { @Test @DisplayName("다음 시외버스까지 남은 시간을 조회한다. - Redis") void getNextExpressBusRemainTimeRedis() { - Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) .toInstant(); - when(clock.getZone()).thenReturn(ZoneId.of("Asia/Seoul")); - when(clock.instant()).thenReturn(requestedAt); when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); - BusType busType = BusType.from("express"); BusStation depart = BusStation.from("terminal"); BusStation arrival = BusStation.from("koreatech"); - Version version = versionRepository.save( Version.builder() .version("20240_1711255839") - .type("express_bus_timetable") + .type(EXPRESS.getValue()) .build() ); @@ -351,8 +346,8 @@ void getNextExpressBusRemainTimeRedis() { .given() .when() .param("bus_type", busType.name().toLowerCase()) - .param("depart", depart.name()) - .param("arrival", arrival.name()) + .param("depart", depart.name().toLowerCase()) + .param("arrival", arrival.name().toLowerCase()) .get("/bus") .then() .statusCode(HttpStatus.OK.value()) From e5eaa517033042e687e912c2f641daa7b2444d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 06:01:55 +0900 Subject: [PATCH 109/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 Co-authored-by: dradnats1012 , Choon0414 --- .../bus/util/ExpressBusOpenApiClient.java | 2 +- .../koreatech/koin/acceptance/BusApiTest.java | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index a8a4fb0c6..98846fd11 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -176,6 +176,6 @@ private List getExpressBusRemainTime( @Override public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || ExpressBusCache.getCacheExpireHour() <= duration.toHours(); + return duration.toSeconds() < 0 || Duration.ofHours(ExpressBusCache.getCacheExpireHour()).toSeconds() <= duration.toSeconds(); } } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 2bc084e2f..7b5d2185d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -317,18 +317,25 @@ void getNextCityBusRemainTimeOpenApi() { @Test @DisplayName("다음 시외버스까지 남은 시간을 조회한다. - Redis") void getNextExpressBusRemainTimeRedis() { - Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant(); - when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); + BusType busType = BusType.from("express"); BusStation depart = BusStation.from("terminal"); BusStation arrival = BusStation.from("koreatech"); + Version version = versionRepository.save( Version.builder() .version("20240_1711255839") .type(EXPRESS.getValue()) .build() ); + versionRepository.save(version); + + Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) + .toInstant(); + + when(clock.instant()).thenReturn(requestedAt); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); expressBusCacheRepository.save( ExpressBusCache.create( @@ -346,8 +353,8 @@ void getNextExpressBusRemainTimeRedis() { .given() .when() .param("bus_type", busType.name().toLowerCase()) - .param("depart", depart.name().toLowerCase()) - .param("arrival", arrival.name().toLowerCase()) + .param("depart", depart.name()) + .param("arrival", arrival.name()) .get("/bus") .then() .statusCode(HttpStatus.OK.value()) From 27b27fa961e1b8636977e60d8020ca268ca0c30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 14:27:34 +0900 Subject: [PATCH 110/123] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/acceptance/BusApiTest.java | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 7b5d2185d..2efb5479c 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -1,15 +1,12 @@ package in.koreatech.koin.acceptance; import static in.koreatech.koin.domain.version.model.VersionType.CITY; -import static in.koreatech.koin.domain.version.model.VersionType.EXPRESS; import static java.time.format.DateTimeFormatter.ofPattern; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.Mockito.when; import java.time.Clock; import java.time.Instant; -import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -30,9 +27,6 @@ import in.koreatech.koin.domain.bus.model.enums.BusDirection; 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.express.ExpressBusCache; -import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; -import in.koreatech.koin.domain.bus.model.express.ExpressBusRoute; import in.koreatech.koin.domain.bus.model.mongo.BusCourse; import in.koreatech.koin.domain.bus.model.mongo.Route; import in.koreatech.koin.domain.bus.repository.BusRepository; @@ -313,62 +307,4 @@ void getNextCityBusRemainTimeOpenApi() { } ); } - - @Test - @DisplayName("다음 시외버스까지 남은 시간을 조회한다. - Redis") - void getNextExpressBusRemainTimeRedis() { - when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT)); - - BusType busType = BusType.from("express"); - BusStation depart = BusStation.from("terminal"); - BusStation arrival = BusStation.from("koreatech"); - - Version version = versionRepository.save( - Version.builder() - .version("20240_1711255839") - .type(EXPRESS.getValue()) - .build() - ); - versionRepository.save(version); - - Instant requestedAt = ZonedDateTime.parse("2024-02-21 18:00:30 KST", ofPattern("yyyy-MM-dd " + "HH:mm:ss z")) - .toInstant(); - - when(clock.instant()).thenReturn(requestedAt); - when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); - - expressBusCacheRepository.save( - ExpressBusCache.create( - new ExpressBusRoute("terminal", "koreatech"), - List.of( - new ExpressBusCacheInfo( - LocalTime.of(18, 10), - LocalTime.of(18, 20), - 1900 - ) - ) - ) - ); - ExtractableResponse response = RestAssured - .given() - .when() - .param("bus_type", busType.name().toLowerCase()) - .param("depart", depart.name()) - .param("arrival", arrival.name()) - .get("/bus") - .then() - .statusCode(HttpStatus.OK.value()) - .extract(); - - assertThat(response.asPrettyString()) - .isEqualTo(""" - { - "bus_type": "express", - "now_bus": { - "bus_number": null, - "remain_time": 1170 - }, - "next_bus": null - }"""); - } } From e9bce646712a09361008538e086f1a63c749c227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 14:36:40 +0900 Subject: [PATCH 111/123] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/city/CityBusArrival.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java index 495961d5a..c01749508 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusArrival.java @@ -13,10 +13,5 @@ public record CityBusArrival( String routetp, // 노선 유형, "일반 버스" String vehicletp // 차량 유형 (저상버스), "일반차량" ) { - public static CityBusArrival getEmpty(String nodeid) { - return builder() - .nodeid(nodeid) - .arrtime(-1L) - .build(); - } + } From d848476dffcb57c0e1e780d6d47091c860092dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:24:25 +0900 Subject: [PATCH 112/123] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java | 2 +- .../in/koreatech/koin/domain/bus/model/redis/BusCache.java | 2 +- .../koreatech/koin/domain/bus/util/CityBusOpenApiClient.java | 3 ++- src/test/java/in/koreatech/koin/acceptance/BusApiTest.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java index d2d70ff29..032bc9ab0 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCacheInfo.java @@ -17,7 +17,7 @@ public record CityBusCacheInfo( * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로 * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌 * */ - public static CityBusCacheInfo from(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { + public static CityBusCacheInfo of(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { return new CityBusCacheInfo( busArrivalInfo.routeno(), updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java index f85b0b34b..bc511e729 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/redis/BusCache.java @@ -18,7 +18,7 @@ public record BusCache( * 학교 버스는 도착 시간을 저장하고, 시내버스는 남은 시간만을 저장하므로 * Redis에 저장할때 (캐시 저장 시각 + 남은시간)으로 저장하여 통일시켜줌 * */ - public static BusCache from(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { + public static BusCache of(CityBusArrival busArrivalInfo, LocalDateTime updatedAt) { return new BusCache( busArrivalInfo.routeno(), updatedAt.plusSeconds(busArrivalInfo.arrtime()).toLocalTime() diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 9f26470e3..b7ac7edcb 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -113,7 +113,7 @@ private void getAllCityBusArrivalInfoByOpenApi() { CityBusCache.create( arrivalInfos.get(0).nodeid(), arrivalInfos.stream() - .map(busArrivalInfo -> CityBusCacheInfo.from(busArrivalInfo, updatedAt)) + .map(busArrivalInfo -> CityBusCacheInfo.of(busArrivalInfo, updatedAt)) .toList() ) ); @@ -189,6 +189,7 @@ private List extractBusArrivalInfo(String jsonResponse) { } } + @Override public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 2efb5479c..fd9dfaf9b 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -168,7 +168,7 @@ void getNextCityBusRemainTimeRedis() { cityBusCacheRepository.save( CityBusCache.create( depart.getNodeId(direction), - List.of(CityBusCacheInfo.from( + List.of(CityBusCacheInfo.of( CityBusArrival.builder() .routeno(busNumber) .arrtime(remainTime) From a9f64d9f800f5a66ffc60688f683f8ce7f2ee083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:27:06 +0900 Subject: [PATCH 113/123] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/service/BusService.java | 3 +-- .../koin/domain/bus/util/CityBusOpenApiClient.java | 6 ++---- .../koin/domain/bus/util/ExpressBusOpenApiClient.java | 7 ++++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java index f3bdb0ac8..c8949fe40 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java +++ b/src/main/java/in/koreatech/koin/domain/bus/service/BusService.java @@ -41,8 +41,7 @@ public BusRemainTimeResponse getBusRemainTime(BusType busType, BusStation depart } if (busType == BusType.EXPRESS) { - var remainTimes = expressBusOpenApiClient.getBusRemainTime(depart.name().toLowerCase(), - arrival.name().toLowerCase()); + var remainTimes = expressBusOpenApiClient.getBusRemainTime(depart, arrival); return BusRemainTimeResponse.of(busType, remainTimes, clock); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index b7ac7edcb..4a524cb42 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -76,11 +76,9 @@ public CityBusOpenApiClient( public List getBusRemainTime(String nodeId) { Version version = versionRepository.getByType(VersionType.CITY); - if (isCacheExpired(version, clock)) { - getAllCityBusArrivalInfoByOpenApi(); + storeRemainTimeByOpenApi(); } - return getCityBusArrivalInfoByCache(nodeId); } @@ -91,7 +89,7 @@ private List getCityBusArrivalInfoByCache(String nodeId) { .orElseGet(ArrayList::new); } - private void getAllCityBusArrivalInfoByOpenApi() { + private void storeRemainTimeByOpenApi() { List> arrivalInfosList = BusStationNode.getNodeIds().stream() .map(this::getOpenApiResponse) .map(this::extractBusArrivalInfo) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index 98846fd11..ddf6194e1 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -31,6 +31,7 @@ import in.koreatech.koin.domain.bus.dto.ExpressBusTimeTable; import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; +import in.koreatech.koin.domain.bus.model.enums.BusStation; import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; import in.koreatech.koin.domain.bus.model.express.ExpressBusCache; import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; @@ -76,12 +77,12 @@ public ExpressBusOpenApiClient( this.restTemplate = restTemplate; } - public List getBusRemainTime(String departName, String arrivalName) { + public List getBusRemainTime(BusStation depart, BusStation arrival) { Version version = versionRepository.getByType(VersionType.EXPRESS); if (isCacheExpired(version, clock)) { - storeRemainTimeByOpenApi(departName, arrivalName); + storeRemainTimeByOpenApi(depart.name().toLowerCase(), arrival.name().toLowerCase()); } - return getStoredRemainTime(departName, arrivalName); + return getStoredRemainTime(depart.name().toLowerCase(), arrival.name().toLowerCase()); } private List storeRemainTimeByOpenApi(String departName, String arrivalName) { From f5c3cfe0f07aedf8137fd8a891d08bcc51804d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:33:00 +0900 Subject: [PATCH 114/123] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/model/city/CityBusCache.java | 2 +- .../domain/bus/model/express/ExpressBusCache.java | 2 +- .../koin/domain/bus/util/CityBusOpenApiClient.java | 2 +- .../domain/bus/util/ExpressBusOpenApiClient.java | 14 +++++--------- .../in/koreatech/koin/acceptance/BusApiTest.java | 2 +- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java index 6759598c6..d86b286b0 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java @@ -33,7 +33,7 @@ private CityBusCache(String id, List busInfos, Long expiration this.expiration = expiration; } - public static CityBusCache create(String nodeId, List busInfos) { + public static CityBusCache of(String nodeId, List busInfos) { return CityBusCache.builder() .id(nodeId) .busInfos(busInfos) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java index 9d780773a..0d97a6e34 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java @@ -25,7 +25,7 @@ private ExpressBusCache(String id, List busInfos) { this.busInfos = busInfos; } - public static ExpressBusCache create(ExpressBusRoute route, List busInfos) { + public static ExpressBusCache of(ExpressBusRoute route, List busInfos) { return ExpressBusCache.builder() .id(generateId(route)) .busInfos(busInfos) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 4a524cb42..356a09dab 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -108,7 +108,7 @@ private void storeRemainTimeByOpenApi() { } cityBusCacheRepository.save( - CityBusCache.create( + CityBusCache.of( arrivalInfos.get(0).nodeid(), arrivalInfos.stream() .map(busArrivalInfo -> CityBusCacheInfo.of(busArrivalInfo, updatedAt)) diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index ddf6194e1..40674875d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -85,12 +85,13 @@ public List getBusRemainTime(BusStation depart, BusStation return getStoredRemainTime(depart.name().toLowerCase(), arrival.name().toLowerCase()); } - private List storeRemainTimeByOpenApi(String departName, String arrivalName) { + private void storeRemainTimeByOpenApi(String departName, String arrivalName) { JsonObject busApiResponse = getBusApiResponse(departName, arrivalName); List busArrivals = extractBusArrivalInfo(busApiResponse); expressBusCacheRepository.save( - ExpressBusCache.create( + ExpressBusCache.of( new ExpressBusRoute(departName, arrivalName), + // API로 받은 yyyyMMddHHmm 형태의 시간을 HH:mm 형태로 변환하여 Redis에 저장한다. busArrivals.stream() .map(it -> new ExpressBusCacheInfo( LocalTime.parse( @@ -106,12 +107,6 @@ private List storeRemainTimeByOpenApi(String departName, S .toList() )); versionRepository.getByType(VersionType.EXPRESS).update(clock); - return getExpressBusRemainTime( - busArrivals - .stream() - .map(ExpressBusTimeTable::from) - .toList() - ); } private JsonObject getBusApiResponse(String departName, String arrivalName) { @@ -177,6 +172,7 @@ private List getExpressBusRemainTime( @Override public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); - return duration.toSeconds() < 0 || Duration.ofHours(ExpressBusCache.getCacheExpireHour()).toSeconds() <= duration.toSeconds(); + return duration.toSeconds() < 0 + || Duration.ofHours(ExpressBusCache.getCacheExpireHour()).toSeconds() <= duration.toSeconds(); } } diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index fd9dfaf9b..7c7e6e57e 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -166,7 +166,7 @@ void getNextCityBusRemainTimeRedis() { when(dateTimeProvider.getNow()).thenReturn(Optional.of(requestedAt)); cityBusCacheRepository.save( - CityBusCache.create( + CityBusCache.of( depart.getNodeId(direction), List.of(CityBusCacheInfo.of( CityBusArrival.builder() From ea596e4384a49c5ac7e220549fb5308cd2b1e49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:37:50 +0900 Subject: [PATCH 115/123] =?UTF-8?q?refactor:=20ttl=20=EC=84=A0=EC=96=B8?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/model/express/ExpressBusCache.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java index 0d97a6e34..e76f517dc 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java @@ -1,15 +1,18 @@ package in.koreatech.koin.domain.bus.model.express; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; import jakarta.persistence.Id; import lombok.Builder; import lombok.Getter; @Getter -@RedisHash(value = "expressBus", timeToLive = 3600L) +@RedisHash(value = "expressBus") public class ExpressBusCache { private static final long CACHE_EXPIRE_HOUR = 1L; @@ -17,12 +20,16 @@ public class ExpressBusCache { @Id private String id; - private final List busInfos; + private final List busInfos = new ArrayList<>(); + + @TimeToLive(unit = TimeUnit.MINUTES) + private final Long expiration; @Builder private ExpressBusCache(String id, List busInfos) { this.id = id; - this.busInfos = busInfos; + this.busInfos.addAll(busInfos); + this.expiration = CACHE_EXPIRE_HOUR; } public static ExpressBusCache of(ExpressBusRoute route, List busInfos) { From 69786cc5b44293c2f686d7d25ff320e14a9dabfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:51:35 +0900 Subject: [PATCH 116/123] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EC=83=81=ED=99=94=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/dto/ExpressBusRemainTime.java | 8 -------- .../koin/domain/bus/util/BusOpenApiClient.java | 11 ----------- .../koin/domain/bus/util/CityBusOpenApiClient.java | 3 +-- .../koin/domain/bus/util/ExpressBusOpenApiClient.java | 4 +--- 4 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java index e6b88a4a3..e05896d6c 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusRemainTime.java @@ -14,12 +14,4 @@ public ExpressBusRemainTime(LocalTime busArrivalTime, String busType) { super(busArrivalTime); this.busType = busType; } - - public record InnerRemainTime( - Long busNumber, - Long remainTime - ) { - - } - } diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java deleted file mode 100644 index 119822ae5..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/util/BusOpenApiClient.java +++ /dev/null @@ -1,11 +0,0 @@ -package in.koreatech.koin.domain.bus.util; - -import java.time.Clock; - -import in.koreatech.koin.domain.bus.model.BusRemainTime; -import in.koreatech.koin.domain.version.model.Version; - -public abstract class BusOpenApiClient { - - public abstract boolean isCacheExpired(Version version, Clock clock); -} diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index 356a09dab..e77a2a7da 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -46,7 +46,7 @@ */ @Component @Transactional(readOnly = true) -public class CityBusOpenApiClient extends BusOpenApiClient { +public class CityBusOpenApiClient { private static final String ENCODE_TYPE = "UTF-8"; private static final String CHEONAN_CITY_CODE = "34010"; @@ -187,7 +187,6 @@ private List extractBusArrivalInfo(String jsonResponse) { } } - @Override public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); return duration.toSeconds() < 0 || CityBusCache.getCacheExpireSeconds() <= duration.toSeconds(); diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index 40674875d..48621aa44 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -29,7 +29,6 @@ import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime; import in.koreatech.koin.domain.bus.dto.ExpressBusTimeTable; -import in.koreatech.koin.domain.bus.model.BusRemainTime; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStation; import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; @@ -48,7 +47,7 @@ */ @Component @Transactional(readOnly = true) -public class ExpressBusOpenApiClient extends BusOpenApiClient { +public class ExpressBusOpenApiClient { private static final String OPEN_API_URL = "https://apis.data.go.kr/1613000/SuburbsBusInfoService/getStrtpntAlocFndSuberbsBusInfo"; private static final Type ARRIVAL_INFO_TYPE = new TypeToken>() { @@ -169,7 +168,6 @@ private List getExpressBusRemainTime( .toList(); } - @Override public boolean isCacheExpired(Version version, Clock clock) { Duration duration = Duration.between(version.getUpdatedAt().toLocalTime(), LocalTime.now(clock)); return duration.toSeconds() < 0 From 41009aa097be3e40279680e2c2b3a77bdc8af6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 15:52:23 +0900 Subject: [PATCH 117/123] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/model/BusRemainTime.java | 4 ++-- .../in/koreatech/koin/domain/bus/model/mongo/Route.java | 4 ++-- .../java/in/koreatech/koin/acceptance/BusApiTest.java | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 28ceab955..8e00c815d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -27,13 +27,13 @@ public Long getRemainSeconds(Clock clock) { return null; } - public static BusRemainTime from(String arrivalTime) { + public static BusRemainTime of(String arrivalTime) { return builder() .busArrivalTime(toLocalTime(arrivalTime)) .build(); } - public static BusRemainTime from(Long remainTime, LocalTime updatedAt) { + public static BusRemainTime of(Long remainTime, LocalTime updatedAt) { return builder() .busArrivalTime(updatedAt.plusSeconds(remainTime)) .build(); diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java index 48aab2069..e3e843779 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/Route.java @@ -45,7 +45,7 @@ public boolean isCorrectRoute(BusStation depart, BusStation arrival, Clock clock boolean foundDepart = false; for (ArrivalNode node : arrivalInfos) { if (depart.getDisplayNames().contains(node.getNodeName()) - && (BusRemainTime.from(node.getArrivalTime()).isBefore(clock))) { + && (BusRemainTime.of(node.getArrivalTime()).isBefore(clock))) { foundDepart = true; } if (arrival.getDisplayNames().contains(node.getNodeName()) && foundDepart) { @@ -57,7 +57,7 @@ public boolean isCorrectRoute(BusStation depart, BusStation arrival, Clock clock public BusRemainTime getRemainTime(BusStation busStation) { ArrivalNode convertedNode = convertToArrivalNode(busStation); - return BusRemainTime.from(convertedNode.arrivalTime); + return BusRemainTime.of(convertedNode.arrivalTime); } private ArrivalNode convertToArrivalNode(BusStation busStation) { diff --git a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java index 7c7e6e57e..f7348415d 100644 --- a/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/BusApiTest.java @@ -131,7 +131,7 @@ void getNextShuttleBusRemainTime() { .isEqualTo(busType.name().toLowerCase()); softly.assertThat((Long) response.body().jsonPath().get("now_bus.bus_number")).isNull(); softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo( - BusRemainTime.from(arrivalTime).getRemainSeconds(clock)); + BusRemainTime.of(arrivalTime).getRemainSeconds(clock)); softly.assertThat((Long) response.body().jsonPath().get("next_bus.bus_number")).isNull(); softly.assertThat((Long) response.body().jsonPath().get("next_bus.remain_time")).isNull(); } @@ -196,7 +196,7 @@ void getNextCityBusRemainTimeRedis() { softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber); softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time")) .isEqualTo( - BusRemainTime.from(remainTime, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + BusRemainTime.of(remainTime, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); softly.assertThat(response.body().jsonPath().getObject("next_bus.bus_number", Long.class)).isNull(); softly.assertThat(response.body().jsonPath().getObject("next_bus.remain_time", Long.class)).isNull(); } @@ -299,11 +299,11 @@ void getNextCityBusRemainTimeOpenApi() { softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400); softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time")) .isEqualTo( - BusRemainTime.from(600L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + BusRemainTime.of(600L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405); softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.remain_time")) .isEqualTo( - BusRemainTime.from(800L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); + BusRemainTime.of(800L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock)); } ); } From 0480178372b2664936cc1fedfc78cead0c9767d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 16:00:56 +0900 Subject: [PATCH 118/123] =?UTF-8?q?style:=20=EA=B3=B5=EB=B0=B1=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/model/enums/BusApiType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java index 906049adc..62089c562 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/enums/BusApiType.java @@ -10,7 +10,7 @@ public enum BusApiType { CITY, EXPRESS, ; - + public static BusApiType from(BusType value) { return Arrays.stream(values()) .filter(busApiType -> busApiType.toString().equalsIgnoreCase(value.toString())) From b91f7bd6c2f8ad935525e9ce9a21e762eb04c36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 16:03:39 +0900 Subject: [PATCH 119/123] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/bus/model/BusRemainTime.java | 8 +++++--- .../koin/domain/bus/model/city/CityBusRemainTime.java | 9 ++++++--- .../koreatech/koin/domain/bus/model/mongo/BusCourse.java | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java index 8e00c815d..6fe644359 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/BusRemainTime.java @@ -59,11 +59,13 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; - BusRemainTime that = (BusRemainTime)o; + } + BusRemainTime that = (BusRemainTime) o; return Objects.equals(busArrivalTime, that.busArrivalTime); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java index 4f7ba7c4f..36f15735d 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusRemainTime.java @@ -10,6 +10,7 @@ @Getter @SuperBuilder public class CityBusRemainTime extends BusRemainTime { + private final Long busNumber; public CityBusRemainTime(Long busNumber, LocalTime busArrivalTime) { @@ -31,11 +32,13 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; - CityBusRemainTime that = (CityBusRemainTime)o; + } + CityBusRemainTime that = (CityBusRemainTime) o; return Objects.equals(getBusArrivalTime(), that.getBusArrivalTime()) && Objects.equals(busNumber, that.busNumber); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java index ab503522c..6d492c75b 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/mongo/BusCourse.java @@ -6,7 +6,6 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; -import in.koreatech.koin.domain.bus.model.mongo.Route; import jakarta.persistence.Id; import lombok.AccessLevel; import lombok.Builder; From dd188a0b2441fcceb947efd406d245bbf3db5f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 16:04:05 +0900 Subject: [PATCH 120/123] =?UTF-8?q?style:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/bus/model/city/CityBusCache.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java index d86b286b0..c51697cf4 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/city/CityBusCache.java @@ -41,10 +41,6 @@ public static CityBusCache of(String nodeId, List busInfos) { .build(); } - public static long getCacheExpireMinute() { - return CACHE_EXPIRE_MINUTE; - } - public static long getCacheExpireSeconds() { return CACHE_EXPIRE_SECONDS; } From ed0a336f0e3fe4f4df9d0e71f1cbd414e1e4431a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 16:05:13 +0900 Subject: [PATCH 121/123] =?UTF-8?q?style:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/bus/dto/BusRemainTimeRequest.java | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java b/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java deleted file mode 100644 index 5ebbadc26..000000000 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/BusRemainTimeRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package in.koreatech.koin.domain.bus.dto; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(SnakeCaseStrategy.class) -public record BusRemainTimeRequest( - -) { - -} From 5d39461c655b8dfd3b6d8daecdf664cbe6d528be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 18:02:45 +0900 Subject: [PATCH 122/123] =?UTF-8?q?refactor:=20API=20=EC=82=AC=EC=9A=A9=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 --- .../bus/model/express/ExpressBusCache.java | 10 +-- .../domain/bus/util/CityBusOpenApiClient.java | 1 - .../bus/util/ExpressBusOpenApiClient.java | 64 ++++++++++++++----- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java index e76f517dc..54315e863 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusCache.java @@ -20,16 +20,16 @@ public class ExpressBusCache { @Id private String id; - private final List busInfos = new ArrayList<>(); + private List busInfos; - @TimeToLive(unit = TimeUnit.MINUTES) + @TimeToLive(unit = TimeUnit.HOURS) private final Long expiration; @Builder - private ExpressBusCache(String id, List busInfos) { + private ExpressBusCache(String id, List busInfos, Long expiration) { this.id = id; - this.busInfos.addAll(busInfos); - this.expiration = CACHE_EXPIRE_HOUR; + this.busInfos = (busInfos == null) ? new ArrayList<>() : busInfos; + this.expiration = expiration == null ? CACHE_EXPIRE_HOUR : expiration; } public static ExpressBusCache of(ExpressBusRoute route, List busInfos) { diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java index e77a2a7da..19df15fad 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/CityBusOpenApiClient.java @@ -161,7 +161,6 @@ private String getRequestURL(String cityCode, String nodeId) throws UnsupportedE private List extractBusArrivalInfo(String jsonResponse) { List result = new ArrayList<>(); - try { JsonObject response = JsonParser.parseString(jsonResponse) .getAsJsonObject() diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index 48621aa44..64057ab68 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -1,9 +1,15 @@ package in.koreatech.koin.domain.bus.util; import static in.koreatech.koin.domain.bus.model.enums.BusType.EXPRESS; +import static java.net.URLEncoder.encode; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.format.DateTimeFormatter.ofPattern; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; import java.time.Clock; import java.time.Duration; import java.time.LocalDateTime; @@ -11,11 +17,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; @@ -29,6 +33,7 @@ import in.koreatech.koin.domain.bus.dto.ExpressBusRemainTime; import in.koreatech.koin.domain.bus.dto.ExpressBusTimeTable; +import in.koreatech.koin.domain.bus.exception.BusOpenApiException; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStation; import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; @@ -53,7 +58,6 @@ public class ExpressBusOpenApiClient { private static final Type ARRIVAL_INFO_TYPE = new TypeToken>() { }.getType(); - private final RestTemplate restTemplate; private final VersionRepository versionRepository; private final ExpressBusCacheRepository expressBusCacheRepository; private final String openApiKey; @@ -109,19 +113,47 @@ private void storeRemainTimeByOpenApi(String departName, String arrivalName) { } private JsonObject getBusApiResponse(String departName, String arrivalName) { - ExpressBusStationNode departNode = ExpressBusStationNode.from(departName); - ExpressBusStationNode arrivalNode = ExpressBusStationNode.from(arrivalName); - String contentCount = "30"; - var parameters = Map.of("serviceKey", openApiKey, - "numOfRows", contentCount, - "depTerminalName", departNode.getStationId(), - "arrTerminalName", arrivalNode.getStationId(), - "depPlandTime", LocalDateTime.now(clock).format(ofPattern("yyyyMMdd")), - "_type", "json" - ); - ResponseEntity forEntity = restTemplate.getForEntity(OPEN_API_URL, String.class, parameters); - return JsonParser.parseString(Objects.requireNonNull(forEntity.getBody())) - .getAsJsonObject(); + try { + URL url = getBusApiURL(departName, arrivalName); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Content-type", "application/json"); + BufferedReader reader; + if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) { + reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + } else { + reader = new BufferedReader(new InputStreamReader(conn.getErrorStream())); + } + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(line); + } + reader.close(); + conn.disconnect(); + return JsonParser.parseString(result.toString()) + .getAsJsonObject(); + } catch (Exception e) { + throw BusOpenApiException.withDetail("departName: " + departName + " arrivalName: " + arrivalName); + } + } + + private URL getBusApiURL(String depart, String arrival) { + ExpressBusStationNode departNode = ExpressBusStationNode.from(depart); + ExpressBusStationNode arrivalNode = ExpressBusStationNode.from(arrival); + StringBuilder urlBuilder = new StringBuilder(OPEN_API_URL); /*URL*/ + try { + urlBuilder.append("?" + encode("serviceKey", UTF_8) + "=" + openApiKey); + urlBuilder.append("&" + encode("numOfRows", UTF_8) + "=" + encode("30", UTF_8)); + urlBuilder.append("&" + encode("_type", UTF_8) + "=" + encode("json", UTF_8)); + urlBuilder.append("&" + encode("depTerminalId", UTF_8) + "=" + encode(departNode.getStationId(), UTF_8)); + urlBuilder.append("&" + encode("arrTerminalId", UTF_8) + "=" + encode(arrivalNode.getStationId(), UTF_8)); + urlBuilder.append("&" + encode("depPlandTime", UTF_8) + "=" + + encode(LocalDateTime.now(clock).format(ofPattern("yyyyMMdd")), UTF_8)); + return new URL(urlBuilder.toString()); + } catch (Exception e) { + throw new IllegalStateException("시외버스 API URL 생성중 문제가 발생했습니다. uri:" + urlBuilder); + } } private List extractBusArrivalInfo(JsonObject jsonObject) { From 62db49d69ad31acd192e2cd501bbe3bdd78f7f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=AE=E1=86=AB=E1=84=92?= =?UTF-8?q?=E1=85=A9?= Date: Fri, 5 Apr 2024 18:04:32 +0900 Subject: [PATCH 123/123] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bus/dto/ExpressBusTimeTable.java | 10 +++++----- ...rival.java => OpenApiExpressBusArrival.java} | 2 +- .../bus/util/ExpressBusOpenApiClient.java | 17 +++++++---------- 3 files changed, 13 insertions(+), 16 deletions(-) rename src/main/java/in/koreatech/koin/domain/bus/model/express/{ExpressBusArrival.java => OpenApiExpressBusArrival.java} (90%) diff --git a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java index d4b225573..1783b979a 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java +++ b/src/main/java/in/koreatech/koin/domain/bus/dto/ExpressBusTimeTable.java @@ -4,8 +4,8 @@ import java.time.LocalTime; -import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; +import in.koreatech.koin.domain.bus.model.express.OpenApiExpressBusArrival; public record ExpressBusTimeTable( LocalTime departure, @@ -13,10 +13,10 @@ public record ExpressBusTimeTable( int charge ) { - public static ExpressBusTimeTable from(ExpressBusArrival expressBusArrival) { - LocalTime departure = LocalTime.parse(expressBusArrival.depPlandTime(), ofPattern("yyyyMMddHHmm")); - LocalTime arrival = LocalTime.parse(expressBusArrival.arrPlandTime(), ofPattern("yyyyMMddHHmm")); - int charge = expressBusArrival.charge(); + public static ExpressBusTimeTable from(OpenApiExpressBusArrival openApiExpressBusArrival) { + LocalTime departure = LocalTime.parse(openApiExpressBusArrival.depPlandTime(), ofPattern("yyyyMMddHHmm")); + LocalTime arrival = LocalTime.parse(openApiExpressBusArrival.arrPlandTime(), ofPattern("yyyyMMddHHmm")); + int charge = openApiExpressBusArrival.charge(); return new ExpressBusTimeTable(departure, arrival, charge); } diff --git a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java b/src/main/java/in/koreatech/koin/domain/bus/model/express/OpenApiExpressBusArrival.java similarity index 90% rename from src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java rename to src/main/java/in/koreatech/koin/domain/bus/model/express/OpenApiExpressBusArrival.java index 84e1c1e58..02c7ffffb 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/model/express/ExpressBusArrival.java +++ b/src/main/java/in/koreatech/koin/domain/bus/model/express/OpenApiExpressBusArrival.java @@ -3,7 +3,7 @@ import lombok.Builder; @Builder -public record ExpressBusArrival( +public record OpenApiExpressBusArrival( String arrPlaceNm, // 도착지 String arrPlandTime, // 도착 시간 String depPlaceNm, // 출발지 diff --git a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java index 64057ab68..2e10bdb77 100644 --- a/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java +++ b/src/main/java/in/koreatech/koin/domain/bus/util/ExpressBusOpenApiClient.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -36,11 +35,11 @@ import in.koreatech.koin.domain.bus.exception.BusOpenApiException; import in.koreatech.koin.domain.bus.model.enums.BusOpenApiResultCode; import in.koreatech.koin.domain.bus.model.enums.BusStation; -import in.koreatech.koin.domain.bus.model.express.ExpressBusArrival; import in.koreatech.koin.domain.bus.model.express.ExpressBusCache; import in.koreatech.koin.domain.bus.model.express.ExpressBusCacheInfo; import in.koreatech.koin.domain.bus.model.express.ExpressBusRoute; import in.koreatech.koin.domain.bus.model.express.ExpressBusStationNode; +import in.koreatech.koin.domain.bus.model.express.OpenApiExpressBusArrival; import in.koreatech.koin.domain.bus.repository.ExpressBusCacheRepository; import in.koreatech.koin.domain.version.model.Version; import in.koreatech.koin.domain.version.model.VersionType; @@ -55,7 +54,7 @@ public class ExpressBusOpenApiClient { private static final String OPEN_API_URL = "https://apis.data.go.kr/1613000/SuburbsBusInfoService/getStrtpntAlocFndSuberbsBusInfo"; - private static final Type ARRIVAL_INFO_TYPE = new TypeToken>() { + private static final Type ARRIVAL_INFO_TYPE = new TypeToken>() { }.getType(); private final VersionRepository versionRepository; @@ -69,15 +68,13 @@ public ExpressBusOpenApiClient( VersionRepository versionRepository, Gson gson, Clock clock, - ExpressBusCacheRepository expressBusCacheRepository, - RestTemplate restTemplate + ExpressBusCacheRepository expressBusCacheRepository ) { this.openApiKey = openApiKey; this.versionRepository = versionRepository; this.gson = gson; this.clock = clock; this.expressBusCacheRepository = expressBusCacheRepository; - this.restTemplate = restTemplate; } public List getBusRemainTime(BusStation depart, BusStation arrival) { @@ -90,7 +87,7 @@ public List getBusRemainTime(BusStation depart, BusStation private void storeRemainTimeByOpenApi(String departName, String arrivalName) { JsonObject busApiResponse = getBusApiResponse(departName, arrivalName); - List busArrivals = extractBusArrivalInfo(busApiResponse); + List busArrivals = extractBusArrivalInfo(busApiResponse); expressBusCacheRepository.save( ExpressBusCache.of( new ExpressBusRoute(departName, arrivalName), @@ -156,7 +153,7 @@ private URL getBusApiURL(String depart, String arrival) { } } - private List extractBusArrivalInfo(JsonObject jsonObject) { + private List extractBusArrivalInfo(JsonObject jsonObject) { try { var response = jsonObject.get("response").getAsJsonObject(); BusOpenApiResultCode.validateResponse(response); @@ -165,12 +162,12 @@ private List extractBusArrivalInfo(JsonObject jsonObject) { return Collections.emptyList(); } JsonElement item = body.get("items").getAsJsonObject().get("item"); - List result = new ArrayList<>(); + List result = new ArrayList<>(); if (item.isJsonArray()) { return gson.fromJson(item, ARRIVAL_INFO_TYPE); } if (item.isJsonObject()) { - result.add(gson.fromJson(item, ExpressBusArrival.class)); + result.add(gson.fromJson(item, OpenApiExpressBusArrival.class)); } return result; } catch (JsonSyntaxException e) {