Skip to content

Commit 32dd7ec

Browse files
authored
차량 요청 목록 조회api 추가, 역지오코딩으로 주소 추가 등등
차량 요청 목록 조회api 추가, 역지오코딩으로 주소 추가 등등
2 parents 650ac04 + 6510b7b commit 32dd7ec

File tree

14 files changed

+210
-33
lines changed

14 files changed

+210
-33
lines changed

scripts/start.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export $(cat /home/ubuntu/apps/okagaka-server/.env | xargs)
2525

2626
# DB, AWS, 기타 환경 변수 전달
2727
nohup java \
28+
-Duser.timezone=Asia/Seoul \
2829
-DRDS_HOST=$RDS_HOST \
2930
-DRDS_PORT=$RDS_PORT \
3031
-DRDS_NAME=$RDS_NAME \

src/main/java/com/okagaka/OkaGaka/common/external/tmap/TmapGeocodingClient.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,54 @@ public Coordinate getCoordinates(String cityDo, String guGun, String dong, Strin
5555

5656
return new Coordinate(latitude, longitude);
5757
}
58+
59+
/**
60+
* 좌표를 주소로 변환합니다. (역 지오코딩)
61+
* 건물명(buildingName)을 우선적으로 반환하며, 없을 경우 '도로명 + 건물번호'를 반환합니다.
62+
* @param longitude 경도
63+
* @param latitude 위도
64+
* @return 변환된 건물명 또는 도로명 주소
65+
*/
66+
public String getAddress(double longitude, double latitude) {
67+
String url = UriComponentsBuilder.fromHttpUrl("https://apis.openapi.sk.com/tmap/geo/reversegeocoding")
68+
.queryParam("version", "1")
69+
.queryParam("lat", latitude)
70+
.queryParam("lon", longitude)
71+
.queryParam("addressType", "A03") // A03: 도로명 주소
72+
.encode()
73+
.toUriString();
74+
75+
HttpHeaders headers = new HttpHeaders();
76+
headers.set("Accept", "application/json");
77+
headers.set("appKey", appKey);
78+
79+
HttpEntity<String> entity = new HttpEntity<>(headers);
80+
81+
ResponseEntity<JsonNode> response = restTemplate.exchange(url, HttpMethod.GET, entity, JsonNode.class);
82+
83+
JsonNode addressInfo = response.getBody().path("addressInfo");
84+
85+
if (addressInfo.isMissingNode()) {
86+
throw new RuntimeException("주소 변환 실패: lat=" + latitude + ", lon=" + longitude);
87+
}
88+
89+
// 1. buildingName을 우선적으로 가져옵니다.
90+
String buildingName = addressInfo.path("buildingName").asText(null);
91+
92+
// 2. buildingName이 존재하고 비어있지 않다면, 해당 값을 바로 반환합니다.
93+
if (buildingName != null && !buildingName.isEmpty()) {
94+
return buildingName;
95+
}
96+
97+
// 3. buildingName이 없는 경우, 대체 정보로 '도로명 + 건물번호'를 조합하여 반환합니다.
98+
String roadName = addressInfo.path("roadName").asText("");
99+
String buildingIndex = addressInfo.path("buildingIndex").asText("");
100+
String fallbackAddress = (roadName + " " + buildingIndex).trim();
101+
102+
if (fallbackAddress.isEmpty()) {
103+
throw new RuntimeException("파싱된 주소 정보가 없습니다: lat=" + latitude + ", lon=" + longitude);
104+
}
105+
106+
return fallbackAddress;
107+
}
58108
}

src/main/java/com/okagaka/OkaGaka/domain/aidecision/service/AsyncDecisionService.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -374,17 +374,17 @@ private void performFinalAnalysisAndSaveDecision(CarRequest carRequest, LocalDat
374374
User user = carRequest.getUser();
375375
Vehicle vehicle = user.getFamilyGroup().getVehicle();
376376

377-
// 1. 대중교통 정보 조회 (API 제한으로 임시 비활성화)
378-
System.out.println(">> [임시] 대중교통 API 호출을 건너뜁니다.");
379-
TransitInfo transitInfo = null; // API 호출 대신 null을 할당
377+
// // 1. 대중교통 정보 조회 (API 제한으로 임시 비활성화)
378+
// System.out.println(">> [임시] 대중교통 API 호출을 건너뜁니다.");
379+
// TransitInfo transitInfo = null; // API 호출 대신 null을 할당
380380

381-
// // 1. 대중교통 정보 조회
382-
// TransitInfo transitInfo = transitService.getTransitInfo(
383-
// String.valueOf(carRequest.getRequesterLongitude()),
384-
// String.valueOf(carRequest.getRequesterLatitude()),
385-
// String.valueOf(carRequest.getDestinationLongitude()),
386-
// String.valueOf(carRequest.getDestinationLatitude())
387-
// );
381+
// 1. 대중교통 정보 조회
382+
TransitInfo transitInfo = transitService.getTransitInfo(
383+
String.valueOf(carRequest.getRequesterLongitude()),
384+
String.valueOf(carRequest.getRequesterLatitude()),
385+
String.valueOf(carRequest.getDestinationLongitude()),
386+
String.valueOf(carRequest.getDestinationLatitude())
387+
);
388388

389389
if (transitInfo == null) {
390390
System.out.println(">> 조회된 대중교통 경로가 없습니다.");

src/main/java/com/okagaka/OkaGaka/domain/carrequest/controller/CarRequestController.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.okagaka.OkaGaka.common.security.CustomUserDetails;
55
import com.okagaka.OkaGaka.domain.aidecision.dto.AiDecisionResponse;
66
import com.okagaka.OkaGaka.domain.carrequest.dto.CarRequestDto;
7+
import com.okagaka.OkaGaka.domain.carrequest.dto.CarRequestListResponse;
78
import com.okagaka.OkaGaka.domain.carrequest.dto.CarRequestResponse;
89
import com.okagaka.OkaGaka.domain.carrequest.dto.ConfirmationRequest;
910
import com.okagaka.OkaGaka.domain.carrequest.service.CarRequestService;
@@ -16,6 +17,8 @@
1617
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1718
import org.springframework.web.bind.annotation.*;
1819

20+
import java.util.List;
21+
1922
@RestController
2023
@RequiredArgsConstructor
2124
@RequestMapping("/api/car-request")
@@ -58,4 +61,14 @@ public ResponseEntity<ApiResponse<Void>> confirmChoice(
5861
return ResponseEntity.ok(ApiResponse.success(null, "결정이 성공적으로 반영되었습니다."));
5962
}
6063

64+
@Operation(summary = "내 차량 요청 목록 조회", description = "로그인한 사용자의 모든 차량 요청 목록을 최신순으로 조회합니다.")
65+
@GetMapping("/my-requests")
66+
public ResponseEntity<ApiResponse<List<CarRequestListResponse>>> getMyCarRequests(
67+
@AuthenticationPrincipal CustomUserDetails userDetails
68+
) {
69+
Long userId = userDetails.getUserId();
70+
List<CarRequestListResponse> response = carRequestService.getUserCarRequests(userId);
71+
return ResponseEntity.ok(ApiResponse.success(response));
72+
}
73+
6174
}

src/main/java/com/okagaka/OkaGaka/domain/carrequest/dto/CarRequestDto.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@
77
@Setter
88
public class CarRequestDto {
99

10-
// 요청자 현재 위치
11-
// private String requesterCityDo;
12-
// private String requesterGuGun;
13-
// private String requesterDong;
14-
// private String requesterBunji;
15-
1610
// 도착지
1711
private String destinationCityDo;
1812
private String destinationGuGun;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.okagaka.OkaGaka.domain.carrequest.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.okagaka.OkaGaka.domain.carrequest.entity.CarRequest;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
import java.time.LocalDateTime;
9+
10+
@Getter
11+
@Builder
12+
@JsonInclude(JsonInclude.Include.NON_NULL)
13+
public class CarRequestListResponse {
14+
15+
private Long carRequestId;
16+
// private double destinationLongitude;
17+
// private double destinationLatitude;
18+
private String destinationAddress;
19+
private LocalDateTime requestTime; // 요청 시간
20+
private CarRequest.CarRequestStatus status;
21+
22+
// CONFIRMED 상태일 때만 포함될 필드
23+
private LocalDateTime estimatedPickupTime;
24+
private LocalDateTime estimatedDestinationTime;
25+
26+
/**
27+
* CarRequest 엔티티를 DTO로 변환하는 정적 팩토리 메서드
28+
*/
29+
// public static CarRequestListResponse from(CarRequest carRequest) {
30+
// CarRequestListResponseBuilder builder = CarRequestListResponse.builder()
31+
// .carRequestId(carRequest.getId())
32+
// .destinationLongitude(carRequest.getDestinationLongitude())
33+
// .destinationLatitude(carRequest.getDestinationLatitude())
34+
// .requestTime(carRequest.getCreatedAt()) // BaseTimeEntity의 생성 시간 활용
35+
// .status(carRequest.getStatus());
36+
//
37+
// // 상태가 CONFIRMED일 경우에만 예상 시간 정보를 추가
38+
// if (carRequest.getStatus() == CarRequest.CarRequestStatus.CONFIRMED) {
39+
// builder.estimatedPickupTime(carRequest.getEstimatedPickupTime())
40+
// .estimatedDestinationTime(carRequest.getEstimatedDestinationTime());
41+
// }
42+
//
43+
// return builder.build();
44+
// }
45+
}

src/main/java/com/okagaka/OkaGaka/domain/carrequest/entity/CarRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public enum CarRequestStatus {
6767
ANALYSIS_COMPLETE, // AI 분석 완료 (사용자 확인 대기)
6868
CONFIRMED, // 사용자가 차량 이용을 최종 확정함
6969
REJECTED, // 규칙 기반 또는 시스템 오류로 거절됨
70-
CANCELLED , // 사용자가 대중교통을 선택하거나 요청을 취소함
70+
CANCELLED , // 사용자가 대중교통을 선택하거나 요청을 취소함
7171
COMPLETED
7272
}
7373

src/main/java/com/okagaka/OkaGaka/domain/carrequest/repository/CarRequestRepository.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,11 @@ public interface CarRequestRepository extends JpaRepository<CarRequest, Long> {
3737

3838
// carpoolGroupId로 모든 관련 요청을 찾는 메서드
3939
List<CarRequest> findAllByCarpoolGroupId(Long carpoolGroupId);
40+
41+
/**
42+
* 특정 사용자의 모든 차량 요청을 생성 시간(요청 시간)의 내림차순으로 조회합니다.
43+
* @param userId 사용자 ID
44+
* @return 정렬된 차량 요청 목록
45+
*/
46+
List<CarRequest> findByUser_IdOrderByCreatedAtDesc(Long userId);
4047
}

src/main/java/com/okagaka/OkaGaka/domain/carrequest/service/CarRequestService.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,41 @@ public void confirmUserChoice(Long carRequestId, Long userId, ConfirmationReques
407407
throw e;
408408
}
409409
}
410+
411+
/**
412+
* 로그인한 사용자의 모든 차량 요청 목록을 조회합니다.
413+
* @param userId 로그인한 사용자의 ID
414+
* @return 차량 요청 DTO 리스트 (최신순으로 정렬됨)
415+
*/
416+
@Transactional(readOnly = true)
417+
public List<CarRequestListResponse> getUserCarRequests(Long userId) {
418+
List<CarRequest> requests = carRequestRepository.findByUser_IdOrderByCreatedAtDesc(userId);
419+
420+
return requests.stream()
421+
.map(carRequest -> {
422+
// ✨ 각 요청에 대해 역 지오코딩 호출
423+
String address = tmapGeocodingClient.getAddress(
424+
carRequest.getDestinationLongitude(),
425+
carRequest.getDestinationLatitude()
426+
);
427+
428+
// DTO 빌더 생성
429+
CarRequestListResponse.CarRequestListResponseBuilder builder = CarRequestListResponse.builder()
430+
.carRequestId(carRequest.getId())
431+
.destinationAddress(address) // 변환된 주소 사용
432+
.requestTime(carRequest.getCreatedAt())
433+
.status(carRequest.getStatus());
434+
435+
// CONFIRMED 상태일 때 시간 정보 추가
436+
if (carRequest.getStatus() == CarRequest.CarRequestStatus.CONFIRMED) {
437+
builder.estimatedPickupTime(carRequest.getEstimatedPickupTime())
438+
.estimatedDestinationTime(carRequest.getEstimatedDestinationTime());
439+
}
440+
441+
return builder.build();
442+
})
443+
.collect(Collectors.toList());
444+
}
410445
}
411446

412447
// /**

src/main/java/com/okagaka/OkaGaka/domain/location/service/LocationCacheService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void saveVehicleLocation(Long groupId, Long vehicleId, VehicleLocationDTO
5555
String key = String.format(VEHICLE_LOCATION_KEY, groupId, vehicleId);
5656
String locationJson = objectMapper.writeValueAsString(dto);
5757
// 차량 정보는 더 자주 업데이트될 수 있으므로 TTL을 적절히 조절 (예: 10분)
58-
redisTemplate.opsForValue().set(key, locationJson, 10, TimeUnit.MINUTES); // TTL 10분으로 설정
58+
redisTemplate.opsForValue().set(key, locationJson, 40, TimeUnit.MINUTES); // TTL 40분으로 설정
5959
} catch (Exception e) {
6060
// 로깅
6161
System.err.println("Failed to save vehicle location to Redis: " + e.getMessage());

0 commit comments

Comments
 (0)