| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package com.ruoyi.app.flashdelivery.route;
- import com.alibaba.fastjson2.JSON;
- import com.alibaba.fastjson2.JSONArray;
- import com.alibaba.fastjson2.JSONObject;
- import com.ruoyi.common.core.domain.entity.SysDictData;
- import com.ruoyi.common.utils.DictUtils;
- import com.ruoyi.system.domain.flash.FlashDeliveryVehicleType;
- import org.apache.http.client.config.RequestConfig;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.entity.ContentType;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.impl.client.HttpClients;
- import org.apache.http.util.EntityUtils;
- import org.springframework.stereotype.Component;
- import java.math.BigDecimal;
- import java.nio.charset.StandardCharsets;
- import java.util.List;
- /** Google Routes API 的服务端实现,复用既有地图字典中的 Key。 */
- @Component
- public class GoogleRoutesDistanceProvider implements RouteDistanceProvider {
- private static final String ENDPOINT = "https://routes.googleapis.com/directions/v2:computeRoutes";
- private static final RequestConfig TIMEOUTS = RequestConfig.custom()
- .setConnectTimeout(2500).setConnectionRequestTimeout(2500).setSocketTimeout(3500).build();
- /** 调用 Google Routes API 获取所选车型路线距离与时长;密钥缺失或响应异常时抛错,由上层降级。 */
- @Override
- public RouteDistance route(BigDecimal originLatitude, BigDecimal originLongitude,
- BigDecimal destinationLatitude, BigDecimal destinationLongitude,
- Integer vehicleType) {
- return route(List.of(new GeoPoint(originLatitude, originLongitude),
- new GeoPoint(destinationLatitude, destinationLongitude)), vehicleType);
- }
- /** 一次请求完整路线,按原始顺序传中途点;不调用路线优化接口。 */
- @Override
- public RouteDistance route(List<GeoPoint> points, Integer vehicleType) {
- JSONObject body = requestBody(points, vehicleType);
- // 闪送与外卖共用既有地图 Key,避免维护第二套字典配置。
- List<SysDictData> keys = DictUtils.getDictCache(mapKeyDictType());
- if (keys == null || keys.isEmpty() || keys.get(0).getDictValue() == null
- || keys.get(0).getDictValue().isBlank()) {
- throw new IllegalStateException("google routes key unavailable");
- }
- HttpPost request = new HttpPost(ENDPOINT);
- request.setConfig(TIMEOUTS);
- request.setHeader("X-Goog-Api-Key", keys.get(0).getDictValue());
- // 只请求计价需要的字段,减少响应体及不必要的数据暴露。
- request.setHeader("X-Goog-FieldMask", "routes.distanceMeters,routes.duration");
- request.setEntity(new StringEntity(body.toJSONString(), ContentType.APPLICATION_JSON));
- try (CloseableHttpClient client = HttpClients.custom().disableAutomaticRetries().build();
- CloseableHttpResponse response = client.execute(request)) {
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode < 200 || statusCode >= 300) {
- // 仅保留 HTTP 状态,绝不记录 Key、地址或完整 Google 响应内容。
- throw new IllegalStateException("google routes http status " + statusCode);
- }
- JSONObject json = JSON.parseObject(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8));
- JSONArray routes = json.getJSONArray("routes");
- if (routes == null || routes.isEmpty()) {
- throw new IllegalStateException("google routes empty response");
- }
- JSONObject first = routes.getJSONObject(0);
- int meters = first.getIntValue("distanceMeters");
- if (meters <= 0) {
- throw new IllegalStateException("google routes invalid distance");
- }
- return new RouteDistance(meters, parseDuration(first.getString("duration")), "ROUTE");
- } catch (IllegalStateException exception) {
- throw exception;
- } catch (Exception exception) {
- throw new IllegalStateException("google routes request failed", exception);
- }
- }
- /** 两点和多点共用的地图请求体,保留重复坐标对应的独立站点。 */
- JSONObject requestBody(List<GeoPoint> points, Integer vehicleType) {
- FlashDeliveryRouteService.validatePoints(points);
- JSONObject body = new JSONObject();
- GeoPoint origin = points.get(0);
- GeoPoint destination = points.get(points.size() - 1);
- body.put("origin", waypoint(origin.latitude(), origin.longitude()));
- body.put("destination", waypoint(destination.latitude(), destination.longitude()));
- if (points.size() > 2) {
- JSONArray intermediates = new JSONArray();
- for (GeoPoint point : points.subList(1, points.size() - 1)) {
- intermediates.add(waypoint(point.latitude(), point.longitude()));
- }
- body.put("intermediates", intermediates);
- }
- body.put("optimizeWaypointOrder", false);
- body.put("travelMode", travelModeFor(vehicleType));
- body.put("routingPreference", "TRAFFIC_AWARE");
- body.put("computeAlternativeRoutes", false);
- body.put("units", "METRIC");
- return body;
- }
- /** 保留测试和旧调用的默认机车车型。 */
- JSONObject requestBody(List<GeoPoint> points) {
- return requestBody(points, FlashDeliveryVehicleType.MOTORCYCLE);
- }
- private JSONObject waypoint(BigDecimal latitude, BigDecimal longitude) {
- JSONObject latLng = new JSONObject();
- latLng.put("latitude", latitude);
- latLng.put("longitude", longitude);
- JSONObject location = new JSONObject();
- location.put("latLng", latLng);
- JSONObject waypoint = new JSONObject();
- waypoint.put("location", location);
- return waypoint;
- }
- /** Google TWO_WHEELER 对应机车;无效或历史空值安全按机车回落。 */
- static String travelModeFor(Integer vehicleType) {
- return Integer.valueOf(FlashDeliveryVehicleType.CAR).equals(vehicleType)
- ? "DRIVE" : "TWO_WHEELER";
- }
- /** 闪送与外卖共用的地图 Key 字典类型。 */
- static String mapKeyDictType() {
- return "sys_googlemap_key";
- }
- private Integer parseDuration(String value) {
- if (value == null || !value.endsWith("s")) return null;
- try {
- return new BigDecimal(value.substring(0, value.length() - 1)).intValue();
- } catch (NumberFormatException exception) {
- return null;
- }
- }
- }
|