puzhibing
2026-05-11 01fe113711578471cc11a43fac6a3f5e630759fc
合并线上代码
2个文件已添加
8个文件已修改
418 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/BluetoothDeliverVo.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/BluetoothDebugConfig.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/PropertiesAutoConfiguration.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaskOrderCancelUtil.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/TrajectoryDensifier.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/BluetoothDeliverVo.java
@@ -6,9 +6,9 @@
@Data
public class BluetoothDeliverVo {
    /**
     * VIM码
     * VIN码
     */
    private String vim;
    private String vin;
    /**
     * 乘客用户 ID
     */
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java
@@ -54,6 +54,20 @@
@Api(tags = "订单任务Api")
public class OrderMissionController {
    /**
     * 共享线程池,用于推送司机位置轨迹,避免每次请求创建新线程池导致资源泄漏
     */
    private static final ThreadPoolExecutor DRIVER_LOCATION_EXECUTOR = new ThreadPoolExecutor(
            2, 4, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(500),
            r -> {
                Thread t = new Thread(r, "driver-location-push");
                t.setDaemon(true);
                return t;
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Resource
    private XPUUtil xpuUtil;
    @Resource
@@ -108,7 +122,7 @@
                taskActionBuilder.setOrderId(dto.getLastOrderId());
                taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                taskActionBuilder.setActionType(dto.getTaskActionType());
                taskBuilder.setPreActions(0, taskActionBuilder);
                taskBuilder.addPreActions(taskActionBuilder.build());
            }
            taskListBuilder.addTasks(taskBuilder.build());
        }else if(dto.getOrderType().equals(com.xiaopeng.api.vo.OrderType.USER_ORDER)){
@@ -149,7 +163,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }else{
@@ -188,7 +202,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }
@@ -232,7 +246,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }else{
@@ -270,7 +284,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }
@@ -311,7 +325,7 @@
                taskActionBuilder.setOrderId(dto.getLastOrderId());
                taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                taskActionBuilder.setActionType(dto.getTaskActionType());
                taskBuilder.setPreActions(0, taskActionBuilder);
                taskBuilder.addPreActions(taskActionBuilder.build());
            }
            taskListBuilder.addTasks(taskBuilder.build());
        }
@@ -457,21 +471,40 @@
     */
    @GetMapping("/base/v1/order/acceptingOrder")
    public R acceptingOrder(Long orderId, String code, Long vehicleId, Double startLon, Double startLat){
        log.info("【模拟接单】开始,orderId={}, code={}, vehicleId={}, startLon={}, startLat={}", orderId, code, vehicleId, startLon, startLat);
        if(null == vehicleId){
            vehicleId = 11L;//默认使用造的测试车辆
            log.info("【模拟接单】vehicleId为空,使用默认测试车辆 vehicleId={}", vehicleId);
        }
        if(null == startLat || null == startLon){
            startLat = 23.146815D;
            startLon = 113.459765D;
            log.info("【模拟接单】起点坐标为空,使用默认坐标 startLon={}, startLat={}", startLon, startLat);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        log.info("【模拟接单】查询车辆信息,vehicleId={}, vin={}", vehicleId, xpVehicle != null ? xpVehicle.getVehicleVin() : "null");
        XpOrder xpOrder = null;
        if(null != orderId){
            xpOrder = xpOrderClient.queryOrderById(orderId).getData();
            log.info("【模拟接单】按orderId查询订单,orderId={}, orderNo={}", orderId, xpOrder != null ? xpOrder.getOrderNumber() : "null");
        }
        if(StringUtils.isNotEmpty(code)){
            xpOrder = xpOrderClient.queryOrderByOrderNo(code).getData();
            log.info("【模拟接单】按orderNo查询订单,code={}, orderId={}", code, xpOrder != null ? xpOrder.getId() : "null");
        }
        orderId = xpOrder.getId();
        log.info("【模拟接单】订单信息,orderId={}, appUserId={}, startLon={}, startLat={}", orderId, xpOrder.getAppUserId(), xpOrder.getStartLon(), xpOrder.getStartLat());
        OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
        dto.setOrderId(orderId);
        LonLatDto origin = new LonLatDto();
        origin.setLongitude(startLon);
        origin.setLatitude(startLat);
        dto.setOrigin(origin);
        //保存车辆实时位置到redis
        redisTemplate.opsForGeo().add("vehiclePosition:" + xpVehicle.getVehicleVin(), new Point(startLon, startLat), xpVehicle.getVehicleVin());
        log.info("【模拟接单】保存车辆位置到Redis,vin={}, lon={}, lat={}", xpVehicle.getVehicleVin(), startLon, startLat);
        //保存车辆实时信息到mongodb
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
@@ -507,32 +540,31 @@
            mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), update, VehiclePosition.class);
        }
        orderId = xpOrder.getId();
        OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
        dto.setOrderId(orderId);
        LonLatDto origin = new LonLatDto();
        origin.setLongitude(startLon);
        origin.setLatitude(startLat);
        dto.setOrigin(origin);
        LonLatDto destination = new LonLatDto();
        destination.setLongitude(xpOrder.getStartLon());
        destination.setLatitude(xpOrder.getStartLat());
        dto.setDestination(destination);
        dto.setRouteType(1);
        dto.setVehicleId(xpOrder.getVehicleId());
        log.info("【模拟接单】请求规划接驾路线,origin=[{},{}], destination=[{},{}]", startLon, startLat, xpOrder.getStartLon(), xpOrder.getStartLat());
        R<RouteVo> routeVoR = xpMapClient.getOrderDistanceAndDuration(dto);
        if (200 != routeVoR.getCode()) {
            log.error("【模拟接单】获取订单线路和运费失败,orderId={}, code={}, msg={}", orderId, routeVoR.getCode(), routeVoR.getMsg());
            throw new RuntimeException("获取订单线路和运费失败");
        }
        RouteVo routeVo = routeVoR.getData();
        log.info("【模拟接单】获取接驾路线成功,orderId={}, distance={}, duration={}, polylineSize={}", orderId, routeVo.getDistance(), routeVo.getDuration(), routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        //缓存数据,用于后续推给乘客导航线路
        redisService.setCacheObject("ORDER_NAVIGATION_START_DATA:" + xpOrder.getAppUserId(), JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS);
        log.info("【模拟接单】缓存接驾导航数据成功,key=ORDER_NAVIGATION_START_DATA:{}", xpOrder.getAppUserId());
        PopOrderDto dto1 = new PopOrderDto();
        dto1.setOrderId(Long.valueOf(orderId));
        dto1.setXpVehicleVin(xpVehicle.getVehicleVin());
        dto1.setOrderType(com.xiaopeng.api.vo.OrderType.USER_ORDER);
        log.info("【模拟接单】调用popOrder,orderId={}, vin={}", orderId, xpVehicle.getVehicleVin());
        R<?> r = xpOrderClient.popOrder(dto1);
        log.info("【模拟接单】popOrder结果,orderId={}, code={}", orderId, r.getCode());
        //构建推送消息
        Map<String, Object> params = new HashMap<>();
@@ -547,11 +579,14 @@
        SendMessageDTO dto2 = new SendMessageDTO();
        dto2.setAppUserId(appUserId + "");
        dto2.setMessage(message);
        log.info("【模拟接单】推送订单状态(status=3)给用户,appUserId={}, orderId={}", appUserId, orderId);
        R<String> stringR = xpPushMessageClient.sendStatus(dto2);
        if (R.isError(stringR)) {
            log.error("推送消息失败:{}", stringR.getMsg());
            log.error("【模拟接单】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, orderId, stringR.getMsg());
        }
        log.info("【模拟接单】更新订单状态为PICK_UP,orderId={}", orderId);
        xpOrderClient.updateOrderStatus(orderId, OrderStatusEnum.PICK_UP.getCode(), false);
        log.info("【模拟接单】流程完成,orderId={}", orderId);
//        PushOrderRouteDTO dto3 = new PushOrderRouteDTO();
//        dto3.setOrderId(orderId);
@@ -573,8 +608,10 @@
     */
    @GetMapping("/base/v1/order/pushStartDriverLocation")
    private void pushStartDriverLocation(Long vehicleId, Long orderId, String code){
        log.info("【推送接驾轨迹】开始,vehicleId={}, orderId={}, code={}", vehicleId, orderId, code);
        if(null == vehicleId){
            vehicleId = 1L;
            log.info("【推送接驾轨迹】vehicleId为空,使用默认 vehicleId={}", vehicleId);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        XpOrder xpOrder = null;
@@ -587,31 +624,29 @@
        orderId = xpOrder.getId();
        String vin = xpVehicle.getVehicleVin();
        Long appUserId = xpOrder.getAppUserId();
        log.info("【推送接驾轨迹】订单信息,orderId={}, orderNo={}, vin={}, appUserId={}", orderId, xpOrder.getOrderNumber(), vin, appUserId);
        Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_START_DATA:" + xpOrder.getAppUserId());
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
//        RouteVo routeVo =new RouteVo();
//        ArrayList<LonLatDto> lonLatDtos = new ArrayList<>();
//        LonLatDto lonLatDto1 = new LonLatDto();
//        lonLatDto1.setLatitude(30.575685);
//        lonLatDto1.setLongitude(104.001118);
//        lonLatDtos.add(lonLatDto1);
//        routeVo.setPolyline(lonLatDtos);
        log.info("【推送接驾轨迹】获取接驾导航缓存数据,orderId={}, polylineSize={}", orderId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1000));
        Long finalOrderId = orderId;
        Long finalOrderId1 = orderId;
        threadPoolExecutor.execute(() -> {
            //等待10秒后开始执行车子位移
        DRIVER_LOCATION_EXECUTOR.execute(() -> {
            //等待1秒后开始执行车子位移
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
                Thread.currentThread().interrupt();
                log.error("pushStartDriverLocation 线程被中断", e);
                return;
            }
            List<LonLatDto> polyline = routeVo.getPolyline();
            log.info("【推送接驾轨迹】开始逐点推送位置,orderId={}, 总点数={}", finalOrderId, polyline.size());
            int idx = 0;
            for (LonLatDto lonLatDto : polyline) {
                Double latitude = lonLatDto.getLatitude();
                Double longitude = lonLatDto.getLongitude();
                log.info("【推送接驾轨迹】推送第{}/{}个点,orderId={}, lon={}, lat={}", ++idx, polyline.size(), finalOrderId, longitude, latitude);
                //保存车辆实时位置到redis
                redisTemplate.opsForGeo().add("vehiclePosition:" + vin, new Point(longitude, latitude), vin);
@@ -629,6 +664,8 @@
                    vehiclePosition.setMileageRange(300f);
                    vehiclePosition.setHasOrder(0);
                    vehiclePosition.setOrderId(finalOrderId1);
                    vehiclePosition.setTotalOdometer(10000L);
                    vehiclePosition.setAddTime(System.currentTimeMillis());
                    mongoTemplate.save(vehiclePosition);
                }else{
                    vehiclePosition.setOrderId(finalOrderId1);
@@ -640,7 +677,9 @@
                            .set("currentElectricity", 80)
                            .set("mileageRange", 300f)
                            .set("hasOrder", 0)
                            .set("orderId", finalOrderId1);
                            .set("orderId", finalOrderId1)
                            .set("totalOdometer", 10000L)
                            .set("addTime", System.currentTimeMillis());;
                    mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(vin)), update, VehiclePosition.class);
                }
                //调用推送服务接口
@@ -652,7 +691,6 @@
                    dto.setLatitude(latitude);
                    dto.setDuration(10.0);
                    dto.setDistance(2.0);
                    dto.setSpeed(2.0f);
                    dto.setDirection(60);
                    dto.setMessageType(PushMessageTypeEnum.ORDER_ROUTE.getDesc());
                    xpPushMessageClient.pushOrderRoute(dto);
@@ -662,10 +700,13 @@
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                    Thread.currentThread().interrupt();
                    log.error("pushStartDriverLocation 循环线程被中断", e);
                    return;
                }
            }
            log.info("【推送接驾轨迹】所有点推送完毕,推送status=4给用户,orderId={}, appUserId={}", finalOrderId, appUserId);
            //构建推送消息
            Map<String, Object> params = new HashMap<>();
            Map<String, Object> content = new HashMap<>();
@@ -680,9 +721,11 @@
            dto2.setMessage(message);
            R<String> stringR = xpPushMessageClient.sendStatus(dto2);
            if (R.isError(stringR)) {
                log.error("推送消息失败:{}", stringR.getMsg());
                log.error("【推送接驾轨迹】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, finalOrderId, stringR.getMsg());
            }
            log.info("【推送接驾轨迹】更新订单状态为PICK_UP_GUESTS,orderId={}", finalOrderId);
            xpOrderClient.updateOrderStatus(finalOrderId, OrderStatusEnum.PICK_UP_GUESTS.getCode(), false);
            log.info("【推送接驾轨迹】流程完成,orderId={}", finalOrderId);
        });
    }
@@ -696,6 +739,7 @@
     */
    @GetMapping("/base/v1/order/updateOrderStatus")
    public R updateOrderStatus(Long orderId, String code, Integer status) {
        log.info("【模拟修改订单状态】开始,orderId={}, code={}, status={}", orderId, code, status);
        XpOrder xpOrder = null;
        if(null != orderId){
            xpOrder = xpOrderClient.queryOrderById(orderId).getData();
@@ -704,6 +748,7 @@
            xpOrder = xpOrderClient.queryOrderByOrderNo(code).getData();
        }
        orderId = xpOrder.getId();
        log.info("【模拟修改订单状态】订单信息,orderId={}, orderNo={}, appUserId={}", orderId, xpOrder.getOrderNumber(), xpOrder.getAppUserId());
        //构建推送消息
        Map<String, Object> params = new HashMap<>();
        Map<String, Object> content = new HashMap<>();
@@ -716,15 +761,19 @@
        SendMessageDTO dto2 = new SendMessageDTO();
        dto2.setAppUserId(xpOrder.getAppUserId() + "");
        dto2.setMessage(message);
        log.info("【模拟修改订单状态】推送状态消息给用户,appUserId={}, orderId={}, status={}", xpOrder.getAppUserId(), orderId, status);
        R<String> stringR = xpPushMessageClient.sendStatus(dto2);
        if (R.isError(stringR)) {
            log.error("推送消息失败:{}", stringR.getMsg());
            log.error("【模拟修改订单状态】推送消息失败,appUserId={}, orderId={}, msg={}", xpOrder.getAppUserId(), orderId, stringR.getMsg());
        }
        log.info("【模拟修改订单状态】更新订单状态,orderId={}, status={}", orderId, status);
        xpOrderClient.updateOrderStatus(orderId, status, false);
        if(5 == status){
            log.info("【模拟修改订单状态】status=5,主动推送全程轨迹,orderId={}", orderId);
            //主动推一次全程轨迹
            Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId());
            RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
            log.info("【模拟修改订单状态】获取送驾导航缓存,orderId={}, polylineSize={}", orderId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("type", "ORDER_ROUTE");
            jsonObject.put("timestamp", System.currentTimeMillis());
@@ -749,21 +798,25 @@
            SendMessageDTO messageDTO = new SendMessageDTO();
            messageDTO.setAppUserId(xpOrder.getAppUserId().toString());
            messageDTO.setMessage(jsonObject.toJSONString());
            log.info("【模拟修改订单状态】推送全程轨迹,appUserId={}, orderId={}", xpOrder.getAppUserId(), orderId);
            xpPushMessageClient.sendRoute(messageDTO);
        }
        log.info("【模拟修改订单状态】完成,orderId={}, status={}", orderId, status);
        return R.ok();
    }
    /**
     * 推送司机接驾轨迹和实时位置
     * 推送司机送驾轨迹和实时位置
     * @param orderId
     */
    @GetMapping("/base/v1/order/pushEndDriverLocation")
    private void pushEndDriverLocation(Long vehicleId, Long orderId, String code){
        log.info("【推送送驾轨迹】开始,vehicleId={}, orderId={}, code={}", vehicleId, orderId, code);
        if(null == vehicleId){
            vehicleId = 1L;
            log.info("【推送送驾轨迹】vehicleId为空,使用默认 vehicleId={}", vehicleId);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        XpOrder xpOrder = null;
@@ -779,22 +832,27 @@
        Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId());
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
        log.info("开始实施推送车辆位置和剩余导航线路 {} {}", xpOrder.getOrderNumber(), JSON.toJSONString(routeVo));
        log.info("【推送送驾轨迹】获取送驾导航缓存,orderId={}, vin={}, appUserId={}, polylineSize={}", orderId, vin, appUserId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1000));
        Long finalOrderId = orderId;
        Long finalOrderId1 = orderId;
        threadPoolExecutor.execute(() -> {
            //等待10秒后开始执行车子位移
        DRIVER_LOCATION_EXECUTOR.execute(() -> {
            //等待1秒后开始执行车子位移
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
                Thread.currentThread().interrupt();
                log.error("pushEndDriverLocation 线程被中断", e);
                return;
            }
            List<LonLatDto> polyline = routeVo.getPolyline();
            log.info("【推送送驾轨迹】开始逐点推送位置,orderId={}, 总点数={}", finalOrderId, polyline.size());
            int idx = 0;
            for (LonLatDto lonLatDto : polyline) {
                Double latitude = lonLatDto.getLatitude();
                Double longitude = lonLatDto.getLongitude();
                log.info("【推送送驾轨迹】推送第{}/{}个点,orderId={}, lon={}, lat={}", ++idx, polyline.size(), finalOrderId, longitude, latitude);
                //保存车辆实时位置到redis
                redisTemplate.opsForGeo().add("vehiclePosition:" + vin, new Point(longitude, latitude), vin);
@@ -812,6 +870,8 @@
                    vehiclePosition.setMileageRange(300f);
                    vehiclePosition.setHasOrder(0);
                    vehiclePosition.setOrderId(finalOrderId1);
                    vehiclePosition.setTotalOdometer(10000L);
                    vehiclePosition.setAddTime(System.currentTimeMillis());
                    mongoTemplate.save(vehiclePosition);
                }else{
                    vehiclePosition.setOrderId(finalOrderId1);
@@ -823,7 +883,9 @@
                            .set("currentElectricity", 80)
                            .set("mileageRange", 300f)
                            .set("hasOrder", 0)
                            .set("orderId", finalOrderId1);
                            .set("orderId", finalOrderId1)
                            .set("totalOdometer", 10000L)
                            .set("addTime", System.currentTimeMillis());;
                    mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(vin)), update, VehiclePosition.class);
                }
                //调用推送服务接口
@@ -835,7 +897,6 @@
                    dto.setLatitude(latitude);
                    dto.setDuration(20.0);
                    dto.setDistance(10.0);
                    dto.setSpeed(10.0f);
                    dto.setDirection(60);
                    dto.setMessageType(PushMessageTypeEnum.ORDER_ROUTE.getDesc());
                    R<String> stringR = xpPushMessageClient.pushOrderRoute(dto);
@@ -851,14 +912,17 @@
                    xpOrderClient.saveVehicleLocation(orderTrajectory);
                }
                //5秒钟循环一次
                //1秒钟循环一次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                    Thread.currentThread().interrupt();
                    log.error("pushEndDriverLocation 循环线程被中断", e);
                    return;
                }
            }
            log.info("【推送送驾轨迹】所有点推送完毕,推送status=7给用户,orderId={}, appUserId={}", finalOrderId, appUserId);
            //构建推送消息
            Map<String, Object> params = new HashMap<>();
            Map<String, Object> content = new HashMap<>();
@@ -873,11 +937,12 @@
            dto2.setMessage(message);
            R<String> stringR = xpPushMessageClient.sendStatus(dto2);
            if (R.isError(stringR)) {
                log.error("推送消息失败:{}", stringR.getMsg());
                log.error("【推送送驾轨迹】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, finalOrderId, stringR.getMsg());
            }
            log.info("【推送送驾轨迹】更新订单状态为SEE_OFF_GUESTS,orderId={}", finalOrderId);
            xpOrderClient.updateOrderStatus(finalOrderId, OrderStatusEnum.SEE_OFF_GUESTS.getCode(), false);
            log.info("【推送送驾轨迹】流程完成,orderId={}", finalOrderId);
        });
    }
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -271,6 +271,7 @@
    }
    @Value("${map.urls.weather:https://restapi.amap.com/v3/weather/weatherInfo?key=%s&city=%s}")
    String WEATHER_URL;
@@ -300,4 +301,5 @@
        return R.fail("获取天气信息失败");
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/BluetoothDebugConfig.java
New file
@@ -0,0 +1,50 @@
package com.xiaopeng.order.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
 * 蓝牙开锁联调配置(临时)
 * <p>
 * 通过 Nacos 配置固定的蓝牙 token、macAddress、slaveMacList,用于联调测试。
 * <p>
 * 配置格式示例:
 * <pre>
 * bluetooth-debug:
 *   enabled: false
 *   token: "your-fixed-token"
 *   mac-address: "AA:BB:CC:DD:EE:FF"
 *   slave-mac-list:
 *     - "AA:BB:CC:DD:EE:01"
 *     - "AA:BB:CC:DD:EE:02"
 * </pre>
 */
@Slf4j
@Data
@ConfigurationProperties(prefix = "bluetooth-debug")
public class BluetoothDebugConfig {
    /**
     * 联调开关,默认 false(关闭)
     */
    private boolean enabled = false;
    /**
     * 固定的蓝牙 token
     */
    private String token;
    /**
     * 固定的 MAC 地址
     */
    private String macAddress;
    /**
     * 固定的从设备 MAC 地址列表
     */
    private List<String> slaveMacList;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/PropertiesAutoConfiguration.java
New file
@@ -0,0 +1,10 @@
package com.xiaopeng.order.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({BluetoothDebugConfig.class})
public class PropertiesAutoConfiguration {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -36,6 +36,7 @@
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.common.security.service.TokenService;
import com.xiaopeng.order.config.BluetoothDebugConfig;
import com.xiaopeng.order.dto.OrderNavigationDto;
import com.xiaopeng.api.model.order.OrderTrajectory;
import com.xiaopeng.order.model.dto.XpOrderCommentDto;
@@ -112,6 +113,10 @@
    @Resource
    private TokenService tokenService;
    @Autowired
    private BluetoothDebugConfig bluetoothDebugConfig;
    /**
@@ -707,6 +712,18 @@
    @GetMapping("/api/v1/mini-program/bluetooth/getUnlocktToken/{orderId}")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<BluetoothTokenVo> getBluetoothToken(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // ========== 联调临时逻辑:通过 Nacos 配置返回固定蓝牙信息(bluetooth-debug.enabled=true 时生效) ==========
        if (bluetoothDebugConfig.isEnabled()) {
            BluetoothTokenVo vo = new BluetoothTokenVo();
            vo.setToken(bluetoothDebugConfig.getToken());
            vo.setMacAddress(bluetoothDebugConfig.getMacAddress());
            vo.setSlaveMacList(bluetoothDebugConfig.getSlaveMacList());
            return R.ok(vo);
        }
        // ========== 联调临时逻辑结束 ==========
        // 原逻辑
        String uid = request.getHeader("XP-Uid");
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if (!Objects.equals(xpOrder.getAppUserId(), Long.valueOf(uid))) {
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -697,7 +697,8 @@
                    redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                    XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                    BluetoothDeliverVo vo = new BluetoothDeliverVo();
                    vo.setVim(xpVehicle.getVehicleVin());
                    vo.setCredential(token);
                    vo.setVin(xpVehicle.getVehicleVin());
                    vo.setPassengerUid(order.getAppUserId());
                    BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                    if (null == delete || delete.getCode() != 0) {
@@ -1899,7 +1900,8 @@
                redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setVim(xpVehicle.getVehicleVin());
                vo.setCredential(token);
                vo.setVin(xpVehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
                BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                if (null == delete || delete.getCode() != 0) {
@@ -1934,7 +1936,8 @@
                redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setVim(xpVehicle.getVehicleVin());
                vo.setCredential(token);
                vo.setVin(xpVehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
                BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                if (null == delete || delete.getCode() != 0) {
@@ -2342,19 +2345,20 @@
        order.setAppUserId(appUserId);
        bootCommonService.setCache_orderStatus(order.getId(), order, null);
        //删除蓝牙秘钥
//        String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId);
//        if(StringUtils.isNotBlank(token)){
//            redisService.deleteObject("rt:bluetooth:unlock:" + token);
//            redisService.deleteObject("rt:bluetooth:unlock:order:" + orderId);
//            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
//            BluetoothDeliverVo vo = new BluetoothDeliverVo();
//            vo.setVim(xpVehicle.getVehicleVin());
//            vo.setPassengerUid(order.getAppUserId());
//            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
//            if(null == delete || delete.getCode() != 0){
//                log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber());
//            }
//        }
        String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId);
        if(StringUtils.isNotBlank(token)){
            redisService.deleteObject("rt:bluetooth:unlock:" + token);
            redisService.deleteObject("rt:bluetooth:unlock:order:" + orderId);
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
            BluetoothDeliverVo vo = new BluetoothDeliverVo();
            vo.setCredential(token);
            vo.setVin(xpVehicle.getVehicleVin());
            vo.setPassengerUid(order.getAppUserId());
            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
            if(null == delete || delete.getCode() != 0){
                log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber());
            }
        }
        // 根据订单所属开通城市id 查询定价
        XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(), order.getVehicleId());
@@ -3737,17 +3741,17 @@
                //开始下发蓝牙token
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setCredential(token);
                vo.setVim(vehicle.getVehicleVin());
                vo.setVin(vehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
//                BaseVehicleUserDto deliver = bluetoothUnlockClient.deliver(vo);
//                if(null == deliver){
//                    throw new ServiceException("下发蓝牙token失败");
//                }
//                if(deliver.getCode() != 0){
//                    log.error("下发蓝牙token失败:{} {}", deliver.getMsg(), order.getOrderNumber());
//                    throw new ServiceException(deliver.getMsg());
//                }
//                log.info("下发蓝牙token成功:{} {}", deliver.getData(), order.getOrderNumber());
                BaseVehicleUserDto deliver = bluetoothUnlockClient.deliver(vo);
                if(null == deliver){
                    throw new ServiceException("下发蓝牙token失败");
                }
                if(deliver.getCode() != 0){
                    log.error("下发蓝牙token失败:{} {}", deliver.getMsg(), order.getOrderNumber());
                    throw new ServiceException(deliver.getMsg());
                }
                log.info("下发蓝牙token成功:{} {}", deliver.getData(), order.getOrderNumber());
            }
            if (order.getStatus() == 2) {
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaskOrderCancelUtil.java
@@ -84,7 +84,8 @@
            redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
            BluetoothDeliverVo vo = new BluetoothDeliverVo();
            vo.setVim(xpVehicle.getVehicleVin());
            vo.setCredential(token);
            vo.setVin(xpVehicle.getVehicleVin());
            vo.setPassengerUid(order.getAppUserId());
            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
            if(null == delete || delete.getCode() != 0){
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
@@ -26,14 +26,44 @@
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Api(tags = "订单推送轨迹接口")
@RestController
@RequestMapping("")
public class PushOrderRouteController {
    /**
     * 推送轨迹重试线程池(类级别单例复用)
     * 修复:原代码在每次 sendRoute 失败时都 new ThreadPoolExecutor 且不 shutdown,
     * 导致核心线程在 LinkedBlockingQueue.take() 上永久 WAITING,造成线程泄漏。
     *
     * 参数推导(基于业务并发量估算):
     * - 车端上报频率 ~1次/秒/车,接口限频1.5秒,实际处理 ~0.67次/秒/车
     * - 常态(50车 + 5%失败率):~1.65 任务/秒,任务耗时~5秒,同时~8个任务
     * - 突发(100车 + 20%失败率):~13 任务/秒,同时~66个任务
     * - corePoolSize=4 覆盖常态,maxPoolSize=16 + 队列512 覆盖突发
     */
    private static final ThreadPoolExecutor RETRY_EXECUTOR = new ThreadPoolExecutor(
            4,
            16,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(512),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "route-push-retry-" + counter.incrementAndGet());
                    t.setDaemon(true);
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Autowired
    private RedisService redisService;
@@ -141,8 +171,7 @@
                R<String> stringR = xpPushMessageClient.sendRoute(sendMessageDTO);
                //推送失败,重试3次
                if(stringR.getCode() != Constants.SUCCESS){
                    ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
                    executor.execute(() -> {
                    RETRY_EXECUTOR.execute(() -> {
                        for (int i = 0; i < 3; i++) {
                            R<String> stringR1 = xpPushMessageClient.sendStatus(sendMessageDTO);
                            if(stringR1.getCode() == Constants.SUCCESS){
@@ -151,7 +180,8 @@
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                                Thread.currentThread().interrupt();
                                return;
                            }
                            log.info("推送乘客端剩余导航数据消息失败:{} {} {}", stringR1.getMsg(), JSON.toJSONString(sendMessageDTO), xpOrder.getOrderNumber());
                        }
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/TrajectoryDensifier.java
@@ -11,62 +11,95 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class TrajectoryDensifier {
    private static final int THREAD_NUM = 5;
    /**
     * 轨迹补全计算线程池(类级别单例复用)
     * 修复:原代码每次调用 densify() 都 new ThreadPoolExecutor 并在方法结束时 shutdown,
     * 频繁创建/销毁线程开销大,且异常路径(latch.await 被中断)会跳过 shutdown 导致线程泄漏。
     * 改为单例线程池复用,避免上述问题。
     */
    private static final ThreadPoolExecutor DENSIFY_EXECUTOR = new ThreadPoolExecutor(
            5, 5, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(256),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "trajectory-densify-" + counter.incrementAndGet());
                    t.setDaemon(true);
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    static {
        // 允许核心线程超时回收,空闲时不占用资源
        DENSIFY_EXECUTOR.allowCoreThreadTimeOut(true);
    }
    public static List<LonLatDto> densify(List<LonLatDto> originalPoints, int distance) {
        if (originalPoints == null || originalPoints.size() <= 1) {
            return new ArrayList<>(originalPoints);
        }
        int num = originalPoints.size() / THREAD_NUM;
        if(num == 0){
            num = 1;
        }
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(num, num, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        CountDownLatch latch = new CountDownLatch(num);
        Map<Integer, List<LonLatDto>> map = new HashMap<>();
        Map<Integer, List<LonLatDto>> map = new ConcurrentHashMap<>();
        for (int i = 0; i < num; i++) {
            List<LonLatDto> trackPoints = originalPoints.subList(i * THREAD_NUM, i == num - 1 ? originalPoints.size() : (i + 1) * THREAD_NUM);
            int finalI = i;
            poolExecutor.execute(()->{
                List<LonLatDto> new_polyline = new ArrayList<>();
                for (int j = 0; j < trackPoints.size(); j++) {
                    LonLatDto trackPoint = trackPoints.get(j);
                    if(j == trackPoints.size() - 1){
                        new_polyline.add(new LonLatDto(trackPoint.getLongitude(), trackPoint.getLatitude()));
                        break;
                    }
                    LonLatDto trackPoint1 = trackPoints.get(j+1);
            DENSIFY_EXECUTOR.execute(()->{
                try {
                    List<LonLatDto> new_polyline = new ArrayList<>();
                    for (int j = 0; j < trackPoints.size(); j++) {
                        LonLatDto trackPoint = trackPoints.get(j);
                        if(j == trackPoints.size() - 1){
                            new_polyline.add(new LonLatDto(trackPoint.getLongitude(), trackPoint.getLatitude()));
                            break;
                        }
                        LonLatDto trackPoint1 = trackPoints.get(j+1);
                    //对两个点位进行坐标补全
                    List<TrackPoint> track = new ArrayList<>();
                    track.add(new TrackPoint(trackPoint.getLatitude(), trackPoint.getLongitude(), null));
                    track.add(new TrackPoint(trackPoint1.getLatitude(), trackPoint1.getLongitude(), null));
                    //10米补全一个点位
                    List<TrackPoint> densified = densifyByDistance(track, distance);
                    for (TrackPoint point : densified) {
                        new_polyline.add(new LonLatDto(point.getLongitude(), point.getLatitude()));
                        //对两个点位进行坐标补全
                        List<TrackPoint> track = new ArrayList<>();
                        track.add(new TrackPoint(trackPoint.getLatitude(), trackPoint.getLongitude(), null));
                        track.add(new TrackPoint(trackPoint1.getLatitude(), trackPoint1.getLongitude(), null));
                        //10米补全一个点位
                        List<TrackPoint> densified = densifyByDistance(track, distance);
                        for (TrackPoint point : densified) {
                            new_polyline.add(new LonLatDto(point.getLongitude(), point.getLatitude()));
                        }
                    }
                    if (!new_polyline.isEmpty()) {
                        new_polyline.remove(new_polyline.size() - 1);
                    }
                    map.put(finalI, new_polyline);
                } finally {
                    latch.countDown();
                }
                new_polyline.remove(new_polyline.size() - 1);
                map.put(finalI, new_polyline);
                latch.countDown();
            });
        }
        try {
            latch.await();
            poolExecutor.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
        List<LonLatDto> result = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            result.addAll(map.get(i));
            List<LonLatDto> chunk = map.get(i);
            if (null != chunk) {
                result.addAll(chunk);
            }
        }
        //添加最后一个点
        result.add(originalPoints.get(originalPoints.size() - 1));