Skip to content

Commit

Permalink
[FEAT] 버스 실시간 도착 정보 연동 및 구현 (#121)
Browse files Browse the repository at this point in the history
## 작업 내용
- [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
  • Loading branch information
JuneParkCode authored Aug 26, 2024
1 parent d1c38d8 commit 083d954
Show file tree
Hide file tree
Showing 18 changed files with 428 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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 // 상태구분
) {
}
Original file line number Diff line number Diff line change
@@ -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<String, String> comMsgHeader,
@JacksonXmlProperty(localName = "msgHeader") Map<String, String> msgHeader,
@JacksonXmlProperty(localName = "msgBody") List<BusArrivalBodyDto> msgBody
) implements PublicBusApiResp<BusArrivalBodyDto> {
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,5 +19,6 @@ public interface BusApiService {

List<BusLocationBodyDto> getBusLocationInfo(String apiRouteId) throws ApiClientException;

// List<RouteBusStationArrivalInfoRespDto> getBusStationArrivalInfo(String routeId);
Optional<BusArrivalBodyDto> getBusArrival(String apiRouteId, String apiStationId) throws
ApiClientException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.URI;
import java.util.List;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -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;
Expand Down Expand Up @@ -105,10 +108,25 @@ public List<BusLocationBodyDto> getBusLocationInfo(String apiRouteId) throws Api
}
}

// @Override
// public List<RouteBusStationArrivalInfoRespDto> getBusStationArrivalInfo(String routeId) {
// return null;
// }
@Override
public Optional<BusArrivalBodyDto> getBusArrival(String apiRouteId, String apiStationId) throws
ApiClientException {
final String path = "/6410000/busarrivalservice/getBusArrivalItem";
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("routeId", apiRouteId);
params.add("stationId", apiStationId);
try {
URI uri = this.getOpenApiUri(path, params);
ResponseEntity<BusArrivalRespDto> 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<String, String> params) {
final var builder = new DefaultUriBuilderFactory();
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<BusArrivalRespDto> 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())
));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.talkka.server.bus.exception;

public class GetBusLiveArrivalInfoFailedException extends RuntimeException {
private static final String MESSAGE = "버스 도착 정보 획득에 실패하였습니다.";

public GetBusLiveArrivalInfoFailedException() {
super(MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -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<BusArrivalRespDto> getBusArrivalInfo(Long routeStationId, String apiRouteId,
String apiStationId) throws
GetBusLiveArrivalInfoFailedException;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Long, BusArrivalRespDto> arrivalCache;

@Override
public Optional<BusArrivalRespDto> 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();
}
}
}
Loading

0 comments on commit 083d954

Please sign in to comment.