From 083d954fac7da9f4962d510bcab7ec321bde2ce8 Mon Sep 17 00:00:00 2001 From: Photogrammer <81505228+JuneParkCode@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:24:28 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EB=B2=84=EC=8A=A4=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=8F=84=EC=B0=A9=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EB=B0=8F=20=EA=B5=AC=ED=98=84=20(#121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 작업 내용 - [x] DTO 구현 - [x] BusApiService 에 method 추가 - [x] SimpleBusApiService 에 구현 추가 - [x] CachedStorage interface 작성 - [x] CachedStorage 구현체 (memory based / time expire 가능) 구현 ## 캐시 사용시 변화 시간 - 1분 간격 캐싱 (동일 노선 정류장에 대한 조회시 1분에 한번 request 사용) - 1s -> 50ms ## NOTE - 작업양이 꽤 되니, commit 단위로 리뷰하시면 편합니다. Closes #120 --- .../api/datagg/dto/BusArrivalBodyDto.java | 38 +++++++++++++ .../api/datagg/dto/BusArrivalRespDto.java | 15 ++++++ .../api/datagg/service/BusApiService.java | 5 +- .../datagg/service/SimpleBusApiService.java | 26 +++++++-- .../server/bus/controller/BusLiveInfoApi.java | 28 ++++++++++ .../bus/controller/BusLiveInfoController.java | 34 ++++++++++++ .../server/bus/dto/BusArrivalRespDto.java | 46 ++++++++++++++++ .../server/bus/dto/BusLiveInfoRespDto.java | 24 +++++++++ .../server/bus/enums/RunningStatus.java | 13 +++++ .../GetBusLiveArrivalInfoFailedException.java | 9 ++++ .../server/bus/service/BusArrivalService.java | 12 +++++ .../bus/service/BusLiveInfoService.java | 12 +++++ .../bus/service/BusLiveInfoServiceImpl.java | 40 ++++++++++++++ .../bus/service/CachedBusArrivalService.java | 39 ++++++++++++++ .../talkka/server/bus/util/ArrivalCache.java | 13 +++++ .../server/common/util/CachedStorage.java | 11 ++++ .../common/util/MemoryCachedStorage.java | 53 +++++++++++++++++++ .../server/common/util/TimeExpiredValue.java | 15 ++++++ 18 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalBodyDto.java create mode 100644 server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalRespDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoApi.java create mode 100644 server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoController.java create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusArrivalRespDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/dto/BusLiveInfoRespDto.java create mode 100644 server/src/main/java/com/talkka/server/bus/enums/RunningStatus.java create mode 100644 server/src/main/java/com/talkka/server/bus/exception/GetBusLiveArrivalInfoFailedException.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusArrivalService.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusLiveInfoService.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/BusLiveInfoServiceImpl.java create mode 100644 server/src/main/java/com/talkka/server/bus/service/CachedBusArrivalService.java create mode 100644 server/src/main/java/com/talkka/server/bus/util/ArrivalCache.java create mode 100644 server/src/main/java/com/talkka/server/common/util/CachedStorage.java create mode 100644 server/src/main/java/com/talkka/server/common/util/MemoryCachedStorage.java create mode 100644 server/src/main/java/com/talkka/server/common/util/TimeExpiredValue.java diff --git a/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalBodyDto.java b/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalBodyDto.java new file mode 100644 index 00000000..a4592276 --- /dev/null +++ b/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalBodyDto.java @@ -0,0 +1,38 @@ +package com.talkka.server.api.datagg.dto; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import jakarta.validation.constraints.NotNull; + +@JacksonXmlRootElement(localName = "busArrivalList") +public record BusArrivalBodyDto( + @NotNull + Long stationId, // 정류소아이디 + @NotNull + Long routeId, // 노선아이디 + @NotNull + Integer locationNo1, // 첫번째차량 위치 정보 + @NotNull + Integer predictTime1, // 첫번째차량 도착예상시간 + @NotNull + Integer lowPlate1, // 첫번째차량 저상버스여부 + @NotNull + String plateNo1, // 첫번째차량 차량번호 + @NotNull + Integer remainSeatCnt1, // 첫번째차량 빈자리 수 + @NotNull + Integer locationNo2, // 두번째차량 위치 정보 + @NotNull + Integer predictTime2, // 두번째차량 도착예상시간 + @NotNull + Integer lowPlate2, // 두번째차량 저상버스여부 + @NotNull + String plateNo2, // 두번째차량 차량번호 + @NotNull + Integer remainSeatCnt2, // 두번째차량 빈자리 수 + @NotNull + Integer staOrder, // 정류소 순번 + @NotNull + String flag // 상태구분 +) { +} diff --git a/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalRespDto.java b/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalRespDto.java new file mode 100644 index 00000000..a3ae6775 --- /dev/null +++ b/server/src/main/java/com/talkka/server/api/datagg/dto/BusArrivalRespDto.java @@ -0,0 +1,15 @@ +package com.talkka.server.api.datagg.dto; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JacksonXmlRootElement(localName = "response") +public record BusArrivalRespDto( + @JacksonXmlProperty(localName = "comMsgHeader") Map comMsgHeader, + @JacksonXmlProperty(localName = "msgHeader") Map msgHeader, + @JacksonXmlProperty(localName = "msgBody") List msgBody +) implements PublicBusApiResp { +} \ No newline at end of file diff --git a/server/src/main/java/com/talkka/server/api/datagg/service/BusApiService.java b/server/src/main/java/com/talkka/server/api/datagg/service/BusApiService.java index aeeab52e..ec27733d 100644 --- a/server/src/main/java/com/talkka/server/api/datagg/service/BusApiService.java +++ b/server/src/main/java/com/talkka/server/api/datagg/service/BusApiService.java @@ -1,8 +1,10 @@ package com.talkka.server.api.datagg.service; import java.util.List; +import java.util.Optional; import com.talkka.server.api.core.exception.ApiClientException; +import com.talkka.server.api.datagg.dto.BusArrivalBodyDto; import com.talkka.server.api.datagg.dto.BusLocationBodyDto; import com.talkka.server.api.datagg.dto.BusRouteInfoBodyDto; import com.talkka.server.api.datagg.dto.BusRouteSearchBodyDto; @@ -17,5 +19,6 @@ public interface BusApiService { List getBusLocationInfo(String apiRouteId) throws ApiClientException; - // List getBusStationArrivalInfo(String routeId); + Optional getBusArrival(String apiRouteId, String apiStationId) throws + ApiClientException; } 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..748361cd 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 @@ -2,6 +2,7 @@ import java.net.URI; import java.util.List; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,6 +15,8 @@ import com.talkka.server.api.core.exception.ApiClientException; import com.talkka.server.api.datagg.config.BusApiKeyProperty; +import com.talkka.server.api.datagg.dto.BusArrivalBodyDto; +import com.talkka.server.api.datagg.dto.BusArrivalRespDto; import com.talkka.server.api.datagg.dto.BusLocationBodyDto; import com.talkka.server.api.datagg.dto.BusLocationRespDto; import com.talkka.server.api.datagg.dto.BusRouteInfoBodyDto; @@ -105,10 +108,25 @@ public List getBusLocationInfo(String apiRouteId) throws Api } } - // @Override - // public List getBusStationArrivalInfo(String routeId) { - // return null; - // } + @Override + public Optional getBusArrival(String apiRouteId, String apiStationId) throws + ApiClientException { + final String path = "/6410000/busarrivalservice/getBusArrivalItem"; + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("routeId", apiRouteId); + params.add("stationId", apiStationId); + try { + URI uri = this.getOpenApiUri(path, params); + ResponseEntity resp = restTemplate.getForEntity(uri, BusArrivalRespDto.class); + var body = resp.getBody().msgBody(); + if (body == null || body.isEmpty()) { + return Optional.empty(); + } + return Optional.of(body.get(0)); + } catch (Exception exception) { + throw new ApiClientException(exception.getMessage()); + } + } private URI getOpenApiUri(String path, MultiValueMap params) { final var builder = new DefaultUriBuilderFactory(); diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoApi.java b/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoApi.java new file mode 100644 index 00000000..e3bf8872 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoApi.java @@ -0,0 +1,28 @@ +package com.talkka.server.bus.controller; + +import org.springframework.http.ResponseEntity; + +import com.talkka.server.bus.dto.BusLiveInfoRespDto; +import com.talkka.server.common.dto.ErrorRespDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "버스 실시간 정보", description = "버스 실시간 정보 조회 API") +public interface BusLiveInfoApi { + @Operation(summary = "버스 실시간 정보 조회", description = "버스 실시간 정보 조회 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공", + content = @Content(schema = @Schema(implementation = BusLiveInfoRespDto.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", + content = @Content(schema = @Schema(implementation = ErrorRespDto.class))) + }) + ResponseEntity getBusLiveInfo( + @Parameter(description = "노선 정류장 ID", required = true) + Long routeStationId); +} diff --git a/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoController.java b/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoController.java new file mode 100644 index 00000000..f5ee2fc1 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/controller/BusLiveInfoController.java @@ -0,0 +1,34 @@ +package com.talkka.server.bus.controller; + +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.talkka.server.bus.exception.BusRouteStationNotFoundException; +import com.talkka.server.bus.exception.GetBusLiveArrivalInfoFailedException; +import com.talkka.server.bus.service.BusLiveInfoService; +import com.talkka.server.common.dto.ErrorRespDto; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/bus/live") +public class BusLiveInfoController implements BusLiveInfoApi { + private final BusLiveInfoService busLiveInfoService; + + @Override + @GetMapping("/{routeStationId}") + public ResponseEntity getBusLiveInfo(@PathVariable Long routeStationId) { + ResponseEntity response; + try { + var busLiveInfo = busLiveInfoService.getBusLiveInfo(routeStationId); + response = ResponseEntity.ok(busLiveInfo); + } catch (BusRouteStationNotFoundException | GetBusLiveArrivalInfoFailedException exception) { + response = ResponseEntity.badRequest().body(ErrorRespDto.of(exception)); + } + return response; + } +} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusArrivalRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusArrivalRespDto.java new file mode 100644 index 00000000..1624cfea --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusArrivalRespDto.java @@ -0,0 +1,46 @@ +package com.talkka.server.bus.dto; + +import java.util.Optional; + +import com.talkka.server.api.datagg.dto.BusArrivalBodyDto; +import com.talkka.server.bus.enums.RunningStatus; + +import jakarta.validation.constraints.NotNull; + +public record BusArrivalRespDto( + @NotNull + Integer locationNo1, // 첫번째차량 위치 정보 + @NotNull + Integer predictTime1, // 첫번째차량 도착예상시간 + @NotNull + String plateNo1, // 첫번째차량 차량번호 + @NotNull + Integer remainSeatCnt1, // 첫번째차량 빈자리 수 + @NotNull + Integer locationNo2, // 두번째차량 위치 정보 + @NotNull + Integer predictTime2, // 두번째차량 도착예상시간 + @NotNull + String plateNo2, // 두번째차량 차량번호 + @NotNull + Integer remainSeatCnt2, // 두번째차량 빈자리 수 + @NotNull + RunningStatus flag // 상태구분 +) { + public static Optional of(BusArrivalBodyDto busArrivalBodyDto) { + if (busArrivalBodyDto == null) { + return Optional.empty(); + } + return Optional.of(new BusArrivalRespDto( + busArrivalBodyDto.locationNo1(), + busArrivalBodyDto.predictTime1(), + busArrivalBodyDto.plateNo1(), + busArrivalBodyDto.remainSeatCnt1(), + busArrivalBodyDto.locationNo2(), + busArrivalBodyDto.predictTime2(), + busArrivalBodyDto.plateNo2(), + busArrivalBodyDto.remainSeatCnt2(), + RunningStatus.valueOf(busArrivalBodyDto.flag()) + )); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/dto/BusLiveInfoRespDto.java b/server/src/main/java/com/talkka/server/bus/dto/BusLiveInfoRespDto.java new file mode 100644 index 00000000..2431f38c --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/dto/BusLiveInfoRespDto.java @@ -0,0 +1,24 @@ +package com.talkka.server.bus.dto; + +import jakarta.validation.constraints.NotNull; + +public record BusLiveInfoRespDto( + @NotNull + Short seq, + @NotNull + Long routeId, + @NotNull + String routeName, + @NotNull + BusRouteStationRespDto routeStation, + BusArrivalRespDto arrivalInfo) { + + public static BusLiveInfoRespDto of( + Short seq, + Long routeId, + String routeName, + BusRouteStationRespDto routeStation, + BusArrivalRespDto arrivalInfo) { + return new BusLiveInfoRespDto(seq, routeId, routeName, routeStation, arrivalInfo); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/enums/RunningStatus.java b/server/src/main/java/com/talkka/server/bus/enums/RunningStatus.java new file mode 100644 index 00000000..8e142bcf --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/enums/RunningStatus.java @@ -0,0 +1,13 @@ +package com.talkka.server.bus.enums; + +public enum RunningStatus { + RUN, PASS, STOP, WAIT, UNKNOWN; + + public static RunningStatus fromString(String status) { + try { + return RunningStatus.valueOf(status); + } catch (Exception e) { + return UNKNOWN; + } + } +} diff --git a/server/src/main/java/com/talkka/server/bus/exception/GetBusLiveArrivalInfoFailedException.java b/server/src/main/java/com/talkka/server/bus/exception/GetBusLiveArrivalInfoFailedException.java new file mode 100644 index 00000000..8e97a564 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/exception/GetBusLiveArrivalInfoFailedException.java @@ -0,0 +1,9 @@ +package com.talkka.server.bus.exception; + +public class GetBusLiveArrivalInfoFailedException extends RuntimeException { + private static final String MESSAGE = "버스 도착 정보 획득에 실패하였습니다."; + + public GetBusLiveArrivalInfoFailedException() { + super(MESSAGE); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusArrivalService.java b/server/src/main/java/com/talkka/server/bus/service/BusArrivalService.java new file mode 100644 index 00000000..f971d1b9 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusArrivalService.java @@ -0,0 +1,12 @@ +package com.talkka.server.bus.service; + +import java.util.Optional; + +import com.talkka.server.bus.dto.BusArrivalRespDto; +import com.talkka.server.bus.exception.GetBusLiveArrivalInfoFailedException; + +public interface BusArrivalService { + Optional getBusArrivalInfo(Long routeStationId, String apiRouteId, + String apiStationId) throws + GetBusLiveArrivalInfoFailedException; +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoService.java b/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoService.java new file mode 100644 index 00000000..98d7a163 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoService.java @@ -0,0 +1,12 @@ +package com.talkka.server.bus.service; + +import com.talkka.server.bus.dto.BusLiveInfoRespDto; +import com.talkka.server.bus.exception.BusRouteStationNotFoundException; +import com.talkka.server.bus.exception.GetBusLiveArrivalInfoFailedException; + +public interface BusLiveInfoService { + + BusLiveInfoRespDto getBusLiveInfo(Long routeStationId) throws + BusRouteStationNotFoundException, + GetBusLiveArrivalInfoFailedException; +} diff --git a/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoServiceImpl.java b/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoServiceImpl.java new file mode 100644 index 00000000..73746787 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/BusLiveInfoServiceImpl.java @@ -0,0 +1,40 @@ +package com.talkka.server.bus.service; + +import org.springframework.stereotype.Service; + +import com.talkka.server.bus.dao.BusRouteStationRepository; +import com.talkka.server.bus.dto.BusLiveInfoRespDto; +import com.talkka.server.bus.dto.BusRouteStationRespDto; +import com.talkka.server.bus.exception.BusRouteStationNotFoundException; +import com.talkka.server.bus.exception.GetBusLiveArrivalInfoFailedException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BusLiveInfoServiceImpl implements BusLiveInfoService { + private final BusArrivalService busArrivalService; + private final BusRouteStationRepository busRouteStationRepository; + + @Override + public BusLiveInfoRespDto getBusLiveInfo(Long routeStationId) + throws BusRouteStationNotFoundException, GetBusLiveArrivalInfoFailedException { + var busRouteStation = busRouteStationRepository.findById(routeStationId) + .orElseThrow(() -> new BusRouteStationNotFoundException(routeStationId)); + var route = busRouteStation.getRoute(); + var station = busRouteStation.getStation(); + var routeId = route.getId(); + var routeName = route.getRouteName(); + var apiRouteId = route.getApiRouteId(); + var apiStationId = station.getApiStationId(); + var arrivalInfo = busArrivalService.getBusArrivalInfo(routeStationId, apiRouteId, apiStationId) + .orElse(null); + + return BusLiveInfoRespDto.of( + busRouteStation.getStationSeq(), + routeId, + routeName, + BusRouteStationRespDto.of(busRouteStation), + arrivalInfo); + } +} diff --git a/server/src/main/java/com/talkka/server/bus/service/CachedBusArrivalService.java b/server/src/main/java/com/talkka/server/bus/service/CachedBusArrivalService.java new file mode 100644 index 00000000..57847161 --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/service/CachedBusArrivalService.java @@ -0,0 +1,39 @@ +package com.talkka.server.bus.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.talkka.server.api.core.exception.ApiClientException; +import com.talkka.server.api.datagg.service.BusApiService; +import com.talkka.server.bus.dto.BusArrivalRespDto; +import com.talkka.server.bus.exception.GetBusLiveArrivalInfoFailedException; +import com.talkka.server.common.util.CachedStorage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class CachedBusArrivalService implements BusArrivalService { + private final BusApiService busApiService; + private final CachedStorage arrivalCache; + + @Override + public Optional getBusArrivalInfo(Long routeStationId, String apiRouteId, + String apiStationId) + throws GetBusLiveArrivalInfoFailedException { + try { + var cached = arrivalCache.get(routeStationId); + if (cached.isPresent()) { + return cached; + } + + var arrivalInfo = busApiService.getBusArrival(apiRouteId, apiStationId) + .flatMap(BusArrivalRespDto::of); + arrivalInfo.ifPresent(busLiveArrivalRespDto -> arrivalCache.put(routeStationId, busLiveArrivalRespDto)); + return arrivalInfo; + } catch (ApiClientException exception) { + throw new GetBusLiveArrivalInfoFailedException(); + } + } +} diff --git a/server/src/main/java/com/talkka/server/bus/util/ArrivalCache.java b/server/src/main/java/com/talkka/server/bus/util/ArrivalCache.java new file mode 100644 index 00000000..c614319a --- /dev/null +++ b/server/src/main/java/com/talkka/server/bus/util/ArrivalCache.java @@ -0,0 +1,13 @@ +package com.talkka.server.bus.util; + +import org.springframework.stereotype.Component; + +import com.talkka.server.bus.dto.BusArrivalRespDto; +import com.talkka.server.common.util.MemoryCachedStorage; + +@Component +public class ArrivalCache extends MemoryCachedStorage { + ArrivalCache() { + super(60); // 60초 주기로 캐싱 무효화 + } +} diff --git a/server/src/main/java/com/talkka/server/common/util/CachedStorage.java b/server/src/main/java/com/talkka/server/common/util/CachedStorage.java new file mode 100644 index 00000000..0f09443e --- /dev/null +++ b/server/src/main/java/com/talkka/server/common/util/CachedStorage.java @@ -0,0 +1,11 @@ +package com.talkka.server.common.util; + +import java.util.Optional; + +public interface CachedStorage { + Optional get(K key); + + void put(K key, V value); + + void remove(K key); +} diff --git a/server/src/main/java/com/talkka/server/common/util/MemoryCachedStorage.java b/server/src/main/java/com/talkka/server/common/util/MemoryCachedStorage.java new file mode 100644 index 00000000..072af984 --- /dev/null +++ b/server/src/main/java/com/talkka/server/common/util/MemoryCachedStorage.java @@ -0,0 +1,53 @@ +package com.talkka.server.common.util; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * MemoryCachedStorage + * Redis 사용 이전에 메모리에 캐시를 저장하는 클래스 + * 사용량이 증가할 경우 put 에서 removeExpired 를 호출하여 메모리를 정리에 시간이 크게 소요될 수 있음 + * (임시방편으로 사용하고 있으며 추후 Redis 로 대체할 예정) + * @param + * @param + */ +public abstract class MemoryCachedStorage implements CachedStorage { + private final Map> cache = new ConcurrentHashMap<>(); + private final Integer expireTimeSeconds; + + public MemoryCachedStorage(Integer expireTimeSeconds) { + this.expireTimeSeconds = expireTimeSeconds; + } + + @Override + public void put(K key, V value) { + TimeExpiredValue timeExpiredValue = TimeExpiredValue.create(value, + System.currentTimeMillis() + expireTimeSeconds * 1000L); + cache.put(key, timeExpiredValue); + removeExpired(); + } + + @Override + public Optional get(K key) { + TimeExpiredValue timeExpiredValue = cache.get(key); + + if (timeExpiredValue == null) { + return Optional.empty(); + } + if (timeExpiredValue.isExpired()) { + cache.remove(key); + return Optional.empty(); + } + return Optional.of(timeExpiredValue.value()); + } + + @Override + public void remove(K key) { + cache.remove(key); + } + + private void removeExpired() { + cache.entrySet().removeIf(entry -> entry.getValue().isExpired()); + } +} diff --git a/server/src/main/java/com/talkka/server/common/util/TimeExpiredValue.java b/server/src/main/java/com/talkka/server/common/util/TimeExpiredValue.java new file mode 100644 index 00000000..74f6a251 --- /dev/null +++ b/server/src/main/java/com/talkka/server/common/util/TimeExpiredValue.java @@ -0,0 +1,15 @@ +package com.talkka.server.common.util; + +public record TimeExpiredValue(long expiredTime, T value) { + public static TimeExpiredValue create(T value, long expiredTime) { + return new TimeExpiredValue<>(expiredTime, value); + } + + public static TimeExpiredValue create(T value) { + return new TimeExpiredValue<>(System.currentTimeMillis(), value); + } + + public boolean isExpired() { + return System.currentTimeMillis() > expiredTime; + } +}