Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 버스위치정보 수집 스케줄러 및 버스 통계 서비스 구현 #114

Merged
merged 32 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b8f697b
feat : EndBus enum 수정 - 코드2 추가
Gyaak Aug 23, 2024
68d1ce4
feat : BusRouteEntity 연관관계 수정
Gyaak Aug 23, 2024
9df9f2d
feat : BusLocation 스케줄러 기능 추가 - 병렬처리, 재시도 로직
Gyaak Aug 23, 2024
bc69ecd
feat : BusLocation 스케줄러 기능 추가 - 병렬처리, 재시도 로직
Gyaak Aug 23, 2024
3f6907f
feat : 버스 위치 데이터 가공을 위한 메소드 추가
Gyaak Aug 23, 2024
f9b4e32
feat : Spring retry 의존성 추가
Gyaak Aug 23, 2024
5325d66
feat : 버스 통계 서비스 구현
Gyaak Aug 23, 2024
a1b6ccd
feat : @Transactional 의존성 수정
Gyaak Aug 24, 2024
f5b8467
feat : query - table alias 수정
Gyaak Aug 24, 2024
f2abdea
feat : 중복 로직 제거
Gyaak Aug 24, 2024
d5ec6de
BusStatEntity 식별을 위한 별도 메소드 생성 및 PK 기준으로 equals&hashcode 오버라이드
Gyaak Aug 24, 2024
e3a9e78
BusStat 조회 서비스 구현
Gyaak Aug 24, 2024
74bf4aa
BusStat 조회 서비스 구현
Gyaak Aug 24, 2024
108ad1d
3003번 율정중학교 정거장 추가
Gyaak Aug 24, 2024
a2d0031
BusStat 조회 구현
Gyaak Aug 24, 2024
64370f1
BusStat 조회 구현
Gyaak Aug 24, 2024
ad071bc
현재 시간 기준으로 버스 통계 조회 로직 구현
Gyaak Aug 24, 2024
0077439
현재 시간 기준으로 버스 통계 조회 로직 구현
Gyaak Aug 24, 2024
46e3c10
3시 기준으로 요일 변경되도록 수정
Gyaak Aug 24, 2024
7e60395
주석 및 로직 수정
Gyaak Aug 24, 2024
2f3cadd
날짜가 바뀌는 시점의 통계 조회 로직 수정
Gyaak Aug 24, 2024
be61855
refactor : TimeSlot 리팩토링
Gyaak Aug 28, 2024
675d2a9
feat : 버스 위치 정보 가공 기능 구현
Gyaak Aug 28, 2024
5dc73ce
feat : 버스 통계 조회 기능 구현
Gyaak Aug 28, 2024
b2a3724
feat : 버스 통계 조회 기능 구현
Gyaak Aug 28, 2024
2cc5b95
refactor : TimeSlot 롤백
Gyaak Aug 28, 2024
d7fead6
refactor : TimeSlot 롤백
Gyaak Aug 28, 2024
8d7fa40
refactor : 불필요한 Serializable 삭제
Gyaak Aug 28, 2024
0eaf961
refactor : 테스트용 컨트롤러 명시 주석 추가
Gyaak Aug 28, 2024
3513910
refactor : standardTime 계산 코드 명확하게 변경
Gyaak Aug 28, 2024
5dd52e1
refactor : 변수명 수정
Gyaak Aug 28, 2024
857adc1
Merge branch 'develop' into feature/#113
Gyaak Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/mysql-files/bus_route_station.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions data/mysql-files/bus_station.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ 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'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' //Swagger

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,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;

Expand Down Expand Up @@ -42,15 +46,10 @@ public List<BusRouteSearchBodyDto> getSearchedRouteInfo(String keyword) throws A
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("keyword", keyword);
try {
URI uri = this.getOpenApiUri(path, params);
ResponseEntity<BusRouteSearchRespDto> 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<BusRouteSearchRespDto> resp = apiCallWithRetry(path, params, BusRouteSearchRespDto.class);
return resp.getBody().msgBody();
} catch (RestClientException exception) {
throw new ApiClientException("결과가 없습니다.");
}
}

Expand All @@ -60,15 +59,10 @@ public List<BusRouteInfoBodyDto> getRouteInfo(String apiRouteId) throws ApiClien
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("routeId", apiRouteId);
try {
URI uri = this.getOpenApiUri(path, params);
ResponseEntity<BusRouteInfoRespDto> 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<BusRouteInfoRespDto> resp = apiCallWithRetry(path, params, BusRouteInfoRespDto.class);
return resp.getBody().msgBody();
} catch (RestClientException exception) {
throw new ApiClientException("결과가 없습니다.");
}
}

Expand All @@ -78,15 +72,10 @@ public List<BusRouteStationBodyDto> getRouteStationInfo(String apiRouteId) throw
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("routeId", apiRouteId);
try {
URI uri = this.getOpenApiUri(path, params);
ResponseEntity<BusRouteStationRespDto> 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<BusRouteStationRespDto> resp = apiCallWithRetry(path, params, BusRouteStationRespDto.class);
return resp.getBody().msgBody();
} catch (RestClientException exception) {
throw new ApiClientException("결과가 없습니다.");
}
}

Expand All @@ -96,15 +85,10 @@ public List<BusLocationBodyDto> getBusLocationInfo(String apiRouteId) throws Api
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("routeId", apiRouteId);
try {
URI uri = this.getOpenApiUri(path, params);
ResponseEntity<BusLocationRespDto> 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<BusLocationRespDto> resp = apiCallWithRetry(path, params, BusLocationRespDto.class);
return resp.getBody().msgBody();
} catch (RestClientException exception) {
throw new ApiClientException("결과가 없습니다.");
}
}

Expand Down Expand Up @@ -139,4 +123,31 @@ private URI getOpenApiUri(String path, MultiValueMap<String, String> params) {
.queryParams(params)
.build();
}

// 리트라이 로직을 포함한 api call
private <T> ResponseEntity<T> apiCallWithRetry(String path, MultiValueMap<String, String> params,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 친구 Abstract class도 빼도 좋을 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리팩토링 진행 하면서 별도 클래스로 분리해 빈으로 등록하는것 고려중입니다.

Class<T> type) throws RestClientException {

final int MAX_ATTEMPTS = 10;
final int RETRY_INTERVAL = 200;
Comment on lines +131 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAX_ATTEMPS 3회정도면 충분해보입니다! 다른 파트에서 주입할 수 있으면 더 좋을듯...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 이번주말에 분리하도록 리팩토링할 예정이라 그때 수정하도록 하겠습니다.


// 이후 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 터트림
});
}
}
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admin 권한 필요 (bus쪽은 공개되어있습니다.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

관리자페이지 완성 후 adminController로 옮길 예정입니다.

public String process() {
busLocationProcessor.start(busLocationRepository.findAll());
return "success";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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")
Comment on lines +20 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View 라는 이름이 모호하다고 생각합니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분은 다른 controller 를 통하게 될 것임. 테스트용 임시 컨트롤러로 유지하고 이후 작업에서 변경하도록 할 것임.

public ResponseEntity<BusViewDto> 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
) {
Comment on lines +24 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 controller 임시로 둔 것으로 이해해도 될까요? (추후에 제가 수정하게 되는 컨트롤러)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다. 프론트에 맞춰서 수정하시면 됩니다.

LocalDateTime time = LocalDateTime.now().plusHours(8);
return ResponseEntity.ok(busViewService.getBusView(routeStationId, stationNum, time, timeRange, week));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -60,7 +60,7 @@ public class BusLocationEntity {
private PlateType plateType;

@Column(name = "remain_seat_count", nullable = false)
private Short remainSeatCount;
private Integer remainSeatCount;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변경되면서 다른 쪽에서 터질 수 있을 것 같은데 염두에 두겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 리팩토링 예정


@Column(name = "api_call_no", nullable = false)
private Integer apiCallNo;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
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.stereotype.Repository;

@Repository
public interface BusLocationRepository extends JpaRepository<BusLocationEntity, Long> {
}

@Query(value = "select distinct l.apiCallNo from bus_location l")
List<Integer> getDistinctApiCallNoList();

@Query(value = "select count(*) from bus_location")
Integer getRowNum();

List<BusLocationEntity> findByApiCallNo(Integer apiCallNo);

List<BusLocationEntity> findByCreatedAtBetween(LocalDateTime startTime, LocalDateTime endTime);

}
Original file line number Diff line number Diff line change
@@ -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 = "plateStatistic", cascade = CascadeType.PERSIST)
private List<BusRemainSeatEntity> seats;
}
Original file line number Diff line number Diff line change
@@ -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<BusPlateStatisticEntity, Long> {
}
Loading
Loading