From b8f697bba8b138f6c2eed6f068319691944f95b0 Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:28:20 +0900 Subject: [PATCH 01/31] =?UTF-8?q?feat=20:=20EndBus=20enum=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=BD=94=EB=93=9C2=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/main/java/com/talkka/server/bus/enums/EndBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/talkka/server/bus/enums/EndBus.java b/server/src/main/java/com/talkka/server/bus/enums/EndBus.java index a365804b..2ed1e373 100644 --- a/server/src/main/java/com/talkka/server/bus/enums/EndBus.java +++ b/server/src/main/java/com/talkka/server/bus/enums/EndBus.java @@ -7,7 +7,7 @@ @Getter public enum EndBus implements EnumCodeInterface { // "0 = RUNNING" or "1 = END" - RUNNING("0"), END("1"), LENT_BUS_END("4"); + RUNNING("0"), END("1"), END_BUS_CODE2("2"), LENT_BUS_END("4"); private final String code; From 68d1ce432f8fb2ca22440286b57f38dcaf61ce3c Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:28:59 +0900 Subject: [PATCH 02/31] =?UTF-8?q?feat=20:=20BusRouteEntity=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/talkka/server/bus/dao/BusRouteEntity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRouteEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusRouteEntity.java index dc7cf632..f90e1b99 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusRouteEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRouteEntity.java @@ -112,6 +112,11 @@ public class BusRouteEntity { @LastModifiedDate private LocalDateTime updatedAt; + // 집계용 엔티티이므로 연관관계 설정 안함 + // @OneToMany(mappedBy = "route") + // @Builder.Default + // private List routeLocations = new ArrayList<>(); + @OneToMany(mappedBy = "route") @Builder.Default private List stations = new ArrayList<>(); From 9df9f2dbf24268b76975071c4537796146131cfe Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:30:02 +0900 Subject: [PATCH 03/31] =?UTF-8?q?feat=20:=20BusLocation=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EB=B3=91=EB=A0=AC=EC=B2=98=EB=A6=AC,=20?= =?UTF-8?q?=EC=9E=AC=EC=8B=9C=EB=8F=84=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datagg/service/SimpleBusApiService.java | 87 ++++++++++--------- .../service/BusLocationCollectService.java | 2 + .../bus/service/BusLocationService.java | 4 +- .../service/LocationCollectingScheduler.java | 58 ++++++++++++- 4 files changed, 105 insertions(+), 46 deletions(-) diff --git a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java index 4d9108bf..8c8ccdef 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java +++ b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java @@ -6,9 +6,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.retry.backoff.FixedBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -39,15 +43,10 @@ public List getSearchedRouteInfo(String keyword) throws A MultiValueMap params = new LinkedMultiValueMap<>(); params.add("keyword", keyword); try { - URI uri = this.getOpenApiUri(path, params); - ResponseEntity resp = restTemplate.getForEntity(uri, BusRouteSearchRespDto.class); - var body = resp.getBody().msgBody(); - if (body == null) { - throw new ApiClientException("결과가 없습니다."); - } - return body; - } catch (Exception exception) { - throw new ApiClientException(exception.getMessage()); + ResponseEntity resp = apiCallWithRetry(path, params, BusRouteSearchRespDto.class); + return resp.getBody().msgBody(); + } catch (RestClientException exception) { + throw new ApiClientException("결과가 없습니다."); } } @@ -58,14 +57,10 @@ public List getRouteInfo(String apiRouteId) throws ApiClien params.add("routeId", apiRouteId); try { URI uri = this.getOpenApiUri(path, params); - ResponseEntity resp = restTemplate.getForEntity(uri, BusRouteInfoRespDto.class); - var body = resp.getBody().msgBody(); - if (body == null) { - throw new ApiClientException("결과가 없습니다."); - } - return body; - } catch (Exception exception) { - throw new ApiClientException(exception.getMessage()); + ResponseEntity resp = apiCallWithRetry(path, params, BusRouteInfoRespDto.class); + return resp.getBody().msgBody(); + } catch (RestClientException exception) { + throw new ApiClientException("결과가 없습니다."); } } @@ -75,15 +70,10 @@ public List getRouteStationInfo(String apiRouteId) throw MultiValueMap params = new LinkedMultiValueMap<>(); params.add("routeId", apiRouteId); try { - URI uri = this.getOpenApiUri(path, params); - ResponseEntity resp = restTemplate.getForEntity(uri, BusRouteStationRespDto.class); - var body = resp.getBody().msgBody(); - if (body == null) { - throw new ApiClientException("결과가 없습니다."); - } - return body; - } catch (Exception exception) { - throw new ApiClientException(exception.getMessage()); + ResponseEntity resp = apiCallWithRetry(path, params, BusRouteStationRespDto.class); + return resp.getBody().msgBody(); + } catch (RestClientException exception) { + throw new ApiClientException("결과가 없습니다."); } } @@ -93,23 +83,13 @@ public List getBusLocationInfo(String apiRouteId) throws Api MultiValueMap params = new LinkedMultiValueMap<>(); params.add("routeId", apiRouteId); try { - URI uri = this.getOpenApiUri(path, params); - ResponseEntity resp = restTemplate.getForEntity(uri, BusLocationRespDto.class); - var body = resp.getBody().msgBody(); - if (body == null) { - throw new ApiClientException("결과가 없습니다."); - } - return body; - } catch (Exception exception) { - throw new ApiClientException(exception.getMessage()); + ResponseEntity resp = apiCallWithRetry(path, params, BusLocationRespDto.class); + return resp.getBody().msgBody(); + } catch (RestClientException exception) { + throw new ApiClientException("결과가 없습니다."); } } - // @Override - // public List getBusStationArrivalInfo(String routeId) { - // return null; - // } - private URI getOpenApiUri(String path, MultiValueMap params) { final var builder = new DefaultUriBuilderFactory(); builder.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); @@ -121,4 +101,31 @@ private URI getOpenApiUri(String path, MultiValueMap params) { .queryParams(params) .build(); } + + // 리트라이 로직을 포함한 api call + private ResponseEntity apiCallWithRetry(String path, MultiValueMap params, + Class type) throws RestClientException { + + final int MAX_ATTEMPTS = 10; + final int RETRY_INTERVAL = 200; + + // 이후 bean 으로 등록하는것 고려 + RetryTemplate retryTemplate = new RetryTemplate(); + + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); + retryPolicy.setMaxAttempts(MAX_ATTEMPTS); + retryTemplate.setRetryPolicy(retryPolicy); + + FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); + backOffPolicy.setBackOffPeriod(RETRY_INTERVAL); + retryTemplate.setBackOffPolicy(backOffPolicy); + + // 재시도마다 새로운 api key 로 시도 + // 파싱 실패시 RestClientException 터트림 + return retryTemplate.execute(context -> { + // 재시도마다 새로운 api key 로 시도 + URI uri = this.getOpenApiUri(path, params); + return restTemplate.getForEntity(uri, type); // 파싱 실패시 RestClientException 터트림 + }); + } } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLocationCollectService.java b/server/src/main/java/com/talkka/server/bus/service/BusLocationCollectService.java index 4a097df6..078e8886 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusLocationCollectService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusLocationCollectService.java @@ -2,4 +2,6 @@ public interface BusLocationCollectService { void collectLocations(); + + void collectLocationsByRouteId(String apiRouteId); } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java b/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java index ef37863f..53017900 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java @@ -4,12 +4,12 @@ import java.util.List; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import com.talkka.server.api.datagg.dto.BusLocationBodyDto; import com.talkka.server.bus.dao.BusLocationEntity; import com.talkka.server.bus.dao.BusLocationRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @Service @@ -18,7 +18,7 @@ public class BusLocationService { private final BusLocationRepository busLocationRepository; @Transactional - public void saveBusLocations(List responseList, int apiCallNo, LocalDateTime createdAt) { + public void saveBusLocations(List responseList, Integer apiCallNo, LocalDateTime createdAt) { List entityList = responseList.stream() .map(dto -> dto.toEntity(apiCallNo, createdAt)) .toList(); diff --git a/server/src/main/java/com/talkka/server/bus/service/LocationCollectingScheduler.java b/server/src/main/java/com/talkka/server/bus/service/LocationCollectingScheduler.java index be63636e..c1f0391b 100644 --- a/server/src/main/java/com/talkka/server/bus/service/LocationCollectingScheduler.java +++ b/server/src/main/java/com/talkka/server/bus/service/LocationCollectingScheduler.java @@ -1,13 +1,19 @@ package com.talkka.server.bus.service; import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import com.talkka.server.bus.util.BusLocationCollectProvider; import com.talkka.server.bus.util.LocationCollectingSchedulerConfigProperty; import lombok.extern.slf4j.Slf4j; @@ -18,6 +24,8 @@ public class LocationCollectingScheduler { private final LocationCollectingSchedulerConfigProperty locationCollectingSchedulerConfigProperty; private final BusLocationCollectService busLocationCollectService; + @Autowired + private BusLocationCollectProvider busLocationCollectProvider; public LocationCollectingScheduler( @Qualifier("locationCollectingSchedulerConfigProperty") @@ -29,14 +37,56 @@ public LocationCollectingScheduler( this.busLocationCollectService = busLocationCollectService; } - @Transactional + // 병렬 버스 위치 api 호출 메소드 @Scheduled(fixedRate = 1000 * 60) // per minute - public void runLocationScheduler() { + public void runParallelLocationScheduler() { if (isEnabled()) { - busLocationCollectService.collectLocations(); + List targetList = busLocationCollectProvider.getTargetIdList(); + ExecutorService executor = Executors.newFixedThreadPool(20); + + // CompletableFuture 리스트 생성 + List> futures = targetList.stream() + .map(targetId -> CompletableFuture.runAsync(() -> { + try { + busLocationCollectService.collectLocationsByRouteId(targetId); + } catch (Exception e) { + // 재시도 후에도 실패한 경우, 로그 남기기 + log.error(" 버스 위치 저장 실패 {} : {}", targetId, e.getMessage()); + } + }, executor)) + .toList(); + + // 모든 작업이 완료될 때까지 기다림 + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + + // ExecutorService 종료 + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } } } + // 순차적 버스 위치 api 호출 메소드 + // @Scheduled(fixedRate = 1000 * 10) // per minute + // public void runLocationScheduler() { + // if (isEnabled()) { + // busLocationCollectProvider.getTargetIdList() + // .forEach(targetId -> { + // try { + // retryCollectLocations(targetId, 3); + // } catch (InterruptedException e) { + // log.error("{} : {}", targetId, e.getMessage()); + // } + // }); + // } + // } + private boolean isEnabled() { if (!locationCollectingSchedulerConfigProperty.isEnabled()) { return false; From bc69ecd847eaf4b1129962f32b295b913addd0b6 Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:31:19 +0900 Subject: [PATCH 04/31] =?UTF-8?q?feat=20:=20BusLocation=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EB=B3=91=EB=A0=AC=EC=B2=98=EB=A6=AC,=20?= =?UTF-8?q?=EC=9E=AC=EC=8B=9C=EB=8F=84=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedApiLocationCollectService.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/service/BlockedApiLocationCollectService.java b/server/src/main/java/com/talkka/server/bus/service/BlockedApiLocationCollectService.java index 1e347125..a9ed170c 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BlockedApiLocationCollectService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BlockedApiLocationCollectService.java @@ -47,17 +47,16 @@ public void collectLocations() { log.info("No target routeId to collect bus locations"); return; } + apiRouteIdList.forEach(this::collectLocationsByRouteId); + } + @Override + @Transactional + public void collectLocationsByRouteId(String apiRouteId) throws ApiClientException { + log.info("Collecting bus locations for routeId: {}", apiRouteId); Integer apiCallNo = apiCallNumberProvider.getApiCallNumber(); LocalDateTime createdAt = LocalDateTime.now(); - for (String apiRouteId : apiRouteIdList) { - log.info("Collecting bus locations for routeId: {}", apiRouteId); - try { // should be refactored - List responseList = busApiService.getBusLocationInfo(apiRouteId); - busLocationService.saveBusLocations(responseList, apiCallNo, createdAt); - } catch (ApiClientException apiClientException) { - log.error("Failed to collect bus locations", apiClientException); - } - } + List responseList = busApiService.getBusLocationInfo(apiRouteId); + busLocationService.saveBusLocations(responseList, apiCallNo, createdAt); } } From 3f6907fffbb19c8b5376908c215e00ab18a3dc64 Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:31:56 +0900 Subject: [PATCH 05/31] =?UTF-8?q?feat=20:=20=EB=B2=84=EC=8A=A4=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EA=B3=B5?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A9=94=EC=86=8C=EB=93=9C=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 --- .../server/bus/dao/BusLocationRepository.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java index 41cfa83f..c6913875 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java @@ -1,8 +1,19 @@ package com.talkka.server.bus.dao; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; - + @Repository public interface BusLocationRepository extends JpaRepository { + + @Query(value = "select distinct l.apiCallNo from bus_location l") + List getDistinctApiCallNoList(); + + @Query(value = "select count(*) from bus_location l") + Integer getRowNum(); + + List findByApiCallNo(Integer apiCallNo); } From f9b4e328dde826eed9715e737dd81f83e1097df8 Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 22:53:35 +0900 Subject: [PATCH 06/31] =?UTF-8?q?feat=20:=20Spring=20retry=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=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 --- server/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/build.gradle b/server/build.gradle index c27ae026..33c09422 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -33,6 +33,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // XML Parser implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' + // Spring Retry + implementation 'org.springframework.retry:spring-retry' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' From 5325d66e48f23109f0e8386e0e49e857a4fd1076 Mon Sep 17 00:00:00 2001 From: PSH Date: Fri, 23 Aug 2024 23:46:46 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat=20:=20=EB=B2=84=EC=8A=A4=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/bus/dao/BusLocationRepository.java | 6 +- .../talkka/server/bus/dao/BusStatEntity.java | 73 +++++++++++++ .../server/bus/dao/BusStatRepository.java | 12 ++ .../server/bus/service/BusStatService.java | 103 ++++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusStatService.java diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java index c6913875..1bb543cd 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java @@ -1,5 +1,6 @@ package com.talkka.server.bus.dao; +import java.time.LocalDateTime; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -16,4 +17,7 @@ public interface BusLocationRepository extends JpaRepository findByApiCallNo(Integer apiCallNo); -} + + List findByCreatedAtBetween(LocalDateTime startTime, LocalDateTime endTime); + +} \ No newline at end of file diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java new file mode 100644 index 00000000..ffb577fc --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -0,0 +1,73 @@ +package com.talkka.server.bus.dao; + +import java.time.LocalDateTime; +import java.util.Objects; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.talkka.server.bus.enums.PlateType; +import com.talkka.server.bus.util.PlateTypeConverter; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity(name = "bus_stat") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class BusStatEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "route_id", nullable = false) + private String routeId; + + @Column(name = "station_id", nullable = false) + private String stationId; + + @Column(name = "before_seat", nullable = false) + private Integer beforeSeat; + + @Column(name = "after_seat", nullable = false) + private Integer afterSeat; + + @Column(name = "seat_diff", nullable = false) + private Integer seatDiff; + + @Column(name = "plate_no", nullable = false, length = 32) + private String plateNo; + + @Column(name = "before_time", nullable = false) + private LocalDateTime beforeTime; + + @Column(name = "after_time", nullable = false) + private LocalDateTime afterTime; + + @Column(name = "plate_type", nullable = false, length = 1) + @Convert(converter = PlateTypeConverter.class) + private PlateType plateType; + + @Column(name = "created_at", nullable = false) + @CreatedDate + private LocalDateTime createdAt; + + @Override + public int hashCode() { + return Objects.hash(routeId, stationId, beforeSeat, afterSeat, seatDiff, plateNo, beforeTime, afterTime, + plateType); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java new file mode 100644 index 00000000..8a32443d --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -0,0 +1,12 @@ +package com.talkka.server.bus.dao; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BusStatRepository extends JpaRepository { + List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java new file mode 100644 index 00000000..8e9c9107 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -0,0 +1,103 @@ +package com.talkka.server.bus.service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.stereotype.Service; + +import com.talkka.server.bus.dao.BusLocationEntity; +import com.talkka.server.bus.dao.BusLocationRepository; +import com.talkka.server.bus.dao.BusStatEntity; +import com.talkka.server.bus.dao.BusStatRepository; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BusStatService { + private final BusStatRepository busStatRepository; + private final BusLocationRepository busLocationRepository; + + // 생성된 위치정보 중 start~end 기간에 있는 것들만 가공 + @Transactional + public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { + List locationList = busLocationRepository.findByCreatedAtBetween(start, end); + Map locationMap = new HashMap<>(); + // 가공하려는 기간에 속하는 통계정보를 가져와 엔티티의 해시코드를 set 에 저장 + Set statHashSet = new HashSet<>(); + busStatRepository.findByBeforeTimeBetween(start, end).forEach(stat -> statHashSet.add(stat.hashCode())); + + for (BusLocationEntity afterLocation : locationList) { + // 같은 버스의 위치정보가 map 에 없으면 넣고 넘김 + if (!locationMap.containsKey(afterLocation.getPlateNo())) { + locationMap.put(afterLocation.getPlateNo(), afterLocation); + } else { + // 같은 버스의 직전 위치를 map 에서 가져옴 + BusLocationEntity beforeLocation = locationMap.get(afterLocation.getPlateNo()); + + if (// 위치정보들 간 시간 차이가 1시간 미만이어야함 + Duration.between(beforeLocation.getCreatedAt(), afterLocation.getCreatedAt()).toHours() < 1 + // 위치정보들 간 정거장 차이가 1정거장 이하여야함 + && afterLocation.getStationSeq() - beforeLocation.getStationSeq() <= 1 + // 위치정보들 둘다 좌석정보를 가지고 있어야함 + && beforeLocation.getRemainSeatCount() != -1 && afterLocation.getRemainSeatCount() != -1 + ) { + BusStatEntity statEntity = toBusStatEntity(beforeLocation, afterLocation); + // 이미 존재하는 통계 정보인지 확인한 후 db에 저장 + if (!statHashSet.contains(statEntity.hashCode())) { + busStatRepository.save(statEntity); + } + } + // 위치정보 갱신 + locationMap.put(afterLocation.getPlateNo(), afterLocation); + } + } + } + + // 두개의 위치정보를 가지고 BusStatEntity 를 생성 + private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocationEntity after) { + return BusStatEntity.builder() + .routeId(after.getApiRouteId()) + .stationId(before.getApiStationId()) + .beforeSeat(before.getRemainSeatCount().intValue()) + .afterSeat(after.getRemainSeatCount().intValue()) + .seatDiff(after.getRemainSeatCount() - before.getRemainSeatCount()) + .plateNo(after.getPlateNo()) + .beforeTime(before.getCreatedAt()) + .afterTime(after.getCreatedAt()) + .plateType(after.getPlateType()) + .build(); + } + + // old logic + // @Deprecated + // @Transactional + // public void process() { + // List apiCallNoList = busLocationRepository.getDistinctApiCallNoList(); + // apiCallNoList.sort(Comparator.naturalOrder()); + // Map prev = new HashMap<>(); + // System.out.println(apiCallNoList); + // for (Integer apiCallNo : apiCallNoList) { + // Map cur = new HashMap<>(); + // List locationList = busLocationRepository.findByApiCallNo(apiCallNo); + // for (BusLocationEntity location : locationList) { + // BusLocationEntity beforeLocation; + // if ((beforeLocation = prev.get(location.getPlateNo())) != null + // // 두정거장 이상 넘어가면 체크하지 않음 + // && location.getStationSeq() - beforeLocation.getStationSeq() <= 1 && ( + // location.getRemainSeatCount() != -1 && beforeLocation.getRemainSeatCount() != -1)) { + // BusStatEntity statEntity = toBusStatEntity(beforeLocation, location); + // busStatRepository.save(statEntity); + // } + // cur.put(location.getPlateNo(), location); + // } + // prev = cur; + // } + // } +} From a1b6ccd0f592ef63d1e3e7096a3e843adf70f5ba Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 13:11:42 +0900 Subject: [PATCH 08/31] =?UTF-8?q?feat=20:=20@Transactional=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/talkka/server/bus/service/BusLocationService.java | 2 +- .../main/java/com/talkka/server/bus/service/BusStatService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java b/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java index 53017900..b86d69a9 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusLocationService.java @@ -4,12 +4,12 @@ import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.talkka.server.api.datagg.dto.BusLocationBodyDto; import com.talkka.server.bus.dao.BusLocationEntity; import com.talkka.server.bus.dao.BusLocationRepository; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @Service diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 8e9c9107..a84bc7c3 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -9,13 +9,13 @@ import java.util.Set; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.talkka.server.bus.dao.BusLocationEntity; import com.talkka.server.bus.dao.BusLocationRepository; import com.talkka.server.bus.dao.BusStatEntity; import com.talkka.server.bus.dao.BusStatRepository; -import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @Service From f5b8467169cd899f186ffe024e3a24b2d8f573b1 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 13:12:07 +0900 Subject: [PATCH 09/31] =?UTF-8?q?feat=20:=20query=20-=20table=20alias=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 --- .../java/com/talkka/server/bus/dao/BusLocationRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java index 1bb543cd..fd8d14aa 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusLocationRepository.java @@ -13,7 +13,7 @@ public interface BusLocationRepository extends JpaRepository getDistinctApiCallNoList(); - @Query(value = "select count(*) from bus_location l") + @Query(value = "select count(*) from bus_location") Integer getRowNum(); List findByApiCallNo(Integer apiCallNo); From f2abdea83d7158aa761b7badc39bc75f60b02405 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 13:24:37 +0900 Subject: [PATCH 10/31] =?UTF-8?q?feat=20:=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/api/datagg/service/SimpleBusApiService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java index 8c8ccdef..439fe8f7 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java +++ b/server/src/main/java/com/talkka/server/api/datagg/service/SimpleBusApiService.java @@ -56,7 +56,6 @@ public List getRouteInfo(String apiRouteId) throws ApiClien MultiValueMap params = new LinkedMultiValueMap<>(); params.add("routeId", apiRouteId); try { - URI uri = this.getOpenApiUri(path, params); ResponseEntity resp = apiCallWithRetry(path, params, BusRouteInfoRespDto.class); return resp.getBody().msgBody(); } catch (RestClientException exception) { From d5ec6de36c2db1a47e0bc0090ae73e5bae6455fa Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 19:28:45 +0900 Subject: [PATCH 11/31] =?UTF-8?q?BusStatEntity=20=EC=8B=9D=EB=B3=84?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=B3=84=EB=8F=84=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20PK=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20equals&hashcode=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=9D=BC=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/bus/dao/BusStatEntity.java | 30 +++++++++++++++++-- .../server/bus/service/BusStatService.java | 6 ++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java index ffb577fc..a76f7677 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -67,7 +67,33 @@ public class BusStatEntity { @Override public int hashCode() { - return Objects.hash(routeId, stationId, beforeSeat, afterSeat, seatDiff, plateNo, beforeTime, afterTime, - plateType); + return getId().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BusStatEntity that = (BusStatEntity)obj; + return getId().equals(that.getId()); + } + + // 내용이 중복된 BusStatEntity 식별을 위한 메소드 + public int identifier() { + return Objects.hash( + this.getRouteId(), + this.getStationId(), + this.getBeforeSeat(), + this.getBeforeTime(), + this.getAfterSeat(), + this.getAfterTime(), + this.getSeatDiff(), + this.getPlateNo(), + this.getPlateType() + ); } } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index a84bc7c3..0a51c245 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -30,8 +30,8 @@ public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { List locationList = busLocationRepository.findByCreatedAtBetween(start, end); Map locationMap = new HashMap<>(); // 가공하려는 기간에 속하는 통계정보를 가져와 엔티티의 해시코드를 set 에 저장 - Set statHashSet = new HashSet<>(); - busStatRepository.findByBeforeTimeBetween(start, end).forEach(stat -> statHashSet.add(stat.hashCode())); + Set statIdentifierSet = new HashSet<>(); + busStatRepository.findByBeforeTimeBetween(start, end).forEach(stat -> statIdentifierSet.add(stat.identifier())); for (BusLocationEntity afterLocation : locationList) { // 같은 버스의 위치정보가 map 에 없으면 넣고 넘김 @@ -50,7 +50,7 @@ public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { ) { BusStatEntity statEntity = toBusStatEntity(beforeLocation, afterLocation); // 이미 존재하는 통계 정보인지 확인한 후 db에 저장 - if (!statHashSet.contains(statEntity.hashCode())) { + if (!statIdentifierSet.contains(statEntity.identifier())) { busStatRepository.save(statEntity); } } From e3a9e78a1044f8f1ec321bcf9ed7598a86e0ee44 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 21:39:29 +0900 Subject: [PATCH 12/31] =?UTF-8?q?BusStat=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/bus/dao/BusStatEntity.java | 18 +++++----- .../server/bus/dao/BusStatRepository.java | 4 +++ .../talkka/server/bus/dto/BusStatReqDto.java | 11 ++++++ .../talkka/server/bus/dto/BusStatRespDto.java | 36 +++++++++++++++++++ .../server/bus/service/BusStatService.java | 19 ++++++++-- 5 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java index a76f7677..c997ca70 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -33,11 +33,11 @@ public class BusStatEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "route_id", nullable = false) - private String routeId; + @Column(name = "api_route_id", nullable = false) + private String apiRouteId; - @Column(name = "station_id", nullable = false) - private String stationId; + @Column(name = "api_station_id", nullable = false) + private String apiStationId; @Column(name = "before_seat", nullable = false) private Integer beforeSeat; @@ -48,15 +48,15 @@ public class BusStatEntity { @Column(name = "seat_diff", nullable = false) private Integer seatDiff; - @Column(name = "plate_no", nullable = false, length = 32) - private String plateNo; - @Column(name = "before_time", nullable = false) private LocalDateTime beforeTime; @Column(name = "after_time", nullable = false) private LocalDateTime afterTime; + @Column(name = "plate_no", nullable = false, length = 32) + private String plateNo; + @Column(name = "plate_type", nullable = false, length = 1) @Convert(converter = PlateTypeConverter.class) private PlateType plateType; @@ -85,8 +85,8 @@ public boolean equals(Object obj) { // 내용이 중복된 BusStatEntity 식별을 위한 메소드 public int identifier() { return Objects.hash( - this.getRouteId(), - this.getStationId(), + this.getApiRouteId(), + this.getApiStationId(), this.getBeforeSeat(), this.getBeforeTime(), this.getAfterSeat(), diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java index 8a32443d..b335eca2 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -9,4 +9,8 @@ @Repository public interface BusStatRepository extends JpaRepository { List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); + + List findByApiRouteIdAndApiStationIdAndBeforeTimeBetween(String apiRouteId, String apiStationId, + LocalDateTime startTime, + LocalDateTime endTime); } diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java new file mode 100644 index 00000000..ee2d9988 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java @@ -0,0 +1,11 @@ +package com.talkka.server.bus.dto; + +import java.time.LocalDateTime; + +public record BusStatReqDto( + String apiRouteId, + String apiStationId, + LocalDateTime startDateTime, + LocalDateTime endDateTime +) { +} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java new file mode 100644 index 00000000..21a6e7b3 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java @@ -0,0 +1,36 @@ +package com.talkka.server.bus.dto; + +import java.time.LocalDateTime; + +import com.talkka.server.bus.dao.BusStatEntity; +import com.talkka.server.bus.enums.PlateType; + +public record BusStatRespDto( + Long statId, + String apiRouteId, + String apiStationId, + Integer beforeSeat, + Integer afterSeat, + Integer seatDiff, + LocalDateTime beforeTime, + LocalDateTime afterTime, + String plateNo, + PlateType plateType, + LocalDateTime createdAt +) { + public static BusStatRespDto of(BusStatEntity busStatEntity) { + return new BusStatRespDto( + busStatEntity.getId(), + busStatEntity.getApiRouteId(), + busStatEntity.getApiStationId(), + busStatEntity.getBeforeSeat(), + busStatEntity.getAfterSeat(), + busStatEntity.getSeatDiff(), + busStatEntity.getBeforeTime(), + busStatEntity.getAfterTime(), + busStatEntity.getPlateNo(), + busStatEntity.getPlateType(), + busStatEntity.getCreatedAt() + ); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 0a51c245..79cceab6 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -15,6 +15,8 @@ import com.talkka.server.bus.dao.BusLocationRepository; import com.talkka.server.bus.dao.BusStatEntity; import com.talkka.server.bus.dao.BusStatRepository; +import com.talkka.server.bus.dto.BusStatReqDto; +import com.talkka.server.bus.dto.BusStatRespDto; import lombok.RequiredArgsConstructor; @@ -24,6 +26,17 @@ public class BusStatService { private final BusStatRepository busStatRepository; private final BusLocationRepository busLocationRepository; + public List getBusStat(BusStatReqDto busStatReqDto) { + return busStatRepository.findByApiRouteIdAndApiStationIdAndBeforeTimeBetween( + busStatReqDto.apiRouteId(), + busStatReqDto.apiStationId(), + busStatReqDto.startDateTime(), + busStatReqDto.endDateTime() + ).stream() + .map(BusStatRespDto::of) + .toList(); + } + // 생성된 위치정보 중 start~end 기간에 있는 것들만 가공 @Transactional public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { @@ -63,14 +76,14 @@ public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { // 두개의 위치정보를 가지고 BusStatEntity 를 생성 private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocationEntity after) { return BusStatEntity.builder() - .routeId(after.getApiRouteId()) - .stationId(before.getApiStationId()) + .apiRouteId(after.getApiRouteId()) + .apiStationId(before.getApiStationId()) .beforeSeat(before.getRemainSeatCount().intValue()) .afterSeat(after.getRemainSeatCount().intValue()) .seatDiff(after.getRemainSeatCount() - before.getRemainSeatCount()) - .plateNo(after.getPlateNo()) .beforeTime(before.getCreatedAt()) .afterTime(after.getCreatedAt()) + .plateNo(after.getPlateNo()) .plateType(after.getPlateType()) .build(); } From 74bf4aac8127c4ad8f19899ca975bda9159fa6a1 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 23:00:51 +0900 Subject: [PATCH 13/31] =?UTF-8?q?BusStat=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/talkka/server/bus/dao/BusStatEntity.java | 9 +++++++++ .../com/talkka/server/bus/dao/BusStatRepository.java | 12 +++++++++--- .../com/talkka/server/bus/dto/BusStatRespDto.java | 6 ++++++ .../talkka/server/bus/service/BusStatService.java | 3 +++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java index c997ca70..25fec33e 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -61,6 +61,15 @@ public class BusStatEntity { @Convert(converter = PlateTypeConverter.class) private PlateType plateType; + @Column(name = "day_of_week", nullable = false) + private Integer dayOfWeek; + + @Column(name = "hour", nullable = false) + private Integer hour; + + @Column(name = "minute", nullable = false) + private Integer minute; + @Column(name = "created_at", nullable = false) @CreatedDate private LocalDateTime createdAt; diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java index b335eca2..68b06c8d 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -4,13 +4,19 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface BusStatRepository extends JpaRepository { List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); - List findByApiRouteIdAndApiStationIdAndBeforeTimeBetween(String apiRouteId, String apiStationId, - LocalDateTime startTime, - LocalDateTime endTime); + @Query("SELECT b FROM bus_stat b WHERE b.apiRouteId = :apiRouteId AND b.apiStationId = :apiStationId AND b.beforeTime BETWEEN :startTime AND :endTime") + List findByApiRouteIdAndApiStationIdAndBeforeTimeBetween( + @Param("apiRouteId") String apiRouteId, + @Param("apiStationId") String apiStationId, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime + ); } diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java index 21a6e7b3..012d8cfd 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java @@ -16,6 +16,9 @@ public record BusStatRespDto( LocalDateTime afterTime, String plateNo, PlateType plateType, + Integer dayOfWeek, + Integer hour, + Integer minute, LocalDateTime createdAt ) { public static BusStatRespDto of(BusStatEntity busStatEntity) { @@ -30,6 +33,9 @@ public static BusStatRespDto of(BusStatEntity busStatEntity) { busStatEntity.getAfterTime(), busStatEntity.getPlateNo(), busStatEntity.getPlateType(), + busStatEntity.getDayOfWeek(), + busStatEntity.getHour(), + busStatEntity.getMinute(), busStatEntity.getCreatedAt() ); } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 79cceab6..cfd3100c 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -85,6 +85,9 @@ private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocati .afterTime(after.getCreatedAt()) .plateNo(after.getPlateNo()) .plateType(after.getPlateType()) + .dayOfWeek(before.getCreatedAt().getDayOfWeek().getValue()) + .hour(before.getCreatedAt().getHour()) + .minute(before.getCreatedAt().getMinute()) .build(); } From 108ad1d37cf34621696727daf80f86419b2313b4 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 23:43:53 +0900 Subject: [PATCH 14/31] =?UTF-8?q?3003=EB=B2=88=20=EC=9C=A8=EC=A0=95?= =?UTF-8?q?=EC=A4=91=ED=95=99=EA=B5=90=20=EC=A0=95=EA=B1=B0=EC=9E=A5=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 --- data/mysql-files/bus_route_station.csv | 1 + data/mysql-files/bus_station.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/data/mysql-files/bus_route_station.csv b/data/mysql-files/bus_route_station.csv index f3c2fc00..f5d039dc 100644 --- a/data/mysql-files/bus_route_station.csv +++ b/data/mysql-files/bus_route_station.csv @@ -152020,3 +152020,4 @@ routeId,stationId,stationSeq,stationName,mobileNo,regionName,districtCd,centerYn 249000002,206000564,5,봇들육교,07510,성남,2,N,N,127.1076667,37.4033667 249000002,206000540,6,삼평교,07496,성남,2,N,N,127.1047833,37.4041333 249000002,204000341,7,기업성장센터,05332,성남,2,N,N,127.0948,37.4112833 +200000108,200000068,65,율전중학교,01002,수원,2,N,N,126.9658333,37.3010333 \ No newline at end of file diff --git a/data/mysql-files/bus_station.csv b/data/mysql-files/bus_station.csv index 4bb4f498..18091c1f 100644 --- a/data/mysql-files/bus_station.csv +++ b/data/mysql-files/bus_station.csv @@ -31187,3 +31187,4 @@ stationId,stationName,mobileNo,regionName,districtCd,centerYn,turnYn,x,y 234000329,풍산목재,38164,광주,2,N,N,127.2269,37.3608 234000328,오포1동행정복지센터.오포파출소,38534,광주,2,N,N,127.2318,37.3669 219000149,중산마을10단지.동신아파트,20259,고양,2,N,N,126.7788333,37.68725 +200000068,율전중학교,01002,수원,2,N,N,126.9658333,37.3010333 \ No newline at end of file From a2d003198b8c07f9cb8d517d798b6a251d3ea281 Mon Sep 17 00:00:00 2001 From: PSH Date: Sat, 24 Aug 2024 23:49:23 +0900 Subject: [PATCH 15/31] =?UTF-8?q?BusStat=20=EC=A1=B0=ED=9A=8C=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 --- .../talkka/server/bus/dao/BusStatEntity.java | 10 +++++++++ .../talkka/server/bus/dto/BusStatRespDto.java | 4 ++++ .../server/bus/service/BusStatService.java | 21 ++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java index 25fec33e..25cf88ae 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -16,6 +16,8 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -33,6 +35,14 @@ public class BusStatEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @ManyToOne + @JoinColumn(name = "route_id", nullable = true) + private BusRouteEntity route; + + @ManyToOne + @JoinColumn(name = "station_id", nullable = true) + private BusStationEntity station; + @Column(name = "api_route_id", nullable = false) private String apiRouteId; diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java index 012d8cfd..d427c2cb 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java @@ -7,6 +7,8 @@ public record BusStatRespDto( Long statId, + Long routeId, + Long stationId, String apiRouteId, String apiStationId, Integer beforeSeat, @@ -24,6 +26,8 @@ public record BusStatRespDto( public static BusStatRespDto of(BusStatEntity busStatEntity) { return new BusStatRespDto( busStatEntity.getId(), + busStatEntity.getRoute().getId(), + busStatEntity.getStation().getId(), busStatEntity.getApiRouteId(), busStatEntity.getApiStationId(), busStatEntity.getBeforeSeat(), diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index cfd3100c..3f0fb939 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -13,8 +13,12 @@ import com.talkka.server.bus.dao.BusLocationEntity; import com.talkka.server.bus.dao.BusLocationRepository; +import com.talkka.server.bus.dao.BusRouteEntity; +import com.talkka.server.bus.dao.BusRouteRepository; import com.talkka.server.bus.dao.BusStatEntity; import com.talkka.server.bus.dao.BusStatRepository; +import com.talkka.server.bus.dao.BusStationEntity; +import com.talkka.server.bus.dao.BusStationRepository; import com.talkka.server.bus.dto.BusStatReqDto; import com.talkka.server.bus.dto.BusStatRespDto; @@ -23,8 +27,10 @@ @Service @RequiredArgsConstructor public class BusStatService { - private final BusStatRepository busStatRepository; + private final BusRouteRepository busRouteRepository; + private final BusStationRepository busStationRepository; private final BusLocationRepository busLocationRepository; + private final BusStatRepository busStatRepository; public List getBusStat(BusStatReqDto busStatReqDto) { return busStatRepository.findByApiRouteIdAndApiStationIdAndBeforeTimeBetween( @@ -42,6 +48,10 @@ public List getBusStat(BusStatReqDto busStatReqDto) { public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { List locationList = busLocationRepository.findByCreatedAtBetween(start, end); Map locationMap = new HashMap<>(); + Map routeMap = new HashMap<>(); + Map stationMap = new HashMap<>(); + busRouteRepository.findAll().forEach(route -> routeMap.put(route.getApiRouteId(), route)); + busStationRepository.findAll().forEach(station -> stationMap.put(station.getApiStationId(), station)); // 가공하려는 기간에 속하는 통계정보를 가져와 엔티티의 해시코드를 set 에 저장 Set statIdentifierSet = new HashSet<>(); busStatRepository.findByBeforeTimeBetween(start, end).forEach(stat -> statIdentifierSet.add(stat.identifier())); @@ -61,7 +71,9 @@ public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { // 위치정보들 둘다 좌석정보를 가지고 있어야함 && beforeLocation.getRemainSeatCount() != -1 && afterLocation.getRemainSeatCount() != -1 ) { - BusStatEntity statEntity = toBusStatEntity(beforeLocation, afterLocation); + BusRouteEntity route = routeMap.get(beforeLocation.getApiRouteId()); + BusStationEntity station = stationMap.get(beforeLocation.getApiStationId()); + BusStatEntity statEntity = toBusStatEntity(beforeLocation, afterLocation, route, station); // 이미 존재하는 통계 정보인지 확인한 후 db에 저장 if (!statIdentifierSet.contains(statEntity.identifier())) { busStatRepository.save(statEntity); @@ -74,8 +86,11 @@ public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { } // 두개의 위치정보를 가지고 BusStatEntity 를 생성 - private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocationEntity after) { + private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocationEntity after, + BusRouteEntity route, BusStationEntity station) { return BusStatEntity.builder() + .route(route) + .station(station) .apiRouteId(after.getApiRouteId()) .apiStationId(before.getApiStationId()) .beforeSeat(before.getRemainSeatCount().intValue()) From 64370f153b972d7d78185ba9349414caa40386a7 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 00:03:06 +0900 Subject: [PATCH 16/31] =?UTF-8?q?BusStat=20=EC=A1=B0=ED=9A=8C=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 --- .../talkka/server/bus/dao/BusStatRepository.java | 15 ++++++--------- .../com/talkka/server/bus/dto/BusStatReqDto.java | 4 ++-- .../talkka/server/bus/service/BusStatService.java | 6 +++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java index 68b06c8d..ef10a70a 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -4,19 +4,16 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface BusStatRepository extends JpaRepository { List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); - - @Query("SELECT b FROM bus_stat b WHERE b.apiRouteId = :apiRouteId AND b.apiStationId = :apiStationId AND b.beforeTime BETWEEN :startTime AND :endTime") - List findByApiRouteIdAndApiStationIdAndBeforeTimeBetween( - @Param("apiRouteId") String apiRouteId, - @Param("apiStationId") String apiStationId, - @Param("startTime") LocalDateTime startTime, - @Param("endTime") LocalDateTime endTime + + List findByRouteIdAndStationIdAndBeforeTimeBetween( + Long routeId, + Long stationId, + LocalDateTime startTime, + LocalDateTime endTime ); } diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java index ee2d9988..59c0291d 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java @@ -3,8 +3,8 @@ import java.time.LocalDateTime; public record BusStatReqDto( - String apiRouteId, - String apiStationId, + Long routeId, + Long stationId, LocalDateTime startDateTime, LocalDateTime endDateTime ) { diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 3f0fb939..85ff8935 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -33,9 +33,9 @@ public class BusStatService { private final BusStatRepository busStatRepository; public List getBusStat(BusStatReqDto busStatReqDto) { - return busStatRepository.findByApiRouteIdAndApiStationIdAndBeforeTimeBetween( - busStatReqDto.apiRouteId(), - busStatReqDto.apiStationId(), + return busStatRepository.findByRouteIdAndStationIdAndBeforeTimeBetween( + busStatReqDto.routeId(), + busStatReqDto.stationId(), busStatReqDto.startDateTime(), busStatReqDto.endDateTime() ).stream() From ad071bc03ed930f1d578e45a78ba823b8573ad69 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 01:17:17 +0900 Subject: [PATCH 17/31] =?UTF-8?q?=ED=98=84=EC=9E=AC=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EB=B2=84=EC=8A=A4=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/bus/dao/BusStatEntity.java | 7 ++--- .../server/bus/dao/BusStatRepository.java | 14 +++++++++- .../talkka/server/bus/dto/BusStatRespDto.java | 26 ++----------------- .../server/bus/service/BusStatService.java | 23 ++++++++++++++-- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java index 25cf88ae..4d08076b 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java @@ -74,11 +74,8 @@ public class BusStatEntity { @Column(name = "day_of_week", nullable = false) private Integer dayOfWeek; - @Column(name = "hour", nullable = false) - private Integer hour; - - @Column(name = "minute", nullable = false) - private Integer minute; + @Column(name = "time", nullable = false) + private Integer time; @Column(name = "created_at", nullable = false) @CreatedDate diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java index ef10a70a..8d516669 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -4,16 +4,28 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface BusStatRepository extends JpaRepository { List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); - + List findByRouteIdAndStationIdAndBeforeTimeBetween( Long routeId, Long stationId, LocalDateTime startTime, LocalDateTime endTime ); + + @Query("SELECT b FROM bus_stat b WHERE b.route.id = :routeId AND b.station.id = :stationId AND b.dayOfWeek = :dayOfWeek AND (b.time >= :startTime OR b.time <= :endTime)") + List findByRouteIdAndStationIdAndDayOfWeekBetweenNow( + @Param("routeId") Long routeId, + @Param("stationId") Long stationId, + @Param("dayOfWeek") Integer dayOfWeek, + @Param("startTime") int startTime, + @Param("endTime") int endTime + ); + } diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java index d427c2cb..54ef2d96 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java @@ -1,46 +1,24 @@ package com.talkka.server.bus.dto; -import java.time.LocalDateTime; - import com.talkka.server.bus.dao.BusStatEntity; import com.talkka.server.bus.enums.PlateType; public record BusStatRespDto( - Long statId, - Long routeId, - Long stationId, - String apiRouteId, - String apiStationId, Integer beforeSeat, Integer afterSeat, Integer seatDiff, - LocalDateTime beforeTime, - LocalDateTime afterTime, - String plateNo, PlateType plateType, Integer dayOfWeek, - Integer hour, - Integer minute, - LocalDateTime createdAt + Integer time ) { public static BusStatRespDto of(BusStatEntity busStatEntity) { return new BusStatRespDto( - busStatEntity.getId(), - busStatEntity.getRoute().getId(), - busStatEntity.getStation().getId(), - busStatEntity.getApiRouteId(), - busStatEntity.getApiStationId(), busStatEntity.getBeforeSeat(), busStatEntity.getAfterSeat(), busStatEntity.getSeatDiff(), - busStatEntity.getBeforeTime(), - busStatEntity.getAfterTime(), - busStatEntity.getPlateNo(), busStatEntity.getPlateType(), busStatEntity.getDayOfWeek(), - busStatEntity.getHour(), - busStatEntity.getMinute(), - busStatEntity.getCreatedAt() + busStatEntity.getTime() ); } } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 85ff8935..5ed74e92 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -43,6 +43,21 @@ public List getBusStat(BusStatReqDto busStatReqDto) { .toList(); } + public List getBusStatNow(Long routeId, Long stationId) { + LocalDateTime now = LocalDateTime.now(); + int startTime = getTime(now.minusMinutes(30)); + int endTime = getTime(now.plusMinutes(30)); + return busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenNow( + routeId, + stationId, + now.getDayOfWeek().getValue(), + startTime, + endTime + ).stream() + .map(BusStatRespDto::of) + .toList(); + } + // 생성된 위치정보 중 start~end 기간에 있는 것들만 가공 @Transactional public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { @@ -101,11 +116,15 @@ private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocati .plateNo(after.getPlateNo()) .plateType(after.getPlateType()) .dayOfWeek(before.getCreatedAt().getDayOfWeek().getValue()) - .hour(before.getCreatedAt().getHour()) - .minute(before.getCreatedAt().getMinute()) + .time(getTime(before.getCreatedAt())) .build(); } + // 시간, 분을 이어붙인 int 값 ex) 23:59 -> 2359, 08:27->827 + private static int getTime(LocalDateTime localDateTime) { + return localDateTime.getHour() * 100 + localDateTime.getMinute(); + } + // old logic // @Deprecated // @Transactional From 0077439af7dcc6037fffaddbbc4f153dd6c350f7 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 01:18:47 +0900 Subject: [PATCH 18/31] =?UTF-8?q?=ED=98=84=EC=9E=AC=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EB=B2=84=EC=8A=A4=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bus/controller/BusStatController.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusStatController.java diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java b/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java new file mode 100644 index 00000000..03c73b1d --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java @@ -0,0 +1,64 @@ +package com.talkka.server.bus.controller; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.talkka.server.bus.dto.BusStatReqDto; +import com.talkka.server.bus.dto.BusStatRespDto; +import com.talkka.server.bus.service.BusStatService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/bus/stat") +public class BusStatController { + private final BusStatService busStatService; + + @GetMapping("") + public ResponseEntity> getBusStats( + @RequestParam(name = "routeId", required = false) Long routeId, + @RequestParam(name = "stationId", required = false) Long stationId, + @RequestParam(name = "startDateTime", required = false) String startDateTime, + @RequestParam(name = "endDateTime", required = false) String endDateTime + ) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime start = LocalDateTime.parse(startDateTime, formatter); + LocalDateTime end = LocalDateTime.parse(endDateTime, formatter); + BusStatReqDto statReqDto = new BusStatReqDto( + routeId, + stationId, + start, + end + ); + List statList = busStatService.getBusStat(statReqDto); + return ResponseEntity.ok(statList); + } + + // 현재 시간 전후 30분 구간의 + @GetMapping("/now") + public ResponseEntity> getBusStatsNow( + @RequestParam(name = "routeId", required = false) Long routeId, + @RequestParam(name = "stationId", required = false) Long stationId + ) { + List statList = busStatService.getBusStatNow(routeId, stationId); + return ResponseEntity.ok(statList); + } + + @PostMapping("/makeStatData") + public String makeStatData() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime start = LocalDateTime.parse("2000-01-01 00:00", formatter); + LocalDateTime end = LocalDateTime.parse("2100-12-31 23:59", formatter); + busStatService.makeStatDataBetween(start, end); + return "success"; + } +} From 46e3c1095472bc990da1736a3bb66727bf1fdfa5 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 01:30:16 +0900 Subject: [PATCH 19/31] =?UTF-8?q?3=EC=8B=9C=20=EA=B8=B0=EC=A4=80=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9A=94=EC=9D=BC=20=EB=B3=80=EA=B2=BD=EB=90=98?= =?UTF-8?q?=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 --- .../server/bus/service/BusStatService.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 5ed74e92..a61834cf 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -45,14 +45,12 @@ public List getBusStat(BusStatReqDto busStatReqDto) { public List getBusStatNow(Long routeId, Long stationId) { LocalDateTime now = LocalDateTime.now(); - int startTime = getTime(now.minusMinutes(30)); - int endTime = getTime(now.plusMinutes(30)); return busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenNow( routeId, stationId, - now.getDayOfWeek().getValue(), - startTime, - endTime + getDayOfWeek(now), + getTime(now.minusMinutes(30)), + getTime(now.plusMinutes(30)) ).stream() .map(BusStatRespDto::of) .toList(); @@ -115,7 +113,7 @@ private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocati .afterTime(after.getCreatedAt()) .plateNo(after.getPlateNo()) .plateType(after.getPlateType()) - .dayOfWeek(before.getCreatedAt().getDayOfWeek().getValue()) + .dayOfWeek(getDayOfWeek(before.getCreatedAt())) .time(getTime(before.getCreatedAt())) .build(); } @@ -125,6 +123,13 @@ private static int getTime(LocalDateTime localDateTime) { return localDateTime.getHour() * 100 + localDateTime.getMinute(); } + private static int getDayOfWeek(LocalDateTime localDateTime) { + if (localDateTime.getHour() < 3) { + return localDateTime.getDayOfWeek().getValue() - 1; + } + return localDateTime.getDayOfWeek().getValue(); + } + // old logic // @Deprecated // @Transactional From 7e603951234bd91c89fa4d816a69715e593db5b3 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 01:36:46 +0900 Subject: [PATCH 20/31] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=B0=8F=20?= =?UTF-8?q?=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 --- .../com/talkka/server/bus/controller/BusStatController.java | 2 +- .../java/com/talkka/server/bus/service/BusStatService.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java b/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java index 03c73b1d..5b10f17a 100644 --- a/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java +++ b/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java @@ -43,7 +43,7 @@ public ResponseEntity> getBusStats( return ResponseEntity.ok(statList); } - // 현재 시간 전후 30분 구간의 + // 현재 시간 기준 이전30분 이후30분 구간의 같은 요일 통계 조회 @GetMapping("/now") public ResponseEntity> getBusStatsNow( @RequestParam(name = "routeId", required = false) Long routeId, diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index a61834cf..1245f564 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -123,9 +123,10 @@ private static int getTime(LocalDateTime localDateTime) { return localDateTime.getHour() * 100 + localDateTime.getMinute(); } + // 새벽 3시 기준으로 요일 변경 private static int getDayOfWeek(LocalDateTime localDateTime) { if (localDateTime.getHour() < 3) { - return localDateTime.getDayOfWeek().getValue() - 1; + return (localDateTime.getDayOfWeek().getValue() + 6) % 7; } return localDateTime.getDayOfWeek().getValue(); } From 2f3caddedc1a8cb7cbe4f21d0d136dda9c69f5f3 Mon Sep 17 00:00:00 2001 From: PSH Date: Sun, 25 Aug 2024 02:21:52 +0900 Subject: [PATCH 21/31] =?UTF-8?q?=EB=82=A0=EC=A7=9C=EA=B0=80=20=EB=B0=94?= =?UTF-8?q?=EB=80=8C=EB=8A=94=20=EC=8B=9C=EC=A0=90=EC=9D=98=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=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 --- .../server/bus/dao/BusStatRepository.java | 4 +-- .../server/bus/service/BusStatService.java | 35 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java index 8d516669..1e492911 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java @@ -19,8 +19,8 @@ List findByRouteIdAndStationIdAndBeforeTimeBetween( LocalDateTime endTime ); - @Query("SELECT b FROM bus_stat b WHERE b.route.id = :routeId AND b.station.id = :stationId AND b.dayOfWeek = :dayOfWeek AND (b.time >= :startTime OR b.time <= :endTime)") - List findByRouteIdAndStationIdAndDayOfWeekBetweenNow( + @Query("SELECT b FROM bus_stat b WHERE b.route.id = :routeId AND b.station.id = :stationId AND b.dayOfWeek = :dayOfWeek AND (b.time >= :startTime AND b.time <= :endTime)") + List findByRouteIdAndStationIdAndDayOfWeekBetweenTime( @Param("routeId") Long routeId, @Param("stationId") Long stationId, @Param("dayOfWeek") Integer dayOfWeek, diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java index 1245f564..ce4798be 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java @@ -45,13 +45,35 @@ public List getBusStat(BusStatReqDto busStatReqDto) { public List getBusStatNow(Long routeId, Long stationId) { LocalDateTime now = LocalDateTime.now(); - return busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenNow( + int startTime = getTime(now.minusMinutes(30)); + int endTime = getTime(now.plusMinutes(30)); + List result; + // 두 날짜에 겹쳐있는 경우 ex) 2349 ~ 0049 + if (startTime > endTime) { + result = busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( routeId, stationId, getDayOfWeek(now), - getTime(now.minusMinutes(30)), - getTime(now.plusMinutes(30)) - ).stream() + startTime, + 2359 + ); + result.addAll(busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( + routeId, + stationId, + getDayOfWeek(now), + 0, + endTime + )); + } else { + result = busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( + routeId, + stationId, + getDayOfWeek(now), + startTime, + endTime + ); + } + return result.stream() .map(BusStatRespDto::of) .toList(); } @@ -123,10 +145,11 @@ private static int getTime(LocalDateTime localDateTime) { return localDateTime.getHour() * 100 + localDateTime.getMinute(); } - // 새벽 3시 기준으로 요일 변경 + // 새벽 3시 기준으로 요일 변경 1-7 사이 값을 가짐 private static int getDayOfWeek(LocalDateTime localDateTime) { if (localDateTime.getHour() < 3) { - return (localDateTime.getDayOfWeek().getValue() + 6) % 7; + int beforeDayOfWeek = localDateTime.getDayOfWeek().getValue() - 1; + return beforeDayOfWeek == 0 ? 7 : beforeDayOfWeek; } return localDateTime.getDayOfWeek().getValue(); } From be61855fd9c770129f49057d65b47bfe395b3a59 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 12:58:56 +0900 Subject: [PATCH 22/31] =?UTF-8?q?refactor=20:=20TimeSlot=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/common/enums/TimeSlot.java | 129 +++++++++++------- .../common/util/TimeSlotTypeConverter.java | 12 ++ 2 files changed, 91 insertions(+), 50 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java diff --git a/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java b/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java index e42b5f00..6e0144ef 100644 --- a/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java +++ b/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java @@ -1,58 +1,73 @@ package com.talkka.server.common.enums; +import java.time.LocalDateTime; + import com.talkka.server.common.exception.enums.InvalidTimeSlotEnumException; +import com.talkka.server.common.util.EnumCodeInterface; + +public enum TimeSlot implements EnumCodeInterface { + T_00_00(0), + T_00_30(1), + T_01_00(2), + T_01_30(3), + T_02_00(4), + T_02_30(5), + T_03_00(6), + T_03_30(7), + T_04_00(8), + T_04_30(9), + T_05_00(10), + T_05_30(11), + T_06_00(12), + T_06_30(13), + T_07_00(14), + T_07_30(15), + T_08_00(16), + T_08_30(17), + T_09_00(18), + T_09_30(19), + T_10_00(20), + T_10_30(21), + T_11_00(22), + T_11_30(23), + T_12_00(24), + T_12_30(25), + T_13_00(26), + T_13_30(27), + T_14_00(28), + T_14_30(29), + T_15_00(30), + T_15_30(31), + T_16_00(32), + T_16_30(33), + T_17_00(34), + T_17_30(35), + T_18_00(36), + T_18_30(37), + T_19_00(38), + T_19_30(39), + T_20_00(40), + T_20_30(41), + T_21_00(42), + T_21_30(43), + T_22_00(44), + T_22_30(45), + T_23_00(46), + T_23_30(47); + + private final int code; -public enum TimeSlot { - T_00_00, - T_00_30, - T_01_00, - T_01_30, - T_02_00, - T_02_30, - T_03_00, - T_03_30, - T_04_00, - T_04_30, - T_05_00, - T_05_30, - T_06_00, - T_06_30, - T_07_00, - T_07_30, - T_08_00, - T_08_30, - T_09_00, - T_09_30, - T_10_00, - T_10_30, - T_11_00, - T_11_30, - T_12_00, - T_12_30, - T_13_00, - T_13_30, - T_14_00, - T_14_30, - T_15_00, - T_15_30, - T_16_00, - T_16_30, - T_17_00, - T_17_30, - T_18_00, - T_18_30, - T_19_00, - T_19_30, - T_20_00, - T_20_30, - T_21_00, - T_21_30, - T_22_00, - T_22_30, - T_23_00, - T_23_30; + TimeSlot(int code) { + this.code = code; + } + + @Override + public String getCode() { + return String.valueOf(this.code); + } - TimeSlot() { + public int getCodeValue() { + return this.code; } public static TimeSlot valueOfEnumString(String enumValue) { @@ -62,4 +77,18 @@ public static TimeSlot valueOfEnumString(String enumValue) { throw new InvalidTimeSlotEnumException(); } } + + public static TimeSlot fromCode(int code) { + for (TimeSlot timeSlot : TimeSlot.values()) { + if (timeSlot.code == code) { + return timeSlot; + } + } + throw new InvalidTimeSlotEnumException(); + } + + public static TimeSlot fromLocalDateTime(LocalDateTime time) { + int code = 2 * time.getHour() + (time.getMinute() < 30 ? 0 : 1); + return TimeSlot.fromCode(code); + } } diff --git a/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java b/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java new file mode 100644 index 00000000..2abbe248 --- /dev/null +++ b/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java @@ -0,0 +1,12 @@ +package com.talkka.server.common.util; + +import com.talkka.server.common.enums.TimeSlot; + +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class TimeSlotTypeConverter extends EnumCodeConverter { + public TimeSlotTypeConverter() { + super(TimeSlot.class); + } +} From 675d2a9a7b091746acde3a0b8c592250b7a96257 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 13:00:26 +0900 Subject: [PATCH 23/31] =?UTF-8?q?feat=20:=20=EB=B2=84=EC=8A=A4=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=A0=95=EB=B3=B4=20=EA=B0=80=EA=B3=B5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusLocationProcessController.java | 24 ++ .../bus/controller/BusStatController.java | 64 ---- .../server/bus/dao/BusLocationEntity.java | 4 +- .../bus/dao/BusPlateStatisticEntity.java | 56 ++++ .../bus/dao/BusPlateStatisticRepository.java | 8 + .../server/bus/dao/BusRemainSeatEntity.java | 115 +++++++ .../bus/dao/BusRemainSeatRepository.java | 19 ++ .../bus/dao/BusRouteStationRepository.java | 5 + .../talkka/server/bus/dao/BusStatEntity.java | 115 ------- .../server/bus/dao/BusStatRepository.java | 31 -- .../talkka/server/bus/dto/BusStatReqDto.java | 11 - .../talkka/server/bus/dto/BusStatRespDto.java | 24 -- .../exception/BusRouteNotFoundException.java | 4 +- .../InvalidLocationNotFoundException.java | 9 + .../bus/service/BusLocationProcessor.java | 293 ++++++++++++++++++ .../server/bus/service/BusStatService.java | 182 ----------- 16 files changed, 533 insertions(+), 431 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusLocationProcessController.java delete mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusStatController.java create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticRepository.java create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java create mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java delete mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java delete mode 100644 server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java delete mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java delete mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/exception/InvalidLocationNotFoundException.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java delete mode 100644 server/src/main/java/com/talkka/server/bus/service/BusStatService.java diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusLocationProcessController.java b/server/src/main/java/com/talkka/server/bus/controller/BusLocationProcessController.java new file mode 100644 index 00000000..2b32bc6a --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/controller/BusLocationProcessController.java @@ -0,0 +1,24 @@ +package com.talkka.server.bus.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.talkka.server.bus.dao.BusLocationRepository; +import com.talkka.server.bus.service.BusLocationProcessor; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/bus/stat") +public class BusLocationProcessController { + private final BusLocationProcessor busLocationProcessor; + private final BusLocationRepository busLocationRepository; + + @PostMapping("/process/all") + public String process() { + busLocationProcessor.start(busLocationRepository.findAll()); + return "success"; + } +} diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java b/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java deleted file mode 100644 index 5b10f17a..00000000 --- a/server/src/main/java/com/talkka/server/bus/controller/BusStatController.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.talkka.server.bus.controller; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.talkka.server.bus.dto.BusStatReqDto; -import com.talkka.server.bus.dto.BusStatRespDto; -import com.talkka.server.bus.service.BusStatService; - -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/bus/stat") -public class BusStatController { - private final BusStatService busStatService; - - @GetMapping("") - public ResponseEntity> getBusStats( - @RequestParam(name = "routeId", required = false) Long routeId, - @RequestParam(name = "stationId", required = false) Long stationId, - @RequestParam(name = "startDateTime", required = false) String startDateTime, - @RequestParam(name = "endDateTime", required = false) String endDateTime - ) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - LocalDateTime start = LocalDateTime.parse(startDateTime, formatter); - LocalDateTime end = LocalDateTime.parse(endDateTime, formatter); - BusStatReqDto statReqDto = new BusStatReqDto( - routeId, - stationId, - start, - end - ); - List statList = busStatService.getBusStat(statReqDto); - return ResponseEntity.ok(statList); - } - - // 현재 시간 기준 이전30분 이후30분 구간의 같은 요일 통계 조회 - @GetMapping("/now") - public ResponseEntity> getBusStatsNow( - @RequestParam(name = "routeId", required = false) Long routeId, - @RequestParam(name = "stationId", required = false) Long stationId - ) { - List statList = busStatService.getBusStatNow(routeId, stationId); - return ResponseEntity.ok(statList); - } - - @PostMapping("/makeStatData") - public String makeStatData() { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - LocalDateTime start = LocalDateTime.parse("2000-01-01 00:00", formatter); - LocalDateTime end = LocalDateTime.parse("2100-12-31 23:59", formatter); - busStatService.makeStatDataBetween(start, end); - return "success"; - } -} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusLocationEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusLocationEntity.java index 3a910d8c..1406003c 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusLocationEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusLocationEntity.java @@ -42,7 +42,7 @@ public class BusLocationEntity { private String apiStationId; @Column(name = "station_seq", nullable = false) - private Short stationSeq; + private Integer stationSeq; @Column(name = "end_bus", nullable = false, length = 1) @Convert(converter = EndBusConverter.class) @@ -60,7 +60,7 @@ public class BusLocationEntity { private PlateType plateType; @Column(name = "remain_seat_count", nullable = false) - private Short remainSeatCount; + private Integer remainSeatCount; @Column(name = "api_call_no", nullable = false) private Integer apiCallNo; diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java new file mode 100644 index 00000000..61c118c2 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java @@ -0,0 +1,56 @@ +package com.talkka.server.bus.dao; + +import java.util.List; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.talkka.server.bus.enums.PlateType; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity(name = "bus_plate_statistic") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class BusPlateStatisticEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "route_id", nullable = false) + private BusRouteEntity route; + + @Column(name = "plate_type", nullable = false) + private PlateType plateType; + + @Column(name = "plate_no", nullable = false) + private String plateNo; + + @Column(name = "epoch_day", nullable = false) + private Long epochDay; + + @Column(name = "start_time", nullable = false) + private Integer startTime; + + @Column(name = "end_time", nullable = false) + private Integer endTime; + + @OneToMany(mappedBy = "routeInfo", cascade = CascadeType.PERSIST) + private List seats; +} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticRepository.java new file mode 100644 index 00000000..a8407bcf --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticRepository.java @@ -0,0 +1,8 @@ +package com.talkka.server.bus.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BusPlateStatisticRepository extends JpaRepository { +} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java new file mode 100644 index 00000000..fe4cb1cc --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java @@ -0,0 +1,115 @@ +package com.talkka.server.bus.dao; + +import java.time.LocalDateTime; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.talkka.server.bus.enums.PlateType; +import com.talkka.server.bus.util.PlateTypeConverter; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity(name = "bus_remain_seat") +@Getter +@Builder(builderMethodName = "defaultBuilder") +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class BusRemainSeatEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "route_id", nullable = false) + private BusRouteEntity route; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "station_id", nullable = false) + private BusStationEntity station; + + @Column(name = "station_seq", nullable = false) + private Integer stationSeq; + + @Column(name = "empty_seat", nullable = false) + private Integer emptySeat; + + @Column(name = "plate_no", nullable = false, length = 32) + private String plateNo; + + @Column(name = "plate_type", nullable = false, length = 1) + @Convert(converter = PlateTypeConverter.class) + private PlateType plateType; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Column(name = "epoch_day", nullable = false) + private Long epochDay; + + @Column(name = "time", nullable = false) + private Integer time; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "route_info_id") + private BusPlateStatisticEntity routeInfo; + + // Builder Class + public static class BusRemainSeatEntityBuilder { + public BusRemainSeatEntityBuilder setCreateTime(LocalDateTime createdAt) { + this.createdAt = createdAt; + this.epochDay = getEpochDay(createdAt); + this.time = getTime(createdAt); + return this; + } + } + + public static BusRemainSeatEntityBuilder build() { + return defaultBuilder(); + } + + public void updateRouteInfo(BusPlateStatisticEntity routeInfo) { + this.routeInfo = routeInfo; + } + + /** + * 주어진 {@code LocalDateTime} 객체의 시간과 분을 이어붙여 정수 형태로 반환합니다. + * 예를 들어, 23:59는 2359로, 08:27은 827로 반환됩니다. + * + * @param localDateTime 시간을 추출할 {@code LocalDateTime} 객체입니다. + * @return 시간과 분을 이어붙인 정수 값입니다. + */ + private static int getTime(LocalDateTime localDateTime) { + return localDateTime.getHour() * 100 + localDateTime.getMinute(); + } + + /** + * 주어진 {@code LocalDateTime} 객체에 대해 새벽 3시를 기준으로 날짜를 변경하여 epochDay 값을 반환합니다. + * 시간대가 3시 이전인 경우, 전날의 epochDay 를 반환하며, 그렇지 않은 경우 해당 날짜의 epochDay 를 반환합니다. + * 반환되는 값은 1에서 7 사이의 값을 가집니다. + * + * @param localDateTime 날짜를 계산할 {@code LocalDateTime} 객체입니다. + * @return 새벽 3시를 기준으로 계산된 epochDay 값입니다. + */ + private static Long getEpochDay(LocalDateTime localDateTime) { + if (localDateTime.getHour() < 3) { + return localDateTime.toLocalDate().toEpochDay() - 1; + } + return localDateTime.toLocalDate().toEpochDay(); + } + +} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java new file mode 100644 index 00000000..100c4f3c --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java @@ -0,0 +1,19 @@ +package com.talkka.server.bus.dao; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.talkka.server.bus.enums.PlateType; + +public interface BusRemainSeatRepository extends JpaRepository { + List findByRouteIdAndStationIdAndEpochDayAndTimeBetween(Long routeId, Long stationId, + Long epochDay, Integer startTime, Integer endTime); + + List findByRouteInfoAndPlateTypeAndStationSeqBetweenOrderByStationSeq( + BusPlateStatisticEntity routeInfo, + PlateType plateType, Integer startSeq, Integer endSeq); + + List findByRouteInfoAndStationSeqBetweenOrderByStationSeq(BusPlateStatisticEntity routeInfo, + Integer startSeq, Integer endSeq); +} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRouteStationRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusRouteStationRepository.java index dd8bf57f..1cad50f1 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusRouteStationRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRouteStationRepository.java @@ -12,4 +12,9 @@ public interface BusRouteStationRepository extends JpaRepository findAllByStationId(Long stationId); List findAllByRouteIdAndStationId(Long routeId, Long stationId); + + // 노선의 이전/다음 정거장 존재 유무를 확인하기 위함 + BusRouteStationEntity findByRouteAndStationSeq(BusRouteEntity route, Short stationSeq); + + int countAllByRoute(BusRouteEntity route); } diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java deleted file mode 100644 index 4d08076b..00000000 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatEntity.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.talkka.server.bus.dao; - -import java.time.LocalDateTime; -import java.util.Objects; - -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import com.talkka.server.bus.enums.PlateType; -import com.talkka.server.bus.util.PlateTypeConverter; - -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity(name = "bus_stat") -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -public class BusStatEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "route_id", nullable = true) - private BusRouteEntity route; - - @ManyToOne - @JoinColumn(name = "station_id", nullable = true) - private BusStationEntity station; - - @Column(name = "api_route_id", nullable = false) - private String apiRouteId; - - @Column(name = "api_station_id", nullable = false) - private String apiStationId; - - @Column(name = "before_seat", nullable = false) - private Integer beforeSeat; - - @Column(name = "after_seat", nullable = false) - private Integer afterSeat; - - @Column(name = "seat_diff", nullable = false) - private Integer seatDiff; - - @Column(name = "before_time", nullable = false) - private LocalDateTime beforeTime; - - @Column(name = "after_time", nullable = false) - private LocalDateTime afterTime; - - @Column(name = "plate_no", nullable = false, length = 32) - private String plateNo; - - @Column(name = "plate_type", nullable = false, length = 1) - @Convert(converter = PlateTypeConverter.class) - private PlateType plateType; - - @Column(name = "day_of_week", nullable = false) - private Integer dayOfWeek; - - @Column(name = "time", nullable = false) - private Integer time; - - @Column(name = "created_at", nullable = false) - @CreatedDate - private LocalDateTime createdAt; - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - BusStatEntity that = (BusStatEntity)obj; - return getId().equals(that.getId()); - } - - // 내용이 중복된 BusStatEntity 식별을 위한 메소드 - public int identifier() { - return Objects.hash( - this.getApiRouteId(), - this.getApiStationId(), - this.getBeforeSeat(), - this.getBeforeTime(), - this.getAfterSeat(), - this.getAfterTime(), - this.getSeatDiff(), - this.getPlateNo(), - this.getPlateType() - ); - } -} diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java deleted file mode 100644 index 1e492911..00000000 --- a/server/src/main/java/com/talkka/server/bus/dao/BusStatRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.talkka.server.bus.dao; - -import java.time.LocalDateTime; -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -@Repository -public interface BusStatRepository extends JpaRepository { - List findByBeforeTimeBetween(LocalDateTime startTime, LocalDateTime endTime); - - List findByRouteIdAndStationIdAndBeforeTimeBetween( - Long routeId, - Long stationId, - LocalDateTime startTime, - LocalDateTime endTime - ); - - @Query("SELECT b FROM bus_stat b WHERE b.route.id = :routeId AND b.station.id = :stationId AND b.dayOfWeek = :dayOfWeek AND (b.time >= :startTime AND b.time <= :endTime)") - List findByRouteIdAndStationIdAndDayOfWeekBetweenTime( - @Param("routeId") Long routeId, - @Param("stationId") Long stationId, - @Param("dayOfWeek") Integer dayOfWeek, - @Param("startTime") int startTime, - @Param("endTime") int endTime - ); - -} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java deleted file mode 100644 index 59c0291d..00000000 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatReqDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.talkka.server.bus.dto; - -import java.time.LocalDateTime; - -public record BusStatReqDto( - Long routeId, - Long stationId, - LocalDateTime startDateTime, - LocalDateTime endDateTime -) { -} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java deleted file mode 100644 index 54ef2d96..00000000 --- a/server/src/main/java/com/talkka/server/bus/dto/BusStatRespDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.talkka.server.bus.dto; - -import com.talkka.server.bus.dao.BusStatEntity; -import com.talkka.server.bus.enums.PlateType; - -public record BusStatRespDto( - Integer beforeSeat, - Integer afterSeat, - Integer seatDiff, - PlateType plateType, - Integer dayOfWeek, - Integer time -) { - public static BusStatRespDto of(BusStatEntity busStatEntity) { - return new BusStatRespDto( - busStatEntity.getBeforeSeat(), - busStatEntity.getAfterSeat(), - busStatEntity.getSeatDiff(), - busStatEntity.getPlateType(), - busStatEntity.getDayOfWeek(), - busStatEntity.getTime() - ); - } -} diff --git a/server/src/main/java/com/talkka/server/bus/exception/BusRouteNotFoundException.java b/server/src/main/java/com/talkka/server/bus/exception/BusRouteNotFoundException.java index 14d4097c..62b089e3 100644 --- a/server/src/main/java/com/talkka/server/bus/exception/BusRouteNotFoundException.java +++ b/server/src/main/java/com/talkka/server/bus/exception/BusRouteNotFoundException.java @@ -1,9 +1,9 @@ package com.talkka.server.bus.exception; public class BusRouteNotFoundException extends RuntimeException { - private static final String MESSAGE = "존재하지 않는 노선입니다. routeId: "; + private static final String MESSAGE = "존재하지 않는 노선입니다."; public BusRouteNotFoundException(Long routeId) { - super(MESSAGE + routeId); + super(MESSAGE + "routeId: " + routeId); } } diff --git a/server/src/main/java/com/talkka/server/bus/exception/InvalidLocationNotFoundException.java b/server/src/main/java/com/talkka/server/bus/exception/InvalidLocationNotFoundException.java new file mode 100644 index 00000000..f5cd0910 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/exception/InvalidLocationNotFoundException.java @@ -0,0 +1,9 @@ +package com.talkka.server.bus.exception; + +public class InvalidLocationNotFoundException extends RuntimeException { + private static final String MESSAGE = "올바르지 않은 위치정보 입니다."; + + public InvalidLocationNotFoundException() { + super(MESSAGE); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java b/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java new file mode 100644 index 00000000..dfd2c8dc --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java @@ -0,0 +1,293 @@ +package com.talkka.server.bus.service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.talkka.server.bus.dao.BusLocationEntity; +import com.talkka.server.bus.dao.BusPlateStatisticEntity; +import com.talkka.server.bus.dao.BusPlateStatisticRepository; +import com.talkka.server.bus.dao.BusRemainSeatEntity; +import com.talkka.server.bus.dao.BusRouteEntity; +import com.talkka.server.bus.dao.BusRouteRepository; +import com.talkka.server.bus.dao.BusRouteStationEntity; +import com.talkka.server.bus.dao.BusRouteStationRepository; +import com.talkka.server.bus.dao.BusStationEntity; +import com.talkka.server.bus.dao.BusStationRepository; +import com.talkka.server.bus.enums.PlateType; +import com.talkka.server.bus.exception.InvalidLocationNotFoundException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class BusLocationProcessor { + private final BusRouteRepository busRouteRepository; + private final BusStationRepository busStationRepository; + private final BusRouteStationRepository busRouteStationRepository; + private final BusPlateStatisticRepository busPlateStatisticRepository; + private final Map> locationMap = new HashMap<>(); + private final Map> routeMap = new HashMap<>(); + private final Map> stationMap = new HashMap<>(); + private final Map> routeStationMap = new HashMap<>(); + private final Map routeLengthMap = new HashMap<>(); + private final List routeInfoList = new ArrayList<>(); + + @Transactional + public void start(List locations) { + for (var location : locations) { + try { + if (locationMap.containsKey(location.getPlateNo())) { + var dq = locationMap.get(location.getPlateNo()); + if (!dq.isEmpty() && location.getStationSeq() < dq.peekLast().getStationSeq()) { + process(dq); + dq = new ArrayDeque<>(); + locationMap.put(location.getPlateNo(), dq); + } + dq.add(location); + } else { + Deque dq = new ArrayDeque<>(); + dq.add(location); + locationMap.put(location.getPlateNo(), dq); + } + } catch (InvalidLocationNotFoundException e) { + log.warn("버스 위치정보 가공 실패 locationId : {}, apiRouteId : {}, apiStationId : {}, plateNo : {} ", + location.getBusLocationId(), + location.getApiRouteId(), + location.getApiStationId(), + location.getPlateNo()); + } + } + persist(); + } + + private void persist() { + for (String key : locationMap.keySet()) { + try { + var tmp = locationMap.get(key); + if (!tmp.isEmpty()) { + process(tmp); + } + } catch (InvalidLocationNotFoundException e) { + log.warn("버스 위치정보 가공 실패"); + } + } + busPlateStatisticRepository.saveAll(routeInfoList); + } + + /** + * 특정 버스(plateNo)를 기준으로 수집된 데이터를 처리합니다. + * 빠져있는 정거장 정보를 양 옆 정거장의 평균 정보로 대치하여 전체 노선 데이터를 완성하고, 이를 {@code routeInfoList}에 추가합니다. + * + * @param locations 특정 버스의 위치 정보를 담고 있는 {@code Deque}입니다. + * 이 정보는 특정 버스에 대한 다양한 정거장에서의 위치 데이터를 포함합니다. + */ + private void process(Deque locations) { + assert locations.peek() != null; + String apiRouteId = locations.peek().getApiRouteId(); + String apiStationId = locations.peek().getApiStationId(); + String plateNo = locations.peek().getPlateNo(); + LocalDateTime createdAt = locations.peek().getCreatedAt(); + List result = new ArrayList<>(); + // 노선의 길이 확인 + int num = getRouteLength(apiRouteId); + // 노선 손실률이 80% 이상이면 저장하지 않음 + if (locations.size() < num * 0.2) { + return; + } + var routeStationList = getRouteStationByRouteId(apiRouteId); + try { + if (locations.peek().getStationSeq() != 1) { + result.add(makeSeatEntity(Optional.empty(), Optional.of(locations.peek()), 1, + routeStationList.get(0).getStation())); + } else { + result.add(toSeatEntity(locations.poll(), 1)); + } + for (int i = 2; i <= num; i++) { + BusLocationEntity cur = null; + // StationSeq 가 중복되면 마지막것을 선택 + while (!locations.isEmpty() && locations.peek().getStationSeq() == i) { + cur = locations.poll(); + } + // 특정 정거장의 위치정보가 없거나 좌석정보가 없으면 양옆 정류장 데이터의 평균으로 대치 + if (cur == null || cur.getRemainSeatCount() == -1) { + if (locations.isEmpty()) { + result.add(makeSeatEntity(Optional.of(result.get(i - 2)), Optional.empty(), i, + routeStationList.get(i - 1).getStation())); + } else { + result.add(makeSeatEntity(Optional.of(result.get(i - 2)), Optional.of(locations.peek()), i, + routeStationList.get(i - 1).getStation())); + } + } else { + result.add(toSeatEntity(cur, i)); + } + } + } catch (InvalidLocationNotFoundException e) { + log.warn("버스 위치정보 가공 실패 : 노선({}), 정거장({}), 버스이름({}), 생성일({})", + apiRouteId, apiStationId, plateNo, createdAt); + } + BusPlateStatisticEntity routeInfo = BusPlateStatisticEntity.builder() + .route(result.get(0).getRoute()) + .plateNo(result.get(0).getPlateNo()) + .plateType(result.get(0).getPlateType()) + .epochDay(result.get(0).getEpochDay()) + .startTime(result.get(0).getTime()) + .endTime(result.get(result.size() - 1).getTime()) + .seats(result) + .build(); + result.forEach(seat -> seat.updateRouteInfo(routeInfo)); + routeInfoList.add(routeInfo); + } + + private BusRemainSeatEntity toSeatEntity(BusLocationEntity location, int stationSeq) { + var route = getRoute(location.getApiRouteId()); + var station = getStation(location.getApiStationId()); + return BusRemainSeatEntity.build() + .route(route) + .station(station) + .stationSeq(stationSeq) + .emptySeat(location.getRemainSeatCount()) + .plateNo(location.getPlateNo()) + .plateType(location.getPlateType()) + .setCreateTime(location.getCreatedAt()) + .build(); + } + + /** + * 비어있는 엔티티가 있을 경우, 좌우 데이터를 비교하여 새로운 {@code BusRemainSeatEntity}를 생성합니다. + * + *

이 메서드는 이전 정거장과 이후 정거장의 데이터를 비교하여 평균값을 계산하거나, + * 기점/종점에 따른 특수 조건을 적용하여 새로운 좌석 정보를 생성합니다.

+ * + * @param before 이전 정거장의 좌석 정보. {@code Optional}로 제공되며, + * 비어있을 수도 있습니다. + * @param after 이후 정거장의 위치 정보. {@code Optional}로 제공되며, + * 비어있을 수도 있습니다. + * @param stationSeq 정거장의 순서 번호입니다. + * @param station 현재 정거장을 나타내는 {@code BusStationEntity}입니다. + * @return 새롭게 생성된 {@code BusRemainSeatEntity} 객체입니다. + * @throws InvalidLocationNotFoundException 이전 정거장과 이후 정거장 정보가 모두 없는 경우에 발생합니다. + */ + private BusRemainSeatEntity makeSeatEntity(Optional before, Optional after, + int stationSeq, BusStationEntity station) throws + InvalidLocationNotFoundException { + if (before.isPresent() && after.isPresent()) { + var beforeEntity = before.get(); + var afterEntity = after.get(); + var route = beforeEntity.getRoute(); + return BusRemainSeatEntity.build() + .route(route) + .station(station) + .stationSeq(stationSeq) + .emptySeat((beforeEntity.getEmptySeat() + afterEntity.getRemainSeatCount()) / 2) + .plateNo(beforeEntity.getPlateNo()) + .plateType(beforeEntity.getPlateType()) + .setCreateTime(getBetweenTime(beforeEntity.getCreatedAt(), afterEntity.getCreatedAt(), + afterEntity.getStationSeq() - beforeEntity.getStationSeq())) + .build(); + } else if (before.isPresent()) { + var beforeEntity = before.get(); + int emptySeat = beforeEntity.getEmptySeat(); + // 종점에서는 모든 자리가 비어있음 + if (stationSeq == getRouteLength(beforeEntity.getRoute().getApiRouteId())) { + if (beforeEntity.getPlateType() == PlateType.LARGE) { + emptySeat = 45; + } else { + emptySeat = 70; + } + } + var route = beforeEntity.getRoute(); + return BusRemainSeatEntity.build() + .route(route) + .station(station) + .stationSeq(stationSeq) + .emptySeat(emptySeat) + .plateNo(beforeEntity.getPlateNo()) + .plateType(beforeEntity.getPlateType()) + .setCreateTime(beforeEntity.getCreatedAt().plusMinutes(3)) + .build(); + } else if (after.isPresent()) { + var afterEntity = after.get(); + int emptySeat = afterEntity.getRemainSeatCount(); + // 기점에서는 모든 자리가 비어있음 + if (stationSeq == 1) { + if (afterEntity.getPlateType() == PlateType.LARGE) { + emptySeat = 45; + } else { + emptySeat = 70; + } + } + var route = getRoute(afterEntity.getApiRouteId()); + return BusRemainSeatEntity.build() + .route(route) + .station(station) + .stationSeq(stationSeq) + .emptySeat(emptySeat) + .plateNo(afterEntity.getPlateNo()) + .plateType(afterEntity.getPlateType()) + .setCreateTime(afterEntity.getCreatedAt().minusMinutes(3)) + .build(); + } else { + throw new InvalidLocationNotFoundException(); + } + } + + private BusRouteEntity getRoute(String apiRouteId) throws InvalidLocationNotFoundException { + if (!routeMap.containsKey(apiRouteId)) { + routeMap.put(apiRouteId, busRouteRepository.findByApiRouteId(apiRouteId)); + } + return routeMap.get(apiRouteId).orElseThrow(InvalidLocationNotFoundException::new); + } + + private BusStationEntity getStation(String apiStationId) throws InvalidLocationNotFoundException { + if (!stationMap.containsKey(apiStationId)) { + stationMap.put(apiStationId, busStationRepository.findByApiStationId(apiStationId)); + } + return stationMap.get(apiStationId).orElseThrow(InvalidLocationNotFoundException::new); + } + + private List getRouteStationByRouteId(String apiRouteId) { + var routeId = getRoute(apiRouteId).getId(); + if (!routeStationMap.containsKey(routeId)) { + var list = busRouteStationRepository.findAllByRouteId(routeId); + list.sort(Comparator.comparingInt(BusRouteStationEntity::getStationSeq)); + routeStationMap.put(routeId, list); + } + return routeStationMap.get(routeId); + } + + private Integer getRouteLength(String apiRouteId) throws InvalidLocationNotFoundException { + if (!routeLengthMap.containsKey(apiRouteId)) { + var route = getRoute(apiRouteId); + routeLengthMap.put(apiRouteId, busRouteStationRepository.countAllByRoute(route)); + } + return routeLengthMap.get(apiRouteId); + } + + /** + * 주어진 두 시간 {@code time1}과 {@code time2} 사이의 구간을 주어진 개수 {@code num}만큼 나누어 + * 그중 첫 번째 구간의 시작 시간을 반환합니다. + * + * @param time1 시작 시간으로 사용할 {@code LocalDateTime}입니다. + * @param time2 종료 시간으로 사용할 {@code LocalDateTime}입니다. + * @param num 두 시간 사이의 구간을 나눌 개수입니다. + * @return 첫 번째 구간의 끝 시간에 해당하는 {@code LocalDateTime} 객체를 반환합니다. + */ + private LocalDateTime getBetweenTime(LocalDateTime time1, LocalDateTime time2, int num) { + Duration duration = Duration.between(time1, time2); + Duration halfDuration = duration.dividedBy(num); + return time1.plus(halfDuration); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java b/server/src/main/java/com/talkka/server/bus/service/BusStatService.java deleted file mode 100644 index ce4798be..00000000 --- a/server/src/main/java/com/talkka/server/bus/service/BusStatService.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.talkka.server.bus.service; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.talkka.server.bus.dao.BusLocationEntity; -import com.talkka.server.bus.dao.BusLocationRepository; -import com.talkka.server.bus.dao.BusRouteEntity; -import com.talkka.server.bus.dao.BusRouteRepository; -import com.talkka.server.bus.dao.BusStatEntity; -import com.talkka.server.bus.dao.BusStatRepository; -import com.talkka.server.bus.dao.BusStationEntity; -import com.talkka.server.bus.dao.BusStationRepository; -import com.talkka.server.bus.dto.BusStatReqDto; -import com.talkka.server.bus.dto.BusStatRespDto; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class BusStatService { - private final BusRouteRepository busRouteRepository; - private final BusStationRepository busStationRepository; - private final BusLocationRepository busLocationRepository; - private final BusStatRepository busStatRepository; - - public List getBusStat(BusStatReqDto busStatReqDto) { - return busStatRepository.findByRouteIdAndStationIdAndBeforeTimeBetween( - busStatReqDto.routeId(), - busStatReqDto.stationId(), - busStatReqDto.startDateTime(), - busStatReqDto.endDateTime() - ).stream() - .map(BusStatRespDto::of) - .toList(); - } - - public List getBusStatNow(Long routeId, Long stationId) { - LocalDateTime now = LocalDateTime.now(); - int startTime = getTime(now.minusMinutes(30)); - int endTime = getTime(now.plusMinutes(30)); - List result; - // 두 날짜에 겹쳐있는 경우 ex) 2349 ~ 0049 - if (startTime > endTime) { - result = busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( - routeId, - stationId, - getDayOfWeek(now), - startTime, - 2359 - ); - result.addAll(busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( - routeId, - stationId, - getDayOfWeek(now), - 0, - endTime - )); - } else { - result = busStatRepository.findByRouteIdAndStationIdAndDayOfWeekBetweenTime( - routeId, - stationId, - getDayOfWeek(now), - startTime, - endTime - ); - } - return result.stream() - .map(BusStatRespDto::of) - .toList(); - } - - // 생성된 위치정보 중 start~end 기간에 있는 것들만 가공 - @Transactional - public void makeStatDataBetween(LocalDateTime start, LocalDateTime end) { - List locationList = busLocationRepository.findByCreatedAtBetween(start, end); - Map locationMap = new HashMap<>(); - Map routeMap = new HashMap<>(); - Map stationMap = new HashMap<>(); - busRouteRepository.findAll().forEach(route -> routeMap.put(route.getApiRouteId(), route)); - busStationRepository.findAll().forEach(station -> stationMap.put(station.getApiStationId(), station)); - // 가공하려는 기간에 속하는 통계정보를 가져와 엔티티의 해시코드를 set 에 저장 - Set statIdentifierSet = new HashSet<>(); - busStatRepository.findByBeforeTimeBetween(start, end).forEach(stat -> statIdentifierSet.add(stat.identifier())); - - for (BusLocationEntity afterLocation : locationList) { - // 같은 버스의 위치정보가 map 에 없으면 넣고 넘김 - if (!locationMap.containsKey(afterLocation.getPlateNo())) { - locationMap.put(afterLocation.getPlateNo(), afterLocation); - } else { - // 같은 버스의 직전 위치를 map 에서 가져옴 - BusLocationEntity beforeLocation = locationMap.get(afterLocation.getPlateNo()); - - if (// 위치정보들 간 시간 차이가 1시간 미만이어야함 - Duration.between(beforeLocation.getCreatedAt(), afterLocation.getCreatedAt()).toHours() < 1 - // 위치정보들 간 정거장 차이가 1정거장 이하여야함 - && afterLocation.getStationSeq() - beforeLocation.getStationSeq() <= 1 - // 위치정보들 둘다 좌석정보를 가지고 있어야함 - && beforeLocation.getRemainSeatCount() != -1 && afterLocation.getRemainSeatCount() != -1 - ) { - BusRouteEntity route = routeMap.get(beforeLocation.getApiRouteId()); - BusStationEntity station = stationMap.get(beforeLocation.getApiStationId()); - BusStatEntity statEntity = toBusStatEntity(beforeLocation, afterLocation, route, station); - // 이미 존재하는 통계 정보인지 확인한 후 db에 저장 - if (!statIdentifierSet.contains(statEntity.identifier())) { - busStatRepository.save(statEntity); - } - } - // 위치정보 갱신 - locationMap.put(afterLocation.getPlateNo(), afterLocation); - } - } - } - - // 두개의 위치정보를 가지고 BusStatEntity 를 생성 - private static BusStatEntity toBusStatEntity(BusLocationEntity before, BusLocationEntity after, - BusRouteEntity route, BusStationEntity station) { - return BusStatEntity.builder() - .route(route) - .station(station) - .apiRouteId(after.getApiRouteId()) - .apiStationId(before.getApiStationId()) - .beforeSeat(before.getRemainSeatCount().intValue()) - .afterSeat(after.getRemainSeatCount().intValue()) - .seatDiff(after.getRemainSeatCount() - before.getRemainSeatCount()) - .beforeTime(before.getCreatedAt()) - .afterTime(after.getCreatedAt()) - .plateNo(after.getPlateNo()) - .plateType(after.getPlateType()) - .dayOfWeek(getDayOfWeek(before.getCreatedAt())) - .time(getTime(before.getCreatedAt())) - .build(); - } - - // 시간, 분을 이어붙인 int 값 ex) 23:59 -> 2359, 08:27->827 - private static int getTime(LocalDateTime localDateTime) { - return localDateTime.getHour() * 100 + localDateTime.getMinute(); - } - - // 새벽 3시 기준으로 요일 변경 1-7 사이 값을 가짐 - private static int getDayOfWeek(LocalDateTime localDateTime) { - if (localDateTime.getHour() < 3) { - int beforeDayOfWeek = localDateTime.getDayOfWeek().getValue() - 1; - return beforeDayOfWeek == 0 ? 7 : beforeDayOfWeek; - } - return localDateTime.getDayOfWeek().getValue(); - } - - // old logic - // @Deprecated - // @Transactional - // public void process() { - // List apiCallNoList = busLocationRepository.getDistinctApiCallNoList(); - // apiCallNoList.sort(Comparator.naturalOrder()); - // Map prev = new HashMap<>(); - // System.out.println(apiCallNoList); - // for (Integer apiCallNo : apiCallNoList) { - // Map cur = new HashMap<>(); - // List locationList = busLocationRepository.findByApiCallNo(apiCallNo); - // for (BusLocationEntity location : locationList) { - // BusLocationEntity beforeLocation; - // if ((beforeLocation = prev.get(location.getPlateNo())) != null - // // 두정거장 이상 넘어가면 체크하지 않음 - // && location.getStationSeq() - beforeLocation.getStationSeq() <= 1 && ( - // location.getRemainSeatCount() != -1 && beforeLocation.getRemainSeatCount() != -1)) { - // BusStatEntity statEntity = toBusStatEntity(beforeLocation, location); - // busStatRepository.save(statEntity); - // } - // cur.put(location.getPlateNo(), location); - // } - // prev = cur; - // } - // } -} From 5dc73ce8f87f813098c473383ba33356e7965577 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 13:00:45 +0900 Subject: [PATCH 24/31] =?UTF-8?q?feat=20:=20=EB=B2=84=EC=8A=A4=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=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/controller/BusViewController.java | 33 +++++ .../server/bus/dto/BusRemainSeatDto.java | 40 ++++++ .../com/talkka/server/bus/dto/BusViewDto.java | 52 ++++++++ .../server/bus/service/BusViewService.java | 115 ++++++++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusViewController.java create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusViewDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusViewService.java diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java b/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java new file mode 100644 index 00000000..1b64be2e --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java @@ -0,0 +1,33 @@ +package com.talkka.server.bus.controller; + +import java.time.LocalDateTime; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.talkka.server.bus.dto.BusViewDto; +import com.talkka.server.bus.service.BusViewService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/bus/route-info") +public class BusViewController { + private final BusViewService busViewService; + + @GetMapping("/now") + public ResponseEntity getNow( + @RequestParam(name = "routeStationId", required = false, defaultValue = "17499") Long routeStationId, + @RequestParam(name = "stationNum", required = false, defaultValue = "5") Integer stationNum, + @RequestParam(name = "timeRange", required = false, defaultValue = "45") Integer timeRange, + @RequestParam(name = "week", required = false, defaultValue = "2") Long week + ) { + LocalDateTime time = LocalDateTime.now().plusHours(8); + return ResponseEntity.ok(busViewService.getBusView(routeStationId, stationNum, time, timeRange, week)); + } + +} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java new file mode 100644 index 00000000..7ee03c4c --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java @@ -0,0 +1,40 @@ +package com.talkka.server.bus.dto; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import com.talkka.server.bus.dao.BusRemainSeatEntity; +import com.talkka.server.bus.enums.PlateType; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +public class BusRemainSeatDto { + @Getter + @AllArgsConstructor + private static class SeatInfo implements Serializable { + LocalDateTime arrivedTime; + Integer remainSeat; + } + + private PlateType plateType; + private String plateNo; + private LocalDateTime standardTime; + private List remainSeatList; + + public BusRemainSeatDto(List seats) { + remainSeatList = new ArrayList<>(); + + for (var seat : seats) { + plateType = seat.getPlateType(); + plateNo = seat.getPlateNo(); + remainSeatList.add(new SeatInfo(seat.getCreatedAt(), seat.getEmptySeat())); + } + if (!seats.isEmpty()) { + standardTime = seats.get(seats.size() >> 1).getCreatedAt(); + } + } +} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusViewDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusViewDto.java new file mode 100644 index 00000000..e10793c6 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusViewDto.java @@ -0,0 +1,52 @@ +package com.talkka.server.bus.dto; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.talkka.server.bus.dao.BusRemainSeatEntity; +import com.talkka.server.bus.dao.BusRouteStationEntity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class BusViewDto { + + @Getter + @AllArgsConstructor + private static class StationInfo implements Serializable { + Long stationId; + String stationName; + } + + private LocalDateTime requestTime; + private Long routeId; + private String routeName; + private Integer stationNum; + private Integer busNum; + private List stationList; + private List data; + + public BusViewDto(LocalDateTime requestTime, List routeStationList, + List> data) { + this.requestTime = requestTime; + this.stationList = new ArrayList<>(); + for (var rs : routeStationList) { + stationList.add(new StationInfo(rs.getStation().getId(), rs.getStation().getStationName())); + this.routeId = rs.getRoute().getId(); + this.routeName = rs.getRoute().getRouteName(); + } + this.stationNum = routeStationList.size(); + this.busNum = data.size(); + this.data = data.stream() + .map(BusRemainSeatDto::new) + .sorted(Comparator.comparing(BusRemainSeatDto::getStandardTime)) + .toList(); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusViewService.java b/server/src/main/java/com/talkka/server/bus/service/BusViewService.java new file mode 100644 index 00000000..739f68d0 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusViewService.java @@ -0,0 +1,115 @@ +package com.talkka.server.bus.service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.talkka.server.bus.dao.BusRemainSeatEntity; +import com.talkka.server.bus.dao.BusRemainSeatRepository; +import com.talkka.server.bus.dao.BusRouteStationEntity; +import com.talkka.server.bus.dao.BusRouteStationRepository; +import com.talkka.server.bus.dto.BusViewDto; +import com.talkka.server.bus.exception.BusRouteNotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BusViewService { + private final BusRemainSeatRepository busRemainSeatRepository; + private final BusRouteStationRepository busRouteStationRepository; + + /** + * 버스 경로 정보를 조회합니다. + * + * @param routeStationId 기준 노선정거장 ID + * @param stationNum 현재 정거장에서 전후 몇 정거장을 조회할지 설정 + * ex) stationNum = 3 -> 기준 노선정거장 3개 전~3개 후 조회 + * @param time 기준 시간 + * @param timeRangeMinute 기준 시간 전후 몇 분까지 조회할지 설정 + * ex) timeRangeMinute = 3 -> 기준 시간 3분 전~3분 후 조회 + * @param week 몇 주 전의 데이터를 조회할지 설정 + * ex) week = 2 -> 2주 전 같은 요일의 데이터를 조회 + * @return BusRouteInfoRespDto 버스 경로 정보 + */ + public BusViewDto getBusView( + Long routeStationId, + Integer stationNum, + LocalDateTime time, + Integer timeRangeMinute, + Long week + ) { + // 타겟 노선정거장 조회 + var routeStation = busRouteStationRepository.findById(routeStationId) + .orElseThrow(() -> new BusRouteNotFoundException(routeStationId)); + Long routeId = routeStation.getRoute().getId(); + Long stationId = routeStation.getStation().getId(); + // 타겟 노선이 지나가는 모든 노선정거장 조회 + var routeStationList = busRouteStationRepository.findAllByRouteId(routeId) + .stream() + .filter(rs -> Math.abs(rs.getStationSeq() - routeStation.getStationSeq()) <= stationNum) + .sorted(Comparator.comparing(BusRouteStationEntity::getStationSeq)) + .toList(); + + // 새벽 3시까지는 전날로 침 + long epochDay = time.getHour() < 3 ? time.toLocalDate().toEpochDay() - 1 : time.toLocalDate().toEpochDay(); + var timeIntervals = getTimeInterval(time, timeRangeMinute); + + // 조회 조건에 맞는 버스 도착 정보 가져옴 + List busSeats = new ArrayList<>(); + for (var timeInterval : timeIntervals) { + busSeats.addAll( + busRemainSeatRepository.findByRouteIdAndStationIdAndEpochDayAndTimeBetween( + routeId, + stationId, + epochDay - 7 * week, + timeInterval[0], + timeInterval[1] + ) + ); + } + List> data = new ArrayList<>(); + for (var seat : busSeats) { + // 타겟 정거장 전후 도착정보 + data.add( + busRemainSeatRepository.findByRouteInfoAndStationSeqBetweenOrderByStationSeq( + seat.getRouteInfo(), + seat.getStationSeq() - stationNum, + seat.getStationSeq() + stationNum + ) + ); + } + return new BusViewDto( + time, + routeStationList, + data + ); + } + + /** + * 시간 구간이 0시를 넘어가는 경우, 두 개의 구간으로 나누어 검색을 수행합니다. + * 예를 들어, 23:43에서 00:13 사이의 시간 구간은 23:43-23:59 및 00:00-00:13 두 개의 구간으로 나누어집니다. + * + * @param time 기준 시간으로, 이 시간으로부터 {@code timeRange} 분 전후의 구간을 계산합니다. + * @param timeRange 기준 시간에서 검색할 분 단위의 시간 범위입니다. + * @return 시간 구간을 나타내는 {@code List}를 반환합니다. + * 각 배열은 [시작 시간, 종료 시간] 형식으로 시간 구간을 나타냅니다. + * 시간이 0시를 넘어가는 경우, 두 개의 구간으로 나누어 반환합니다. + */ + private List getTimeInterval(LocalDateTime time, Integer timeRange) { + List intervals = new ArrayList<>(); + int startTime = + time.minusMinutes(timeRange).getHour() * 100 + time.minusMinutes(timeRange).getMinute(); + int endTime = time.plusMinutes(timeRange).getHour() * 100 + time.plusMinutes(timeRange).getMinute(); + if (startTime < endTime) { + intervals.add(new Integer[] {startTime, endTime}); + } else { + intervals.add(new Integer[] {startTime, 2359}); + intervals.add(new Integer[] {0, endTime}); + } + return intervals; + } +} From b2a37248ed5c6953d24d77f00042689f68230629 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 14:02:34 +0900 Subject: [PATCH 25/31] =?UTF-8?q?feat=20:=20=EB=B2=84=EC=8A=A4=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=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 --- .../api/datagg/dto/BusLocationBodyDto.java | 4 +-- .../bus/service/BusLocationProcessor.java | 31 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/talkka/server/api/datagg/dto/BusLocationBodyDto.java b/server/src/main/java/com/talkka/server/api/datagg/dto/BusLocationBodyDto.java index a9f26d4c..3fdd897f 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/dto/BusLocationBodyDto.java +++ b/server/src/main/java/com/talkka/server/api/datagg/dto/BusLocationBodyDto.java @@ -21,12 +21,12 @@ public BusLocationEntity toEntity(int apiCallNo, LocalDateTime createdAt) { return BusLocationEntity.builder() .apiRouteId(routeId) .apiStationId(stationId) - .stationSeq(stationSeq.shortValue()) + .stationSeq(stationSeq) .endBus(endBusEnum) .lowPlate(lowPlateEnum) .plateNo(plateNo) .plateType(plateTypeEnum) - .remainSeatCount(remainSeatCnt.shortValue()) + .remainSeatCount(remainSeatCnt) .apiCallNo(apiCallNo) .createdAt(createdAt) .build(); diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java b/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java index dfd2c8dc..d1793321 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusLocationProcessor.java @@ -47,7 +47,11 @@ public class BusLocationProcessor { @Transactional public void start(List locations) { + init(); for (var location : locations) { + if (location.getRemainSeatCount() == -1) { + continue; + } try { if (locationMap.containsKey(location.getPlateNo())) { var dq = locationMap.get(location.getPlateNo()); @@ -101,14 +105,14 @@ private void process(Deque locations) { String plateNo = locations.peek().getPlateNo(); LocalDateTime createdAt = locations.peek().getCreatedAt(); List result = new ArrayList<>(); - // 노선의 길이 확인 - int num = getRouteLength(apiRouteId); - // 노선 손실률이 80% 이상이면 저장하지 않음 - if (locations.size() < num * 0.2) { - return; - } - var routeStationList = getRouteStationByRouteId(apiRouteId); try { + // 노선의 길이 확인 + var routeStationList = getRouteStationByRouteId(apiRouteId); + int num = routeStationList.size(); + // 노선 손실률이 80% 이상이면 저장하지 않음 + if (locations.size() < num * 0.2) { + return; + } if (locations.peek().getStationSeq() != 1) { result.add(makeSeatEntity(Optional.empty(), Optional.of(locations.peek()), 1, routeStationList.get(0).getStation())); @@ -138,6 +142,9 @@ private void process(Deque locations) { log.warn("버스 위치정보 가공 실패 : 노선({}), 정거장({}), 버스이름({}), 생성일({})", apiRouteId, apiStationId, plateNo, createdAt); } + if (result.isEmpty()) { + return; + } BusPlateStatisticEntity routeInfo = BusPlateStatisticEntity.builder() .route(result.get(0).getRoute()) .plateNo(result.get(0).getPlateNo()) @@ -276,6 +283,15 @@ private Integer getRouteLength(String apiRouteId) throws InvalidLocationNotFound return routeLengthMap.get(apiRouteId); } + private void init() { + for (var stationEntity : busStationRepository.findAll()) { + stationMap.put(stationEntity.getApiStationId(), Optional.of(stationEntity)); + } + for (var routeEntity : busRouteRepository.findAll()) { + routeMap.put(routeEntity.getApiRouteId(), Optional.of(routeEntity)); + } + } + /** * 주어진 두 시간 {@code time1}과 {@code time2} 사이의 구간을 주어진 개수 {@code num}만큼 나누어 * 그중 첫 번째 구간의 시작 시간을 반환합니다. @@ -286,6 +302,7 @@ private Integer getRouteLength(String apiRouteId) throws InvalidLocationNotFound * @return 첫 번째 구간의 끝 시간에 해당하는 {@code LocalDateTime} 객체를 반환합니다. */ private LocalDateTime getBetweenTime(LocalDateTime time1, LocalDateTime time2, int num) { + num = num == 0 ? 1 : num; Duration duration = Duration.between(time1, time2); Duration halfDuration = duration.dividedBy(num); return time1.plus(halfDuration); From 2cc5b95ce8ffafd838702114e39e5e4a4fa26e3d Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:03:57 +0900 Subject: [PATCH 26/31] =?UTF-8?q?refactor=20:=20TimeSlot=20=EB=A1=A4?= =?UTF-8?q?=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talkka/server/common/enums/TimeSlot.java | 131 +++++++----------- 1 file changed, 51 insertions(+), 80 deletions(-) diff --git a/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java b/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java index 6e0144ef..d5c9abb0 100644 --- a/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java +++ b/server/src/main/java/com/talkka/server/common/enums/TimeSlot.java @@ -1,73 +1,58 @@ package com.talkka.server.common.enums; -import java.time.LocalDateTime; - import com.talkka.server.common.exception.enums.InvalidTimeSlotEnumException; -import com.talkka.server.common.util.EnumCodeInterface; - -public enum TimeSlot implements EnumCodeInterface { - T_00_00(0), - T_00_30(1), - T_01_00(2), - T_01_30(3), - T_02_00(4), - T_02_30(5), - T_03_00(6), - T_03_30(7), - T_04_00(8), - T_04_30(9), - T_05_00(10), - T_05_30(11), - T_06_00(12), - T_06_30(13), - T_07_00(14), - T_07_30(15), - T_08_00(16), - T_08_30(17), - T_09_00(18), - T_09_30(19), - T_10_00(20), - T_10_30(21), - T_11_00(22), - T_11_30(23), - T_12_00(24), - T_12_30(25), - T_13_00(26), - T_13_30(27), - T_14_00(28), - T_14_30(29), - T_15_00(30), - T_15_30(31), - T_16_00(32), - T_16_30(33), - T_17_00(34), - T_17_30(35), - T_18_00(36), - T_18_30(37), - T_19_00(38), - T_19_30(39), - T_20_00(40), - T_20_30(41), - T_21_00(42), - T_21_30(43), - T_22_00(44), - T_22_30(45), - T_23_00(46), - T_23_30(47); - - private final int code; - TimeSlot(int code) { - this.code = code; - } - - @Override - public String getCode() { - return String.valueOf(this.code); - } +public enum TimeSlot { + T_00_00, + T_00_30, + T_01_00, + T_01_30, + T_02_00, + T_02_30, + T_03_00, + T_03_30, + T_04_00, + T_04_30, + T_05_00, + T_05_30, + T_06_00, + T_06_30, + T_07_00, + T_07_30, + T_08_00, + T_08_30, + T_09_00, + T_09_30, + T_10_00, + T_10_30, + T_11_00, + T_11_30, + T_12_00, + T_12_30, + T_13_00, + T_13_30, + T_14_00, + T_14_30, + T_15_00, + T_15_30, + T_16_00, + T_16_30, + T_17_00, + T_17_30, + T_18_00, + T_18_30, + T_19_00, + T_19_30, + T_20_00, + T_20_30, + T_21_00, + T_21_30, + T_22_00, + T_22_30, + T_23_00, + T_23_30; - public int getCodeValue() { - return this.code; + TimeSlot() { } public static TimeSlot valueOfEnumString(String enumValue) { @@ -77,18 +62,4 @@ public static TimeSlot valueOfEnumString(String enumValue) { throw new InvalidTimeSlotEnumException(); } } - - public static TimeSlot fromCode(int code) { - for (TimeSlot timeSlot : TimeSlot.values()) { - if (timeSlot.code == code) { - return timeSlot; - } - } - throw new InvalidTimeSlotEnumException(); - } - - public static TimeSlot fromLocalDateTime(LocalDateTime time) { - int code = 2 * time.getHour() + (time.getMinute() < 30 ? 0 : 1); - return TimeSlot.fromCode(code); - } -} +} \ No newline at end of file From d7fead6fde8008e66a0ee06e952b56a1db70afee Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:04:19 +0900 Subject: [PATCH 27/31] =?UTF-8?q?refactor=20:=20TimeSlot=20=EB=A1=A4?= =?UTF-8?q?=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/common/util/TimeSlotTypeConverter.java | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java diff --git a/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java b/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java deleted file mode 100644 index 2abbe248..00000000 --- a/server/src/main/java/com/talkka/server/common/util/TimeSlotTypeConverter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.talkka.server.common.util; - -import com.talkka.server.common.enums.TimeSlot; - -import jakarta.persistence.Converter; - -@Converter(autoApply = true) -public class TimeSlotTypeConverter extends EnumCodeConverter { - public TimeSlotTypeConverter() { - super(TimeSlot.class); - } -} From 8d7fa401bf128acc5645302ffa573f3456969675 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:04:45 +0900 Subject: [PATCH 28/31] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20Serializable=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/talkka/server/bus/dto/BusRemainSeatDto.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java index 7ee03c4c..ab845e69 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java @@ -1,6 +1,5 @@ package com.talkka.server.bus.dto; -import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -15,7 +14,7 @@ public class BusRemainSeatDto { @Getter @AllArgsConstructor - private static class SeatInfo implements Serializable { + private static class SeatInfo { LocalDateTime arrivedTime; Integer remainSeat; } @@ -23,11 +22,9 @@ private static class SeatInfo implements Serializable { private PlateType plateType; private String plateNo; private LocalDateTime standardTime; - private List remainSeatList; + private final List remainSeatList = new ArrayList<>(); public BusRemainSeatDto(List seats) { - remainSeatList = new ArrayList<>(); - for (var seat : seats) { plateType = seat.getPlateType(); plateNo = seat.getPlateNo(); From 0eaf961c1f45ab4deb02a2ab7cce364a94464d6d Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:11:05 +0900 Subject: [PATCH 29/31] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/talkka/server/bus/controller/BusViewController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java b/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java index 1b64be2e..b53e8ffb 100644 --- a/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java +++ b/server/src/main/java/com/talkka/server/bus/controller/BusViewController.java @@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor; +// 테스트용 컨트롤러입니다. @RestController @RequiredArgsConstructor @RequestMapping("/api/bus/route-info") From 3513910b6b465d8198966b67895294cbef9ceb97 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:19:53 +0900 Subject: [PATCH 30/31] =?UTF-8?q?refactor=20:=20standardTime=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EC=BD=94=EB=93=9C=20=EB=AA=85=ED=99=95=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/talkka/server/bus/dto/BusRemainSeatDto.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java index ab845e69..cd563ea8 100644 --- a/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java +++ b/server/src/main/java/com/talkka/server/bus/dto/BusRemainSeatDto.java @@ -24,14 +24,17 @@ private static class SeatInfo { private LocalDateTime standardTime; private final List remainSeatList = new ArrayList<>(); - public BusRemainSeatDto(List seats) { - for (var seat : seats) { + public BusRemainSeatDto(List seatList) { + for (var seat : seatList) { plateType = seat.getPlateType(); plateNo = seat.getPlateNo(); remainSeatList.add(new SeatInfo(seat.getCreatedAt(), seat.getEmptySeat())); } - if (!seats.isEmpty()) { - standardTime = seats.get(seats.size() >> 1).getCreatedAt(); + if (!seatList.isEmpty()) { + // 기준 정거장을 기준으로 양 옆으로 조회하기 때문에 + // 기준 정거장은 배열의 정 가운데에 위치함 + int middleIdx = seatList.size() / 2; + standardTime = seatList.get(middleIdx).getCreatedAt(); } } } From 5dd52e13202583bee9e6c16534afa574c77c2c14 Mon Sep 17 00:00:00 2001 From: PSH Date: Wed, 28 Aug 2024 15:28:26 +0900 Subject: [PATCH 31/31] =?UTF-8?q?refactor=20:=20=EB=B3=80=EC=88=98?= =?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 --- .../talkka/server/bus/dao/BusPlateStatisticEntity.java | 2 +- .../com/talkka/server/bus/dao/BusRemainSeatEntity.java | 8 ++++---- .../talkka/server/bus/dao/BusRemainSeatRepository.java | 7 +------ .../com/talkka/server/bus/service/BusViewService.java | 4 ++-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java index 61c118c2..e5af9bb7 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusPlateStatisticEntity.java @@ -51,6 +51,6 @@ public class BusPlateStatisticEntity { @Column(name = "end_time", nullable = false) private Integer endTime; - @OneToMany(mappedBy = "routeInfo", cascade = CascadeType.PERSIST) + @OneToMany(mappedBy = "plateStatistic", cascade = CascadeType.PERSIST) private List seats; } diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java index fe4cb1cc..35ed995c 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatEntity.java @@ -65,8 +65,8 @@ public class BusRemainSeatEntity { private Integer time; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "route_info_id") - private BusPlateStatisticEntity routeInfo; + @JoinColumn(name = "plate_statistic_id") + private BusPlateStatisticEntity plateStatistic; // Builder Class public static class BusRemainSeatEntityBuilder { @@ -82,8 +82,8 @@ public static BusRemainSeatEntityBuilder build() { return defaultBuilder(); } - public void updateRouteInfo(BusPlateStatisticEntity routeInfo) { - this.routeInfo = routeInfo; + public void updateRouteInfo(BusPlateStatisticEntity plateStatistic) { + this.plateStatistic = plateStatistic; } /** diff --git a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java index 100c4f3c..9ac5ad87 100644 --- a/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java +++ b/server/src/main/java/com/talkka/server/bus/dao/BusRemainSeatRepository.java @@ -4,16 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; -import com.talkka.server.bus.enums.PlateType; - public interface BusRemainSeatRepository extends JpaRepository { List findByRouteIdAndStationIdAndEpochDayAndTimeBetween(Long routeId, Long stationId, Long epochDay, Integer startTime, Integer endTime); - List findByRouteInfoAndPlateTypeAndStationSeqBetweenOrderByStationSeq( + List findByPlateStatisticAndStationSeqBetweenOrderByStationSeq( BusPlateStatisticEntity routeInfo, - PlateType plateType, Integer startSeq, Integer endSeq); - - List findByRouteInfoAndStationSeqBetweenOrderByStationSeq(BusPlateStatisticEntity routeInfo, Integer startSeq, Integer endSeq); } diff --git a/server/src/main/java/com/talkka/server/bus/service/BusViewService.java b/server/src/main/java/com/talkka/server/bus/service/BusViewService.java index 739f68d0..0641f195 100644 --- a/server/src/main/java/com/talkka/server/bus/service/BusViewService.java +++ b/server/src/main/java/com/talkka/server/bus/service/BusViewService.java @@ -75,8 +75,8 @@ public BusViewDto getBusView( for (var seat : busSeats) { // 타겟 정거장 전후 도착정보 data.add( - busRemainSeatRepository.findByRouteInfoAndStationSeqBetweenOrderByStationSeq( - seat.getRouteInfo(), + busRemainSeatRepository.findByPlateStatisticAndStationSeqBetweenOrderByStationSeq( + seat.getPlateStatistic(), seat.getStationSeq() - stationNum, seat.getStationSeq() + stationNum )