diff --git a/backend/src/docs/asciidoc/station.adoc b/backend/src/docs/asciidoc/station.adoc index 097a3d45f..3c4379a17 100644 --- a/backend/src/docs/asciidoc/station.adoc +++ b/backend/src/docs/asciidoc/station.adoc @@ -38,3 +38,14 @@ include::{snippets}/charger-station-controller-test/findChargeStationByInvalidId include::{snippets}/charger-station-controller-test/findChargeStationByInvalidId/http-response.adoc[] include::{snippets}/charger-station-controller-test/findChargeStationByInvalidId/response-fields.adoc[] + +== 충전소의 id 값을 기준으로 충전소의 혼잡도를 조회한다 (/stations/{stationId}/statistics) + +=== Request + +include::{snippets}/congestion-controller-test/statistics/http-request.adoc[] + +=== Response + +include::{snippets}/congestion-controller-test/statistics/http-response.adoc[] +include::{snippets}/congestion-controller-test/statistics/response-fields.adoc[] diff --git a/backend/src/main/java/com/carffeine/carffeine/config/RestTemplateConfig.java b/backend/src/main/java/com/carffeine/carffeine/config/RestTemplateConfig.java index 676c62ef8..9e78af6ff 100644 --- a/backend/src/main/java/com/carffeine/carffeine/config/RestTemplateConfig.java +++ b/backend/src/main/java/com/carffeine/carffeine/config/RestTemplateConfig.java @@ -3,12 +3,17 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { - return new RestTemplate(); + DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory(); + defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(defaultUriBuilderFactory); + return restTemplate; } } diff --git a/backend/src/main/java/com/carffeine/carffeine/config/SchedulingConfig.java b/backend/src/main/java/com/carffeine/carffeine/config/SchedulingConfig.java new file mode 100644 index 000000000..5a39301c6 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/config/SchedulingConfig.java @@ -0,0 +1,9 @@ +package com.carffeine.carffeine.config; + +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; + +@EnableScheduling +@Component +public class SchedulingConfig { +} diff --git a/backend/src/main/java/com/carffeine/carffeine/controller/chargerStation/dto/CongestionResponse.java b/backend/src/main/java/com/carffeine/carffeine/controller/chargerStation/dto/CongestionResponse.java index f63a1b319..3116454e3 100644 --- a/backend/src/main/java/com/carffeine/carffeine/controller/chargerStation/dto/CongestionResponse.java +++ b/backend/src/main/java/com/carffeine/carffeine/controller/chargerStation/dto/CongestionResponse.java @@ -3,6 +3,6 @@ import java.util.List; import java.util.Map; -public record CongestionResponse(Map> STANDARD, - Map> QUICK) { +public record CongestionResponse(Map> standard, + Map> quick) { } diff --git a/backend/src/main/java/com/carffeine/carffeine/controller/congestion/CongestionController.java b/backend/src/main/java/com/carffeine/carffeine/controller/congestion/CongestionController.java new file mode 100644 index 000000000..a728de7bd --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/controller/congestion/CongestionController.java @@ -0,0 +1,23 @@ +package com.carffeine.carffeine.controller.congestion; + +import com.carffeine.carffeine.controller.chargerStation.dto.StatisticsResponse; +import com.carffeine.carffeine.service.chargerstation.CongestionService; +import com.carffeine.carffeine.service.chargerstation.dto.StatisticsRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +public class CongestionController { + + private final CongestionService congestionService; + + @GetMapping("/api/stations/{stationId}/statistics") + public ResponseEntity showCongestionStatistics(@PathVariable String stationId) { + StatisticsResponse statisticsResponse = congestionService.calculateCongestion(new StatisticsRequest(stationId)); + return ResponseEntity.ok(statisticsResponse); + } +} diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/ChargeStation.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/ChargeStation.java index b2404dfe3..09260bc17 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/ChargeStation.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/ChargeStation.java @@ -4,6 +4,7 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -15,13 +16,13 @@ import javax.persistence.Table; import java.util.ArrayList; import java.util.List; -import java.util.Objects; @Getter @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity +@EqualsAndHashCode(of = "stationId") @Table(name = "charge_station") public class ChargeStation { @@ -63,21 +64,4 @@ public int getAvailableCount() { .filter(Charger::isAvailable) .count(); } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ChargeStation that = (ChargeStation) o; - return Objects.equals(stationId, that.stationId); - } - - @Override - public int hashCode() { - return Objects.hash(stationId); - } } diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/Charger.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/Charger.java index 40f18e71a..e192ad41f 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/Charger.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/Charger.java @@ -28,6 +28,7 @@ @Entity @Table(name = "charger") public class Charger { + private static final BigDecimal OUTPUT_THRESHOLD = BigDecimal.valueOf(50); @Id @Column(name = "station_id") @@ -60,6 +61,16 @@ public class Charger { private ChargeStation chargeStation; public boolean isAvailable() { + if (chargerStatus == null) { + return false; + } return chargerStatus.isAvailable(); } + + public boolean isQuick() { + if (capacity == null) { + return false; + } + return capacity.compareTo(OUTPUT_THRESHOLD) >= 0; + } } diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerRepository.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerRepository.java index 37f7a9cbe..2b234f741 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerRepository.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerRepository.java @@ -5,5 +5,6 @@ import java.util.List; public interface ChargerRepository extends Repository { + List findAllByStationId(String stationId); } diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerState.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerState.java index 4aaf3d960..c6cfbbb08 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerState.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerState.java @@ -24,6 +24,13 @@ public static ChargerState from(int input) { .orElse(STATUS_UNKNOWN); } + public static ChargerState from(String input) { + return Arrays.stream(ChargerState.values()) + .filter(it -> it.value == input.charAt(0) - '0') + .findAny() + .orElse(STATUS_UNKNOWN); + } + public boolean isStandBy() { return this == STANDBY; } diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatus.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatus.java index 7c8476a8e..f7f3de9c7 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatus.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatus.java @@ -5,19 +5,25 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.IdClass; +import javax.persistence.Table; import java.time.LocalDateTime; +@ToString @Getter @Builder -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @IdClass(ChargerId.class) @Entity +@Table(name = "charger_status") public class ChargerStatus { @Id @@ -30,6 +36,7 @@ public class ChargerStatus { private LocalDateTime latestUpdateTime; + @Enumerated(EnumType.STRING) private ChargerState chargerState; public boolean isAvailable() { diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatusRepository.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatusRepository.java new file mode 100644 index 000000000..9f6cab500 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/charger/ChargerStatusRepository.java @@ -0,0 +1,10 @@ +package com.carffeine.carffeine.domain.chargestation.charger; + +import org.springframework.data.repository.Repository; + +import java.util.List; + +public interface ChargerStatusRepository extends Repository { + + List findAll(); +} diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/IdGenerator.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/IdGenerator.java index b15239e71..e85656ac7 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/IdGenerator.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/IdGenerator.java @@ -1,7 +1,5 @@ package com.carffeine.carffeine.domain.chargestation.congestion; -import com.carffeine.carffeine.domain.chargestation.charger.Charger; - import java.time.DayOfWeek; public class IdGenerator { @@ -9,10 +7,6 @@ public class IdGenerator { private IdGenerator() { } - public static String generateIdWithCharger(DayOfWeek dayOfWeek, RequestPeriod startTime, Charger charger) { - return generateId(dayOfWeek, startTime, charger.getStationId(), charger.getChargerId()); - } - public static String generateId(DayOfWeek dayOfWeek, RequestPeriod startTime, String stationId, String chargerId) { return dayOfWeek.getValue() * 10000L + startTime.getSection() + stationId + chargerId; } diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestion.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestion.java index 75e53e33c..c8777e786 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestion.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestion.java @@ -7,7 +7,9 @@ import lombok.NoArgsConstructor; import javax.persistence.Column; +import javax.persistence.ConstraintMode; import javax.persistence.Entity; +import javax.persistence.ForeignKey; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; @@ -21,6 +23,7 @@ @Entity @Table(name = "periodic_congestion") public class PeriodicCongestion { + @Id private String id; private DayOfWeek dayOfWeek; @@ -30,10 +33,10 @@ public class PeriodicCongestion { private double congestion; @ManyToOne - @JoinColumns({ + @JoinColumns(value = { @JoinColumn(name = "station_id", referencedColumnName = "station_id", insertable = false, updatable = false), @JoinColumn(name = "charger_id", referencedColumnName = "charger_id", insertable = false, updatable = false) - }) + }, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) private Charger charger; @Column(name = "station_id") @@ -53,15 +56,6 @@ public PeriodicCongestion(String id, DayOfWeek dayOfWeek, RequestPeriod startTim this.chargerId = chargerId; } - public PeriodicCongestion(Charger charger, DayOfWeek dayOfWeek, RequestPeriod startTime, int useCount, int totalCount, double congestion) { - id = IdGenerator.generateIdWithCharger(dayOfWeek, startTime, charger); - this.dayOfWeek = dayOfWeek; - this.startTime = startTime; - this.useCount = useCount; - this.totalCount = totalCount; - this.congestion = congestion; - } - public static PeriodicCongestion of(DayOfWeek dayOfWeek, RequestPeriod startTime, int useCount, int totalCount, String stationId, String chargerId) { String id = IdGenerator.generateId(dayOfWeek, startTime, stationId, chargerId); double congestion = (double) useCount / totalCount; diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestionRepository.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestionRepository.java index 1431abf77..4457d97e7 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestionRepository.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/PeriodicCongestionRepository.java @@ -6,6 +6,7 @@ import java.util.List; public interface PeriodicCongestionRepository extends Repository { + List findAllByDayOfWeekAndStartTime(DayOfWeek dayOfWeek, RequestPeriod startTime); PeriodicCongestion save(PeriodicCongestion periodicCongestion); diff --git a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/RequestPeriod.java b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/RequestPeriod.java index b5fa35b4c..b6fa5208c 100644 --- a/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/RequestPeriod.java +++ b/backend/src/main/java/com/carffeine/carffeine/domain/chargestation/congestion/RequestPeriod.java @@ -8,12 +8,15 @@ @Getter public enum RequestPeriod { + ZERO(0), TWELVE(1200); + private static final int UNIT = 100; private static final List periods = Arrays.stream(values()) .sorted(Comparator.comparingInt(RequestPeriod::getSection)) .toList(); + private final int section; RequestPeriod(int section) { @@ -22,7 +25,7 @@ public enum RequestPeriod { public static RequestPeriod from(int hour) { return periods.stream() - .filter(it -> it.section <= hour * 100) + .filter(it -> it.section <= hour * UNIT) .findFirst() .orElseThrow(); } diff --git a/backend/src/main/java/com/carffeine/carffeine/infra/api/RestTemplateChargeStationRequester.java b/backend/src/main/java/com/carffeine/carffeine/infra/api/RestTemplateChargeStationRequester.java index afc25a3d3..da77fc2a1 100644 --- a/backend/src/main/java/com/carffeine/carffeine/infra/api/RestTemplateChargeStationRequester.java +++ b/backend/src/main/java/com/carffeine/carffeine/infra/api/RestTemplateChargeStationRequester.java @@ -1,7 +1,9 @@ package com.carffeine.carffeine.infra.api; import com.carffeine.carffeine.service.chargerstation.ChargeStationRequester; +import com.carffeine.carffeine.service.chargerstation.ChargerStateRequester; import com.carffeine.carffeine.service.chargerstation.dto.ChargeStationRequest; +import com.carffeine.carffeine.service.chargerstation.dto.ChargerStateUpdateRequest; import com.carffeine.carffeine.service.chargerstation.dto.RandomKeySelector; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,12 +19,14 @@ @RequiredArgsConstructor @Slf4j @Component -public class RestTemplateChargeStationRequester implements ChargeStationRequester { +public class RestTemplateChargeStationRequester implements ChargeStationRequester, ChargerStateRequester { private static final String REQUEST_URL = "/getChargerInfo"; + private static final String REQUEST_STATE_URL = "/getChargerStatus"; private static final int ROW_SIZE = 9999; private static final String DATA_TYPE = "JSON"; private static final int ONE_SECOND = 1000; + private static final int PERIOD = 10; private final RestTemplate restTemplate; private final RandomKeySelector randomKeySelector; @@ -30,13 +34,30 @@ public class RestTemplateChargeStationRequester implements ChargeStationRequeste @Override public ChargeStationRequest requestChargeStationRequest(int pageNo) { while (true) { - ChargeStationRequest result = requestChargeStationRequestWithRetry(pageNo); - if (result != null) { - return result; + try { + ChargeStationRequest result = requestChargeStationRequestWithRetry(pageNo); + if (result != null) { + return result; + } + } catch (Exception e) { + log.warn("페이지 요청 실패, 재시도합니다. pageNo: {}", pageNo); + waitForRetry(); } + } + } - log.warn("페이지 요청 실패, 재시도합니다. pageNo: {}", pageNo); - waitForRetry(); + @Override + public ChargerStateUpdateRequest requestChargerStatusUpdate(int pageNo) { + while (true) { + try { + ChargerStateUpdateRequest result = requestChargeStateUpdateRequestWithRetry(pageNo); + if (result != null) { + return result; + } + } catch (Exception e) { + log.warn("페이지 요청 실패, 재시도합니다. pageNo: {}", pageNo); + waitForRetry(); + } } } @@ -49,14 +70,38 @@ private ChargeStationRequest requestChargeStationRequestWithRetry(int pageNo) { return exchange.getBody(); } + private ChargerStateUpdateRequest requestChargeStateUpdateRequestWithRetry(int pageNo) { + URI uri = requestStateWithDecodedKey(pageNo); + ResponseEntity exchange = restTemplate.exchange(new RequestEntity<>(null, HttpMethod.GET, uri), ChargerStateUpdateRequest.class); + if (exchange.getHeaders().getContentType().getSubtype().equals("xml")) { + return null; + } + return exchange.getBody(); + } + private URI requestWithDecodedKey(int pageNo) { String serviceKey = randomKeySelector.generateRandomKey(); - return UriComponentsBuilder.fromUriString("https://apis.d584/EvCharger") + return UriComponentsBuilder.fromUriString("https://apis.data.go.kr/B552584/EvCharger") .path(REQUEST_URL) .queryParam("serviceKey", serviceKey) .queryParam("pageNo", pageNo) .queryParam("numOfRows", ROW_SIZE) .queryParam("dataType", DATA_TYPE) + .encode() + .build() + .toUri(); + } + + private URI requestStateWithDecodedKey(int pageNo) { + String serviceKey = randomKeySelector.generateRandomKey(); + return UriComponentsBuilder.fromUriString("https://apis.data.go.kr/B552584/EvCharger") + .path(REQUEST_STATE_URL) + .queryParam("serviceKey", serviceKey) + .queryParam("pageNo", pageNo) + .queryParam("numOfRows", ROW_SIZE) + .queryParam("dataType", DATA_TYPE) + .queryParam("period", PERIOD) + .encode() .build() .toUri(); } diff --git a/backend/src/main/java/com/carffeine/carffeine/infra/repository/ChargerStatusCustomRepositoryImpl.java b/backend/src/main/java/com/carffeine/carffeine/infra/repository/ChargerStatusCustomRepositoryImpl.java new file mode 100644 index 000000000..f7fa67611 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/infra/repository/ChargerStatusCustomRepositoryImpl.java @@ -0,0 +1,39 @@ +package com.carffeine.carffeine.infra.repository; + +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; +import com.carffeine.carffeine.service.chargerstation.ChargerStatusCustomRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Repository +public class ChargerStatusCustomRepositoryImpl implements ChargerStatusCustomRepository { + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + @Override + @Transactional + public void saveAll(List chargerStatuses) { + String sql = "INSERT INTO charger_status (station_id, charger_id, latest_update_time, charger_state) " + + "VALUES (:stationId, :chargerId, :latestUpdateTime, :chargerState) " + + "ON DUPLICATE KEY UPDATE latest_update_time = :latestUpdateTime, charger_state = :chargerState"; + SqlParameterSource[] sqlParameterSources = chargerStatuses.stream() + .map(this::changeToSqlParameterSource) + .toArray(SqlParameterSource[]::new); + namedParameterJdbcTemplate.batchUpdate(sql, sqlParameterSources); + } + + private MapSqlParameterSource changeToSqlParameterSource(ChargerStatus item) { + return new MapSqlParameterSource() + .addValue("stationId", item.getStationId()) + .addValue("chargerId", item.getChargerId()) + .addValue("latestUpdateTime", item.getLatestUpdateTime()) + .addValue("chargerState", item.getChargerState().name()); + } +} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateRequester.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateRequester.java new file mode 100644 index 000000000..ce523a2b4 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateRequester.java @@ -0,0 +1,8 @@ +package com.carffeine.carffeine.service.chargerstation; + +import com.carffeine.carffeine.service.chargerstation.dto.ChargerStateUpdateRequest; + +public interface ChargerStateRequester { + + ChargerStateUpdateRequest requestChargerStatusUpdate(int pageNo); +} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateUpdateService.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateUpdateService.java new file mode 100644 index 000000000..b5f2306f5 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStateUpdateService.java @@ -0,0 +1,36 @@ +package com.carffeine.carffeine.service.chargerstation; + +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; +import com.carffeine.carffeine.service.chargerstation.dto.ChargerStateRequest; +import com.carffeine.carffeine.service.chargerstation.dto.ChargerStateUpdateRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.List; + +@RequiredArgsConstructor +@Service +public class ChargerStateUpdateService { + + private static final int MAX_PAGE_SIZE = 4; + + private final ChargerStateRequester chargerStateRequester; + private final ChargerStatusCustomRepository chargerStatusCustomRepository; + + @Scheduled(cron = "0 0/7 * * * *") + public void update() { + for (int page = 1; page <= MAX_PAGE_SIZE; page++) { + updateChargerState(page); + } + } + + private void updateChargerState(int pageNo) { + ChargerStateUpdateRequest chargerStateUpdateRequest = chargerStateRequester.requestChargerStatusUpdate(pageNo); + List chargerStateRequests = chargerStateUpdateRequest.items().item(); + List chargerStatuses = chargerStateRequests.stream() + .map(ChargerStateRequest::toChargerStatus) + .toList(); + chargerStatusCustomRepository.saveAll(chargerStatuses); + } +} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStationService.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStationService.java index cb657a5b5..06bc372cc 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStationService.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStationService.java @@ -4,24 +4,22 @@ import com.carffeine.carffeine.domain.chargestation.ChargeStationRepository; import com.carffeine.carffeine.domain.chargestation.Latitude; import com.carffeine.carffeine.domain.chargestation.Longitude; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerState; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatusRepository; import com.carffeine.carffeine.domain.chargestation.congestion.IdGenerator; import com.carffeine.carffeine.domain.chargestation.congestion.PeriodicCongestion; import com.carffeine.carffeine.domain.chargestation.congestion.PeriodicCongestionRepository; import com.carffeine.carffeine.domain.chargestation.congestion.RequestPeriod; import com.carffeine.carffeine.domain.chargestation.exception.ChargeStationException; import com.carffeine.carffeine.domain.chargestation.exception.ChargeStationExceptionType; -import com.carffeine.carffeine.service.chargerstation.dto.ChargersForCongestionRequest; import com.carffeine.carffeine.service.chargerstation.dto.CoordinateRequest; -import com.carffeine.carffeine.service.chargerstation.dto.Item; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; import java.math.BigDecimal; -import java.net.URI; import java.time.DayOfWeek; import java.time.LocalDateTime; import java.util.HashMap; @@ -31,16 +29,9 @@ @RequiredArgsConstructor @Service public class ChargerStationService { - - private static final String REQUEST_URL = "/getChargerStatus"; - private static final int ROW_SIZE = 100; - private static final String DATA_TYPE = "JSON"; - private static final String USING = "3"; private final ChargeStationRepository chargeStationRepository; private final PeriodicCongestionRepository periodicCongestionRepository; - private final RestTemplate restTemplate; - @Value("${api.service_key}") - private String SERVICE_KEY; + private final ChargerStatusRepository chargerStatusRepository; @Transactional(readOnly = true) public List findByCoordinate(CoordinateRequest request) { @@ -63,20 +54,8 @@ public ChargeStation findStationById(String stationId) { .orElseThrow(() -> new ChargeStationException(ChargeStationExceptionType.NOT_FOUND_ID)); } + @Scheduled(cron = "* * 0/1 * * *") public void calculateCongestion() { - URI uri = UriComponentsBuilder.fromUriString("https://apis.data.go.kr/B552584/EvCharger") - .path(REQUEST_URL) - .queryParam("serviceKey", SERVICE_KEY) - .queryParam("pageNo", 1) - .queryParam("numOfRows", ROW_SIZE) - .queryParam("dataType", DATA_TYPE) - .build() - .toUri(); - - ChargersForCongestionRequest chargersForCongestionRequest = restTemplate.getForObject(uri, ChargersForCongestionRequest.class); - - List items = chargersForCongestionRequest.items().item(); - LocalDateTime now = LocalDateTime.now(); DayOfWeek dayOfWeek = now.getDayOfWeek(); @@ -89,23 +68,24 @@ public void calculateCongestion() { String id = congestion.getId(); map.put(id, congestion); } + List chargerStatuses = chargerStatusRepository.findAll(); - for (Item item : items) { - String expectedId = IdGenerator.generateId(dayOfWeek, period, item.statId(), item.chgerId()); + for (ChargerStatus chargerStatus : chargerStatuses) { + String expectedId = IdGenerator.generateId(dayOfWeek, period, chargerStatus.getStationId(), chargerStatus.getChargerId()); if (!map.containsKey(expectedId)) { - int useCount = updateCount(item, 0); - periodicCongestionRepository.save(PeriodicCongestion.of(dayOfWeek, period, useCount, 1, item.statId(), item.chgerId())); + int useCount = updateCount(chargerStatus, 0); + periodicCongestionRepository.save(PeriodicCongestion.of(dayOfWeek, period, useCount, 1, chargerStatus.getStationId(), chargerStatus.getChargerId())); continue; } - int useCount = updateCount(item, map.get(expectedId).getUseCount()); + int useCount = updateCount(chargerStatus, map.get(expectedId).getUseCount()); int totalCount = map.get(expectedId).getTotalCount() + 1; - periodicCongestionRepository.save(PeriodicCongestion.of(dayOfWeek, period, useCount, totalCount, item.statId(), item.chgerId())); + periodicCongestionRepository.save(PeriodicCongestion.of(dayOfWeek, period, useCount, totalCount, chargerStatus.getStationId(), chargerStatus.getChargerId())); } } - private int updateCount(Item item, int status) { - if (item.stat().equals(USING)) { + private int updateCount(ChargerStatus item, int status) { + if (item.getChargerState() == ChargerState.CHARGING_IN_PROGRESS) { return status + 1; } return status; diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStatusCustomRepository.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStatusCustomRepository.java new file mode 100644 index 000000000..a7abf5b86 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ChargerStatusCustomRepository.java @@ -0,0 +1,10 @@ +package com.carffeine.carffeine.service.chargerstation; + +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; + +import java.util.List; + +public interface ChargerStatusCustomRepository { + + void saveAll(List item); +} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/CongestionService.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/CongestionService.java index 322f714cc..74fd2aa3c 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/CongestionService.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/CongestionService.java @@ -23,7 +23,12 @@ @Service @Transactional(readOnly = true) public class CongestionService { + private static final int PERIOD_SIZE = 24; + private static final int OUTPUT_THRESHOLD = 50; + private static final int DEFAULT_VALUE = -1; + private static final int PERCENTAGE = 100; + private final PeriodicCongestionRepository periodicCongestionRepository; private final ChargerRepository chargerRepository; @@ -41,7 +46,7 @@ public StatisticsResponse calculateCongestion(StatisticsRequest statisticsReques private Map> calculateQuick(List congestions, List chargers) { List quickChargerIds = chargers.stream() - .filter(it -> it.getCapacity().intValue() >= 50) + .filter(Charger::isQuick) .map(Charger::getChargerId) .toList(); @@ -50,7 +55,7 @@ private Map> calculateQuick(List> calculateStandard(List congestions, List chargers) { List standardChargerIds = chargers.stream() - .filter(it -> it.getCapacity().intValue() < 50) + .filter(it -> !it.isQuick()) .map(Charger::getChargerId) .toList(); @@ -92,13 +97,13 @@ private double getTotalCongestion(List congestionsByCharger, .toList(); if (congestions.isEmpty()) { - return -1; + return DEFAULT_VALUE; } double totalCongestion = congestions.stream() .mapToDouble(Double::doubleValue) .sum(); - return totalCongestion / congestions.size() * 100; + return totalCongestion / congestions.size() * PERCENTAGE; } } diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ScrapperService.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ScrapperService.java index 0f24e8812..269fbc7be 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ScrapperService.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/ScrapperService.java @@ -3,6 +3,7 @@ import com.carffeine.carffeine.domain.chargestation.ChargeStation; import com.carffeine.carffeine.domain.chargestation.CustomChargeStationRepository; import com.carffeine.carffeine.domain.chargestation.charger.Charger; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; import com.carffeine.carffeine.domain.chargestation.charger.CustomChargerRepository; import com.carffeine.carffeine.service.chargerstation.dto.ChargeStationRequest; import lombok.RequiredArgsConstructor; @@ -26,6 +27,7 @@ public class ScrapperService { private final CustomChargeStationRepository customChargeStationRepository; private final ChargeStationRequester chargeStationRequester; private final CustomChargerRepository customChargerRepository; + private final ChargerStatusCustomRepository chargerStatusCustomRepository; public void scrap() { ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); @@ -41,11 +43,15 @@ private void scrapPage(int page) { ChargeStationRequest chargeStationRequest = chargeStationRequester.requestChargeStationRequest(page); List chargeStations = chargeStationRequest.toStations(); List chargers = chargeStationRequest.toChargers(); + List chargerStatuses = chargers.stream() + .map(Charger::getChargerStatus) + .toList(); if (page != MAX_PAGE_SIZE && chargers.size() < MIN_SIZE) { log.error("공공데이터 API 의 사이즈가 이상해요 page: {}, size: {}", page, chargers.size()); } customChargeStationRepository.saveAll(new HashSet<>(chargeStations)); customChargerRepository.saveAll(chargers); + chargerStatusCustomRepository.saveAll(chargerStatuses); log.info("page: {}, size: {} 저장 완료", page, chargers.size()); } catch (Exception e) { log.error("page: {}", page, e); diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateRequest.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateRequest.java new file mode 100644 index 000000000..8e5d46ef5 --- /dev/null +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateRequest.java @@ -0,0 +1,37 @@ +package com.carffeine.carffeine.service.chargerstation.dto; + +import com.carffeine.carffeine.domain.chargestation.charger.ChargerState; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public record ChargerStateRequest( + String busiId, + String statId, + String chgerId, + String stat, + String statUpdDt, + String lastTsdt, + String lastTedt, + String nowTsdt) { + + private static final int DATETIME_LENGTH = 14; + + public ChargerStatus toChargerStatus() { + return ChargerStatus.builder() + .stationId(statId) + .chargerId(chgerId) + .latestUpdateTime(parseDateTimeFromString(statUpdDt)) + .chargerState(ChargerState.from(stat)) + .build(); + } + + private LocalDateTime parseDateTimeFromString(String input) { + if (input == null || input.length() != DATETIME_LENGTH) { + return null; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + return LocalDateTime.parse(input, formatter); + } +} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersForCongestionRequest.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateUpdateRequest.java similarity index 67% rename from backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersForCongestionRequest.java rename to backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateUpdateRequest.java index aee8af057..73f570436 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersForCongestionRequest.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargerStateUpdateRequest.java @@ -1,8 +1,8 @@ package com.carffeine.carffeine.service.chargerstation.dto; -public record ChargersForCongestionRequest( - Items items, +public record ChargerStateUpdateRequest( int totalCount, + ChargersStateRequest items, int pageNo, String resultCode, int numOfRows diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Items.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersStateRequest.java similarity index 55% rename from backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Items.java rename to backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersStateRequest.java index 5cfb0b613..2e6ee93e3 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Items.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/ChargersStateRequest.java @@ -2,6 +2,5 @@ import java.util.List; -public record Items(List item) { - +public record ChargersStateRequest(List item) { } diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Item.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Item.java deleted file mode 100644 index 61e90232a..000000000 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/Item.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.carffeine.carffeine.service.chargerstation.dto; - -public record Item( - String busiId, - String statId, - String chgerId, - String stat, - String statUpdDt, - String lastTsdt, - String lastTedt, - String nowTsdt) { -} diff --git a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/RandomKeySelector.java b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/RandomKeySelector.java index 6c0810b3b..1ac333a1c 100644 --- a/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/RandomKeySelector.java +++ b/backend/src/main/java/com/carffeine/carffeine/service/chargerstation/dto/RandomKeySelector.java @@ -10,13 +10,13 @@ public class RandomKeySelector { private static final ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); - private static final int SERVICE_KEY_SIZE = 7; private static final int START_INDEX = 0; + @Value("${api.service_key}") private List serviceKeys; public String generateRandomKey() { - int randomNumber = threadLocalRandom.nextInt(START_INDEX, SERVICE_KEY_SIZE); + int randomNumber = threadLocalRandom.nextInt(START_INDEX, serviceKeys.size()); return serviceKeys.get(randomNumber); } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index b387662ba..6d3cbcba0 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -18,8 +18,6 @@ spring: show_sql: true format_sql: true use_sql_comments: true - flyway: - enabled: false profiles: active: local jasypt: @@ -29,12 +27,6 @@ jasypt: api: service_key: - ENC(9Gw2IrF1u/7Pt/rcC1Rwp7vbO7k34/al/B9/KRJ+cghR7xAivEN3fgfx+6nXtdFDCqPDi+Zj6tKc52diibRDXlb3XcaC9L7S3Ruafwnp6Em23om/VxqwbEMPRAt/VU++H/myjcB61RA=), - ENC(Koj9uZI7YtwNsnJecp/SDimwWJeK9n3zzbWPxlmwtprE2h05hStPQ+W4T344V4Y7SJbo5zpx7b0nHCH+7JOUgMJAk/ctr9F4ZUMD8894s9d1sI4cwIGht4XZDOIlWd8ZjUCk63l0C/4=), - ENC(3SYxfkd6fWdjkFuGbK/3+5ot1td2eqTngVAynzJj4o3/6CuRiW4mkLltHRynNLL3Tawlqqd39iQCuQEANWhpFpbvnpVfZeEzgOtY96xlJ9lzNu0Z8ONAUQIIKH+LN7tbUfGtSjfFUmE=), - ENC(lU0FBnShiEmEVKr3rQTW7a6BjLslR9/0Gv8Y596dNqGXVh7KJEQRlZViIzKo0fxX6KiMWyFuTdWrrH2D7ylp73JlDdVaCpUte9G1dUghSHFsb28z2B6bIAPM5uZj7MB4EdkblDbG+Mw=), - ENC(Sdiq67tsnKr3xlFROykuG6efvhNA4tqATObZZEeirXxF4VnwHbwfd1BNMRA1EXQnm2asRfz0qOmMV1xKczfIwqveIRlOZtBPx74cE0tqI23tir6PX2913V22W21C0CuWapkqUAWv/uM=), - ENC(/2YcNiK1XrrIM9ncuZBnIfUexobz4f+eRDUauI/VcoBzTbiY2VgVshA41nAQzzwrs7CWyKwJzkmf2DN6LCC82OJAg7TWOm4uCxtGcJ3mOsjlNiUYu8Mew2+2RhWjTBOceJLIUOBiXPo=), - ENC(qCPTBO4uzXuWoTJ4vxJ6vNkueRbpj/bee/CMo1mNt9RsjK8hobh6hZLTCWC2qa6dR0S/bDpTgTBV9+HXZ2lYPrOQmyFUrYAznn3iGn+n7fxX2O5rqAgirimQgyFxxem61hJmSa2zyxE=) + ENC(qKM+pC2sfTXqlFeYMn0Q/8b5QlCMQbO03PptUKkzDkUd1S3PD9aM2q1VL/ZA+vMlpJyLWcCBEZnPWkITbudrMTSorPAZ+hn+9LJN47wdq4JsX4BwUiUsjaokPIhNqJLZwWRm7wZf4/CUvnz2zoy60hTrArxO8tNgYnn+LeDX0t+sPGEmwT33N8xJZ8DyLVV927PwzvwIvdfrtK4aPY34fg8FEFW8deRfFEL4GVRsGsMf/Hc+5N359AAUR/mo712l+/J7QhCw5jJPgOl0T9MIhASk9A2UN3PftlQN4le6aErkQUpxd2/emXSYPK6OxdO+N97VTMJbcailHK5iC38k8uKrZNULv8YJ5vroaGdU4Moh9XfImxQCbVOGB4ysHAWewgYS+8sVgVAJr5VvP24/PBK1B7HMXVQggK8BuOHqgmdEDWsM8LIKIPdr49+99uKlvcY4jPWsIBoGst51NeBbY42LP/dEU0/U8uWeHnZBY3E=) initialize-charge: enabled: true diff --git a/backend/src/test/java/com/carffeine/carffeine/controller/congestion/CongestionControllerTest.java b/backend/src/test/java/com/carffeine/carffeine/controller/congestion/CongestionControllerTest.java new file mode 100644 index 000000000..eab02e228 --- /dev/null +++ b/backend/src/test/java/com/carffeine/carffeine/controller/congestion/CongestionControllerTest.java @@ -0,0 +1,135 @@ +package com.carffeine.carffeine.controller.congestion; + +import com.carffeine.carffeine.controller.chargerStation.dto.CongestionInfoResponse; +import com.carffeine.carffeine.controller.chargerStation.dto.CongestionResponse; +import com.carffeine.carffeine.controller.chargerStation.dto.StatisticsResponse; +import com.carffeine.carffeine.service.chargerstation.CongestionService; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.carffeine.carffeine.helper.RestDocsHelper.customDocument; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@WebMvcTest(CongestionController.class) +@AutoConfigureRestDocs +class CongestionControllerTest { + + @MockBean + private CongestionService congestionService; + + @Autowired + private MockMvc mockMvc; + + @Test + void 혼잡도를_계산하여_반환한다() throws Exception { + // given + String stationId = "1"; + + given(congestionService.calculateCongestion(any())) + .willReturn( + new StatisticsResponse( + "1", + getExpected()) + ); + + mockMvc.perform(get("/api/stations/{stationId}/statistics", stationId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.stationId").value("1")) + .andDo(customDocument("statistics", + pathParameters( + parameterWithName("stationId").description("충전소 ID") + ), + responseFields( + fieldWithPath("stationId").type(JsonFieldType.STRING).description("충전소 ID"), + fieldWithPath("congestion").type(JsonFieldType.OBJECT).description("혼잡도 정보"), + fieldWithPath("congestion.standard").type(JsonFieldType.OBJECT).description("표준 충전기 혼잡도 정보"), + fieldWithPath("congestion.standard.MON").type(JsonFieldType.ARRAY).description("월요일 혼잡도 정보"), + fieldWithPath("congestion.standard.MON[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.MON[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.TUE").type(JsonFieldType.ARRAY).description("화요일 혼잡도 정보"), + fieldWithPath("congestion.standard.TUE[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.TUE[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.WED").type(JsonFieldType.ARRAY).description("수요일 혼잡도 정보"), + fieldWithPath("congestion.standard.WED[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.WED[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.THU").type(JsonFieldType.ARRAY).description("목요일 혼잡도 정보"), + fieldWithPath("congestion.standard.THU[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.THU[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.FRI").type(JsonFieldType.ARRAY).description("금요일 혼잡도 정보"), + fieldWithPath("congestion.standard.FRI[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.FRI[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.SAT").type(JsonFieldType.ARRAY).description("토요일 혼잡도 정보"), + fieldWithPath("congestion.standard.SAT[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.SAT[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.standard.SUN").type(JsonFieldType.ARRAY).description("일요일 혼잡도 정보"), + fieldWithPath("congestion.standard.SUN[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.standard.SUN[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick").type(JsonFieldType.OBJECT).description("급속 충전기 혼잡도 정보"), + fieldWithPath("congestion.quick.MON").type(JsonFieldType.ARRAY).description("월요일 혼잡도 정보"), + fieldWithPath("congestion.quick.MON[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.MON[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.TUE").type(JsonFieldType.ARRAY).description("화요일 혼잡도 정보"), + fieldWithPath("congestion.quick.TUE[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.TUE[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.WED").type(JsonFieldType.ARRAY).description("수요일 혼잡도 정보"), + fieldWithPath("congestion.quick.WED[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.WED[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.THU").type(JsonFieldType.ARRAY).description("목요일 혼잡도 정보"), + fieldWithPath("congestion.quick.THU[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.THU[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.FRI").type(JsonFieldType.ARRAY).description("금요일 혼잡도 정보"), + fieldWithPath("congestion.quick.FRI[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.FRI[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.SAT").type(JsonFieldType.ARRAY).description("토요일 혼잡도 정보"), + fieldWithPath("congestion.quick.SAT[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.SAT[].ratio").type(JsonFieldType.NUMBER).description("혼잡도"), + fieldWithPath("congestion.quick.SUN").type(JsonFieldType.ARRAY).description("일요일 혼잡도 정보"), + fieldWithPath("congestion.quick.SUN[].hour").type(JsonFieldType.NUMBER).description("시간"), + fieldWithPath("congestion.quick.SUN[].ratio").type(JsonFieldType.NUMBER).description("혼잡도") + ))); + } + + private CongestionResponse getExpected() { + List dailyCongestion = new ArrayList<>(); + for (int i = 0; i < 24; i++) { + dailyCongestion.add(new CongestionInfoResponse(i, -1)); + } + return new CongestionResponse( + Map.of("MON", dailyCongestion, + "TUE", dailyCongestion, + "WED", dailyCongestion, + "THU", dailyCongestion, + "FRI", dailyCongestion, + "SAT", dailyCongestion, + "SUN", dailyCongestion), + Map.of("MON", dailyCongestion, + "TUE", dailyCongestion, + "WED", dailyCongestion, + "THU", dailyCongestion, + "FRI", dailyCongestion, + "SAT", dailyCongestion, + "SUN", dailyCongestion) + ); + } +} diff --git a/backend/src/test/java/com/carffeine/carffeine/infra/repository/ChargerStatusRepositoryImplTest.java b/backend/src/test/java/com/carffeine/carffeine/infra/repository/ChargerStatusRepositoryImplTest.java new file mode 100644 index 000000000..71d898ef9 --- /dev/null +++ b/backend/src/test/java/com/carffeine/carffeine/infra/repository/ChargerStatusRepositoryImplTest.java @@ -0,0 +1,48 @@ +package com.carffeine.carffeine.infra.repository; + +import com.carffeine.carffeine.domain.chargestation.charger.ChargerState; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatus; +import com.carffeine.carffeine.domain.chargestation.charger.ChargerStatusRepository; +import com.carffeine.carffeine.service.chargerstation.ChargerStatusCustomRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ChargerStatusRepositoryImplTest { + + private static final List chargerStatuses = List.of( + new ChargerStatus( + "stationId", + "chargerId", + LocalDateTime.of(2021, 1, 1, 0, 0, 0), + ChargerState.CHARGING_IN_PROGRESS + ) + ); + + private ChargerStatusCustomRepository chargerStatusCustomRepository; + private ChargerStatusRepository chargerStatusRepository; + + @Autowired + void setUp(NamedParameterJdbcTemplate namedParameterJdbcTemplate, ChargerStatusRepository chargerStatusRepository) { + chargerStatusCustomRepository = new ChargerStatusCustomRepositoryImpl(namedParameterJdbcTemplate); + this.chargerStatusRepository = chargerStatusRepository; + } + + @Test + void 상태를_저장한다() { + chargerStatusCustomRepository.saveAll(chargerStatuses); + + List result = chargerStatusRepository.findAll(); + + assertThat(result).hasSize(1); + } +} diff --git a/backend/src/test/java/com/carffeine/carffeine/service/chargerstation/CongestionServiceTest.java b/backend/src/test/java/com/carffeine/carffeine/service/chargerstation/CongestionServiceTest.java index 0b1b85290..288a7b6fd 100644 --- a/backend/src/test/java/com/carffeine/carffeine/service/chargerstation/CongestionServiceTest.java +++ b/backend/src/test/java/com/carffeine/carffeine/service/chargerstation/CongestionServiceTest.java @@ -6,7 +6,6 @@ import com.carffeine.carffeine.domain.chargestation.charger.ChargerRepository; import com.carffeine.carffeine.domain.chargestation.congestion.PeriodicCongestionRepository; import com.carffeine.carffeine.service.chargerstation.dto.StatisticsRequest; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Test; @@ -17,6 +16,8 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + @DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @DataJpaTest @@ -30,13 +31,13 @@ void setCongestionService(PeriodicCongestionRepository periodicCongestionReposit } @Test - void 혼잡도_계산() { + void 상태값이_없는_데이터일_경우_음수가_반환된다() { StatisticsRequest statisticsRequest = new StatisticsRequest("ME174003"); StatisticsResponse statisticsResponse = congestionService.calculateCongestion(statisticsRequest); CongestionResponse expected = getExpected(); - Assertions.assertThat(expected).usingRecursiveComparison() + assertThat(expected).usingRecursiveComparison() .isEqualTo(statisticsResponse.congestion()); }