AmapRouteClient.java
5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.diligrp.rider.service.amap;
import com.diligrp.rider.config.AmapProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;
/**
* 高德电动车路径规划客户端(/v5/direction/electrobike)。
*
* 任何失败场景(未启用 / Key 缺失 / 坐标非法 / 起止同点 / HTTP 异常 / status != "1" / paths 空 / 字段缺失)
* 均返回 Optional.empty(),由调用方决定降级策略。
*/
@Slf4j
@Component
public class AmapRouteClient {
private final AmapProperties amapProperties;
private final ObjectMapper objectMapper;
private final HttpClient httpClient;
public AmapRouteClient(AmapProperties amapProperties, ObjectMapper objectMapper) {
this.amapProperties = amapProperties;
this.objectMapper = objectMapper;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(amapProperties.getConnectTimeoutMillis()))
.build();
}
/** 高德电动车路径规划结果:原始距离(米)与耗时(秒)。 */
public record AmapRoute(int distanceMeters, int durationSeconds) {}
/**
* 调用高德电动车路径规划,返回原始距离(米)+ 耗时(秒)。
*/
public Optional<AmapRoute> getElectrobikeRoute(String startLng, String startLat,
String endLng, String endLat) {
if (!amapProperties.isEnabled()) {
return Optional.empty();
}
if (amapProperties.getKey() == null || amapProperties.getKey().isBlank()) {
log.warn("Amap key 未配置,跳过高德调用");
return Optional.empty();
}
double sLng;
double sLat;
double eLng;
double eLat;
try {
sLng = Double.parseDouble(startLng);
sLat = Double.parseDouble(startLat);
eLng = Double.parseDouble(endLng);
eLat = Double.parseDouble(endLat);
} catch (Exception e) {
return Optional.empty();
}
if (sLng == eLng && sLat == eLat) {
return Optional.empty();
}
String origin = String.format("%.6f,%.6f", sLng, sLat);
String destination = String.format("%.6f,%.6f", eLng, eLat);
String url = amapProperties.getBaseUrl() + "/v5/direction/electrobike"
+ "?key=" + URLEncoder.encode(amapProperties.getKey(), StandardCharsets.UTF_8)
+ "&origin=" + URLEncoder.encode(origin, StandardCharsets.UTF_8)
+ "&destination=" + URLEncoder.encode(destination, StandardCharsets.UTF_8)
+ "&show_fields=cost";
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofMillis(amapProperties.getReadTimeoutMillis()))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
log.warn("Amap electrobike HTTP {} body={}", response.statusCode(), trim(response.body()));
return Optional.empty();
}
JsonNode root = objectMapper.readTree(response.body());
String status = root.path("status").asText();
if (!"1".equals(status)) {
log.warn("Amap electrobike status={} info={} infocode={}",
status, root.path("info").asText(), root.path("infocode").asText());
return Optional.empty();
}
JsonNode paths = root.path("route").path("paths");
if (!paths.isArray() || paths.isEmpty()) {
log.warn("Amap electrobike paths 空 body={}", trim(response.body()));
return Optional.empty();
}
JsonNode first = paths.get(0);
long distanceMeters = first.path("distance").asLong(-1);
long durationSeconds = first.path("cost").path("duration").asLong(-1);
if (distanceMeters < 0 || durationSeconds <= 0) {
log.warn("Amap electrobike 字段异常 distance={} duration={} body={}",
distanceMeters, durationSeconds, trim(response.body()));
return Optional.empty();
}
return Optional.of(new AmapRoute((int) distanceMeters, (int) durationSeconds));
} catch (Exception e) {
log.warn("Amap electrobike 调用失败 err={}", e.getMessage());
return Optional.empty();
}
}
/**
* 返回预计骑行分钟数(向上取整)。委托 {@link #getElectrobikeRoute}。
*/
public Optional<Integer> getElectrobikeDurationMinutes(String startLng, String startLat,
String endLng, String endLat) {
return getElectrobikeRoute(startLng, startLat, endLng, endLat)
.map(r -> (int) Math.ceil(r.durationSeconds() / 60.0));
}
private String trim(String body) {
if (body == null) return "";
return body.length() > 300 ? body.substring(0, 300) : body;
}
}