liujie
2026-03-15 d943874d94e69806cb53b649b378075794bdf683
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi
11个文件已添加
36个文件已修改
1926 ■■■■■ 已修改文件
sql/增量sql/xprt_account.sql 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/增量sql/xprt_maintenance.sql 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/增量sql/xprt_operation.sql 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpChargingRecord.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpVehicleRepair.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/config/JacksonLocalTimeConfig.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleRepairController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/AbstractJob.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/QuartzManager.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/TimeJobType.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/exceptions/TimeException.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/ElectronicEffectJob.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/OrderCancelJob.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/utils/SpringContextsUtil.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/OperationRecordsVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpChargingRecordVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleCleanVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleDetectionVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehiclePreparationVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleRepairVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskNotificationSupport.java 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskTimeoutCheckScheduleTask.java 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskWillTimeoutScheduleTask.java 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/ClockInScheduleTask.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/XpTaskService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpVehiclePreparationServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpVehicleRepairServiceImpl.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_en_US.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_zh_CN.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/base/AbstractJob.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/AddCruiseMissionJob.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/EndTripJob.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/XpVehicleVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/增量sql/xprt_account.sql
@@ -0,0 +1 @@
ALTER TABLE `xp_operations_user` MODIFY COLUMN `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态 1=启用 0=禁用' AFTER `remarks`
sql/增量sql/xprt_maintenance.sql
@@ -0,0 +1,39 @@
ALTER TABLE `xp_task_0` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_1` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_2` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_3` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_4` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_5` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_6` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_7` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_8` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_9` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_10` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_11` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_12` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_13` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_14` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_15` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_16` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_17` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_18` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
ALTER TABLE `xp_task_19` ADD COLUMN `will_timeout_notice_sent` tinyint NOT NULL DEFAULT 0 COMMENT '事故即将超时通知是否已发送(0否1是)' AFTER `update_by`;
sql/增量sql/xprt_operation.sql
@@ -1,7 +0,0 @@
ALTER TABLE `xp_banner` ADD COLUMN `sort` int NULL DEFAULT NULL COMMENT '排序' AFTER `banner_status`;
ALTER TABLE `xp_cruise_plan_vehicle` ADD COLUMN `id` bigint NOT NULL COMMENT '自增id' FIRST;
ALTER TABLE `xp_cruise_plan_vehicle` ADD PRIMARY KEY (`id`) USING BTREE;
ALTER TABLE `xp_cruise_plan_vehicle` MODIFY COLUMN `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id';
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpChargingRecord.java
@@ -48,7 +48,7 @@
    private String startChargingPicture;
    @ApiModelProperty(value = "开始充电时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime startTime;
    @ApiModelProperty(value = "结束剩余电量")
@@ -59,7 +59,7 @@
    private String endChargingPicture;
    @ApiModelProperty(value = "结束充电时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime endTime;
    @ApiModelProperty(value = "开始充电操作人")
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java
@@ -12,9 +12,6 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * 任务管理
@@ -91,6 +88,9 @@
    @ApiModelProperty(value = "是否已发送过期通知 0=未发送 1=已发送")
    private Integer expiredNoticeSent;
    @ApiModelProperty(value = "事故即将超时通知是否已发送 0=未发送 1=已发送")
    private Integer willTimeoutNoticeSent;
    @ApiModelProperty(value = "状态 0=待到达 1=待处理 2=已完成 3=已取消")
    private Integer status;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpVehicleRepair.java
@@ -43,11 +43,11 @@
    private Long vehicleId;
    @ApiModelProperty(value = "开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日", timezone = "GMT+8")
    private LocalDate startTime;
    @ApiModelProperty(value = "结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日", timezone = "GMT+8")
    private LocalDate endTime;
    @ApiModelProperty(value = "维修原因")
@@ -66,14 +66,14 @@
    private Long sendUserId;
    @ApiModelProperty(value = "送修时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime sendTime;
    @ApiModelProperty(value = "取回人")
    private Long returnUserId;
    @ApiModelProperty(value = "取回时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime returnTime;
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -793,8 +793,10 @@
                    break;
                case ORDER_RECEIVED:
                    //收到订单
                    buildPushOrderStatus(orderId, OrderStatusEnum.READY_DEPART, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.READY_DEPART.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.READY_DEPART, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.READY_DEPART.getCode());
                    }
                    break;
                case ORDER_STARTED:
                    //开始执行订单
@@ -802,8 +804,10 @@
                    break;
                case ORDER_EN_ROUTE_TO_PICKUP:
                    //前往上车点
                    buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.PICK_UP.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.PICK_UP.getCode());
                    }
                    break;
                case ORDER_ARRIVED_AT_PICKUP:
                    //到达上车点
@@ -811,49 +815,41 @@
                    break;
                case ORDER_WAITING_FOR_PICKUP:
                    //等待乘客上车
                    buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP_GUESTS, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.PICK_UP_GUESTS.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP_GUESTS, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.PICK_UP_GUESTS.getCode());
                    }
                    break;
                case ORDER_PASSENGER_ONBOARD:
                    //乘客已上车
                    buildPushOrderStatus(orderId, OrderStatusEnum.PASSENGER_BOARDED, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.PASSENGER_BOARDED.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.PASSENGER_BOARDED, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.PASSENGER_BOARDED.getCode());
                    }
                    break;
                case ORDER_EN_ROUTE_TO_DESTINATION:
                    //前往目的地
                    buildPushOrderStatus(orderId, OrderStatusEnum.IN_SERVICE, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.IN_SERVICE.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.IN_SERVICE, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.IN_SERVICE.getCode());
                    }
                    break;
                case ORDER_ARRIVED_AT_DESTINATION:
                    //到达目的地
                    log.info("到达目的地,不做任何处理:{}", orderId);
                    break;
                case ORDER_WAITING_FOR_DROPOFF:
                    //等待乘客下车
                    buildPushOrderStatus(orderId, OrderStatusEnum.SEE_OFF_GUESTS, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.SEE_OFF_GUESTS.getCode());
                    //等待乘客下车,开始倒计时发下巡游单
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.SEE_OFF_GUESTS, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.SEE_OFF_GUESTS.getCode());
                    }
                    break;
                case ORDER_DROPOFF_COMPLETED:
                    //下车完成
                    buildPushOrderStatus(orderId, OrderStatusEnum.APPROVAL_REJECTION, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.APPROVAL_REJECTION.getCode());
                    // 下发巡游任务
                    R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId));
                    if(xpOrderR.getCode()==200){
                        XpOrder orderData = xpOrderR.getData();
                        Long vehicleId =orderData.getVehicleId();
                        R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(vehicleId);
                        if(xpVehicleR.getData()!=null){
                            XpVehicle xpVehicle = xpVehicleR.getData();
                            //订单结束,在添加导航数据之前先将车辆状态修改为空闲
                            xpVehicleClient.updateVehicleStatus(new UpdateVehicleStateDto(xpVehicle.getVehicleVin(), 0));
                            xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
                            AddCruiseMissionDto addCruiseMissionDto = new AddCruiseMissionDto();
                            addCruiseMissionDto.setOrder(orderData);
                            addCruiseMissionDto.setVehicle(xpVehicle);
                            xpCruiseOrderClient.addCruiseMission(addCruiseMissionDto);
                        }
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.APPROVAL_REJECTION, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.APPROVAL_REJECTION.getCode());
                    }
                    break;
                case ORDER_COMPLETED:
@@ -862,12 +858,16 @@
                    break;
                case ORDER_CANCELLED:
                    //已取消
                    buildPushOrderStatus(orderId, OrderStatusEnum.CANCELED, sequence);
                    xpOrderClient.updateOrderStatus(Long.parseLong(orderId.contains("XU") ? orderId.substring(2) : orderId), OrderStatusEnum.CANCELED.getCode());
                    if(!orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.CANCELED, sequence);
                        xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.CANCELED.getCode());
                    }
                    break;
                case ORDER_ARRIVED_AT_CRUISE_DESTINATION:
                    //到达巡游终点
                    buildPushOrderStatus(orderId, OrderStatusEnum.COMPLETED, sequence);
                    if(orderId.contains("XU")){
                        buildPushOrderStatus(orderId, OrderStatusEnum.COMPLETED, sequence);
                    }
                    break;
                case ORDER_PAUSED:
                    //订单暂停
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -217,7 +217,7 @@
        }
//        // 算距离
        // 算距离
        R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        if (distanceAndDuration.getCode() != 200) {
            throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND);
@@ -316,35 +316,7 @@
        // 8. 构建响应对象
        RouteAndFareVo routeAndFareVo = new RouteAndFareVo();
        // MOCK数据
//        RouteVo routeVo = new RouteVo();
//        routeVo.setDistance(3000);
//        routeVo.setDuration(5);
//        routeVo.setRouteID("1");
//        List<LonLatDto> routePolyline = Arrays.asList(
//                // 起点:天府新谷
//                new LonLatDto(104.05363, 30.58937),
//
//                // 途经点1:府城大道与交子北二路交叉口附近
//                new LonLatDto(104.05821, 30.58745),
//
//                // 途经点2:交子大道与成汉南路交叉口
//                new LonLatDto(104.06418, 30.58492),
//
//                // 途经点3:交子大道与万象南路交叉口
//                new LonLatDto(104.06975, 30.58321),
//
//                // 途经点4:交子大道与益州大道交叉口
//                new LonLatDto(104.07366, 30.58198),
//
//                // 终点:金融城(地铁站附近)
//                new LonLatDto(104.076544, 30.581039)
//        );
//        routeVo.setPolyline(routePolyline);
        routeAndFareVo.setRoute(routeVo);
        routeAndFareVo.setXpPricingCard(xpPricingCardVo);
        FareVo fareVo = new FareVo();
@@ -353,7 +325,7 @@
        // 9. 设置费用信息
        fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice());
        fareVo.setOriginalFareStr(fareVo.getOriginalFare() + "");
        fareVo.setOriginalFareStr(new BigDecimal(fareVo.getOriginalFare()).setScale(2, RoundingMode.HALF_UP).toString());
        if (userCouponVo != null) {
            if(userCouponVo.getMoney()>fareVo.getOriginalFare()){
                fareVo.setDiscountAmount(fareVo.getOriginalFare());
@@ -399,8 +371,6 @@
        redisService.setCacheObject(key, voJsonStr, 5l, TimeUnit.MINUTES);
        //缓存导航数据,用于下单后推送导航给车端和乘客端
        redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + userId, JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS);
        return routeAndFareVo;
    }
@@ -568,18 +538,6 @@
        xpOrder.setCreateTime(LocalDateTime.now());
        // 计算距离 和 时间
        RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
        LonLatDto lonLatDto = new LonLatDto();
        lonLatDto.setLongitude(xpPoint.getPointLon());
        lonLatDto.setLatitude(xpPoint.getPointLat());
        routeAndFareDto.setOrigin(lonLatDto);
        LonLatDto lonLatDto1 = new LonLatDto();
        lonLatDto1.setLongitude(endXpPoint.getPointLon());
        lonLatDto1.setLatitude(endXpPoint.getPointLat());
        routeAndFareDto.setDestination(lonLatDto1);
        XpOpenCity openCities = xpOpenCityMapper.selectById(dto.getOpenCityId());
        if (openCities == null) {
            throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND);
@@ -588,18 +546,15 @@
        dto.setOpenCityId(xpOpenCity.getId());
        xpOrder.setCityCode(xpOpenCity.getCityCode());
        XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()));
        if (xpRoutePlan != null) {
            routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
        }
        R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        Integer distance = 0;
        Integer duration = 0;
        if (distanceAndDuration.getCode() != 200) {
//            throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
        Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + uid);
        if (cacheObject != null) {
            RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
            distance = routeVo.getDistance();
            duration = routeVo.getDuration();
        }
//        distance = distanceAndDuration.getData().getDistance();
//        duration = distanceAndDuration.getData().getDuration();
        // 产生订单
        R<Long> addOrderR = xpOrderClient.addOrder(xpOrder);
@@ -632,7 +587,7 @@
            // 和上车点进行判断是否<maxPushDistance
            double v = FastDistanceCalculator.distanceFast(Double.parseDouble(lonLat.split(",")[1]), Double.parseDouble(lonLat.split(",")[0]), xpPoint.getPointLat(), xpPoint.getPointLon());
            if (v < maxPushDistance) {
                String value = orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule();
                String value = orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + xpRoutePlan.getRoutePlanRule();
                if (isPriorityDispatch) {
                    // 头部  value 订单_经纬度_状态_用户id_订单编号_距离_时间
                    redisService.addToHead(s, value);
@@ -660,7 +615,7 @@
        }
        String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat();
        redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule()));
        redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + xpRoutePlan.getRoutePlanRule()));
        log.info("订单推单:{}   {}", key, redisService.getCacheList(key));
        xpOrder.setQueueName(key);
        xpOrderClient.updateOrder(xpOrder);
@@ -676,17 +631,6 @@
        xpGeoQueryEntity.setTimestamp(System.currentTimeMillis());
        geoQueryEntityService.save(xpGeoQueryEntity);
        return orderId.toString();
//
//        XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId());
//        // 获取电子围栏的车辆
//        R<List<XpVehicle>> vehicles = xpVehicleClient.listByFenceId(xpElectronicFenceArea.getElectronicId());
//
        // 产生订单  存订单数据  // 下发指令  消息推送用户端
//        return null;
    }
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java
@@ -137,7 +137,7 @@
        xpPricingCardVo.setStartPrice(startPrice);
        xpPricingCardVo.setDistancePrice(distancePrice);
        xpPricingCardVo.setDurationPrice(durationPrice);
        xpPricingCardVo.setTotalPrice(startPrice + distancePrice + durationPrice);
        xpPricingCardVo.setTotalPrice(new BigDecimal(startPrice + distancePrice + durationPrice).setScale(2,RoundingMode.HALF_UP).doubleValue());
        // 里程
        xpPricingCardVo.setDistance(realDistance);
        xpPricingCardVo.setDuration(realTime);
@@ -504,7 +504,7 @@
        // 动态计算费用
        startPrice= new BigDecimal(startPrice).multiply(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
        amount = amount+startPrice;
        xpPricingCardVo.setTotalPrice(amount);
        xpPricingCardVo.setTotalPrice(new BigDecimal(amount).setScale(2,RoundingMode.HALF_UP).doubleValue());
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/config/JacksonLocalTimeConfig.java
New file
@@ -0,0 +1,41 @@
package com.xiaopeng.maintenance.config;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
/**
 * Jackson LocalTime 序列化/反序列化配置
 *
 * <p>兼容前端以 HH:mm(无秒)传参的场景,同时支持 HH:mm:ss(带秒)。</p>
 */
@Configuration
public class JacksonLocalTimeConfig {
    /**
     * LocalTime 反序列化:兼容 HH:mm / HH:mm:ss
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer localTimeDeserializerCustomizer() {
        DateTimeFormatter parseFormatter = new DateTimeFormatterBuilder()
                .appendPattern("HH:mm")
                .optionalStart()
                .appendPattern(":ss")
                .optionalEnd()
                .toFormatter();
        DateTimeFormatter writeFormatter = DateTimeFormatter.ofPattern("HH:mm");
        return builder -> {
            builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(parseFormatter));
            builder.serializerByType(LocalTime.class, new LocalTimeSerializer(writeFormatter));
        };
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java
@@ -25,6 +25,7 @@
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -61,6 +62,7 @@
    /**
     * 车辆详情-车辆调度-发车 收车
     */
    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "车辆运维-车辆调度-发车/收车", tags = {"运维APP-车辆管理"})
    @PostMapping("/api/v1/vehicle/vehicleDispatchDeparture")
    public R<String> vehicleDispatchDeparture(@RequestBody VehicleDispatchDepartureDTO dto) {
@@ -71,13 +73,12 @@
        if(xpVehicle.getState()==1){
            return R.fail("车辆正在接单中");
        }
        xpVehicle.setOperatingStatus(dto.getDepartureStatus());
        xpVehicleClient.updateVehicleById(xpVehicle);
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
        if(vehiclePosition==null || vehiclePosition.getPoint()==null){
            return R.fail("车辆位置信息不存在");
        }
        xpVehicle.setOperatingStatus(dto.getDepartureStatus());
        xpVehicleClient.updateVehicleById(xpVehicle);
        // 发车
        if(dto.getDepartureStatus()==1){
            // 找车辆的任务  生成巡游单子 下发命令
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleRepairController.java
@@ -58,8 +58,8 @@
    @PostMapping("/api/v1/vehicleRepair/saveReturnRepair")
    public R<String> saveReturnRepair(@Validated @RequestBody XpVehicleRepairReturnDTO dto) {
        Long id = tokenService.getLoginOperationUser().getId();
        if (dto.getRepairMoney().toString().length() > 200) {
            throw new ServiceException("维修费用不能超过200字符", I18nKeyConstant.REPAIR_MONEY_TOO_LONG);
        if (dto.getRepairMoney()>99999999.99) {
            throw new ServiceException("维修费用不能超过10字符", I18nKeyConstant.REPAIR_MONEY_TOO_LONG);
        }
        xpVehicleRepairService.saveReturnRepair(dto,id);
        return R.ok();
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java
@@ -9,7 +9,6 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.time.LocalTime;
import java.util.List;
@Data
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/AbstractJob.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.maintenance.job.base;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.maintenance.job.utils.SpringContextsUtil;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractJob implements Job{
    protected Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public abstract void execute(JobExecutionContext context) throws JobExecutionException;
    protected XpPushMessageClient xpPushMessageClient;
    public AbstractJob(){
        this.xpPushMessageClient = SpringContextsUtil.getBean(XpPushMessageClient.class);
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/QuartzManager.java
New file
@@ -0,0 +1,134 @@
package com.xiaopeng.maintenance.job.base;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
import java.util.Map;
public class QuartzManager {
    private static SchedulerFactory factory = new StdSchedulerFactory();
    private static final String TRIGGER_NAME_PREFIX = "TRIGGER_PREFIX_";
    private static final String JOB_NAME_PREFIX = "JOB_PREFIX_";
    /**
     * 添加定时任务:具体某个时间点执行一次的任务,如:在某个2015-06-01 12:00发送一条消息
     *
     * @param jobName
     *            具体的任务名+ID标识唯一
     * @param jobType
     * @param date
     * @param jp
     */
    public synchronized static void addJob(Class<? extends Job> jobClass, String jobName, TimeJobType jobType, Date date,
                                           Map<String,  ? extends Object> jp) {
        //logger.debug("ADD JOB {},jobName={},jobTyep={},jobDate={},",jobClass.getName(),jobName,jobType,date);
        try {
            Scheduler sched = factory.getScheduler();
            JobDetail job = JobBuilder.newJob(jobClass).withIdentity(JOB_NAME_PREFIX + jobName, jobType.getType())
                    .setJobData(new JobDataMap(jp)).build();
            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withRepeatCount(0)
                    .withMisfireHandlingInstructionFireNow();
            SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                    .withIdentity(TRIGGER_NAME_PREFIX + jobName, jobType.getType())
                    .startAt(date)
                    .withSchedule(scheduleBuilder)
                    .build();
            removeJob(jobName, jobType);
            sched.scheduleJob(job, trigger);
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
       /**
        * 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
        * @param jobName
        * @param time
        */
        public synchronized static void modifyJobTime(String jobName,TimeJobType jobType, Date time) {
             //logger.error("Update JOB exception,jobName={},jobTyep={},jobDate={}," ,jobName,jobType,time);
            try {
                JobKey jobKey = new JobKey(JOB_NAME_PREFIX + jobName, jobType.getType());
                TriggerKey key = new TriggerKey(TRIGGER_NAME_PREFIX + jobName, jobType.getType());
                Scheduler sched = factory.getScheduler();
                SimpleTrigger trigger = (SimpleTrigger) sched.getTrigger(key);
                if(trigger == null) {
                    return;
                }
                Date oldTime = trigger.getStartTime();
                if (oldTime.getTime() != time.getTime()) {
                    JobDetail jobDetail = sched.getJobDetail(jobKey);
                    Class<? extends Job> objJobClass = jobDetail.getJobClass();
                    removeJob(jobName,jobType);
                    Map<String, Object> jp = jobDetail.getJobDataMap();
                    addJob(objJobClass, jobName, jobType, time, jp);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    /**
     * 移除一个任务
     *
     * @param jobName
     */
    public synchronized static void removeJob(String jobName, TimeJobType jobType) {
        try {
            JobKey jobKey = new JobKey(JOB_NAME_PREFIX + jobName, jobType.getType());
            TriggerKey key = new TriggerKey(TRIGGER_NAME_PREFIX + jobName, jobType.getType());
            Scheduler sched = factory.getScheduler();
            JobDetail detail = sched.getJobDetail(jobKey);
            if (detail != null) {
                sched.pauseJob(jobKey);
                sched.pauseTrigger(key);// 停止触发器
                sched.unscheduleJob(key);// 移除触发器
                sched.deleteJob(jobKey);// 删除任务
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    /**
     * 启动所有定时任务
     */
    public synchronized static void startJobs() {
        try {
            Scheduler sched = factory.getScheduler();
            sched.start();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    /**
     * 关闭所有定时任务
     */
    public synchronized static void shutdownJobs() {
        try {
            Scheduler sched = factory.getScheduler();
            if (!sched.isShutdown()) {
                sched.shutdown();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/base/TimeJobType.java
New file
@@ -0,0 +1,33 @@
package com.xiaopeng.maintenance.job.base;
/**
 * @Description 按时间点发布的任务类型
 * @date 2025年2月17日 下午7:22:28
 */
public enum TimeJobType {
    ELECTRONIC_EFFECT("electronic_effect","电子围栏生效"),
    ELECTRONIC_FAILURE("electronic_failure","电子围栏失效"),
    ACCIDENT_WILL_TIMEOUT("accident_will_timeout", "事故即将超时"),
    ACCIDENT_TIMEOUT("accident_timeout", "事故超时");
    private String type;
    private String desc;
    private TimeJobType(String type, String desc) {
        this.type = type;
        this.desc = desc;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/exceptions/TimeException.java
New file
@@ -0,0 +1,37 @@
package com.xiaopeng.maintenance.job.exceptions;
/**
 * @文件说明:定时器任务执行异常
 * @版权所有:成都喜来达
 * @项目名称: fengsheng
 * @创建者: Leeyns
 * @创建日期: 2016年5月18日
 * @最近修改者:Leeyns
 * @最近修改日期:2016年5月18日
 */
public class TimeException extends Exception {
    /**
     *  TODO
     */
    private static final long serialVersionUID = 5703430073981692250L;
    private String message;
    public TimeException() {
        super();
    }
    public TimeException(String message) {
        super(message);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/ElectronicEffectJob.java
New file
@@ -0,0 +1,33 @@
package com.xiaopeng.maintenance.job.jobs;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.maintenance.job.base.AbstractJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.List;
/**
 * 直播推送定时任务
 * @author Administrator
 *
 */
@Slf4j
public class ElectronicEffectJob extends AbstractJob {
    public static final String effect = "electronicEffect_";
    public static final String failure = "electronicFailure_";
    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        Long electronicId = maps.getLong("id");
        Long type = maps.getLong("type");
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/OrderCancelJob.java
New file
@@ -0,0 +1,61 @@
package com.xiaopeng.maintenance.job.jobs;//package com.xiaopeng.operation.job.jobs;
//
//import com.alibaba.fastjson2.JSONObject;
//import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
//import com.xiaopeng.api.model.order.XpOrder;
//import com.xiaopeng.common.core.enums.OrderStatusEnum;
//import com.xiaopeng.common.core.enums.PushMessageTypeEnum;
//import com.xiaopeng.operation.job.base.AbstractJob;
//import lombok.extern.slf4j.Slf4j;
//import org.quartz.JobDataMap;
//import org.quartz.JobExecutionContext;
//import org.quartz.JobExecutionException;
//
//import java.util.Date;
//import java.util.HashMap;
//import java.util.Map;
//import java.util.Objects;
//
///**
// * 直播推送定时任务
// * @author Administrator
// *
// */
//@Slf4j
//public class OrderCancelJob extends AbstractJob {
//
//    public static final String name = "orderCancel_";
//
//    @Override
//    public void execute(JobExecutionContext context)
//            throws JobExecutionException {
//        JobDataMap maps = context.getMergedJobDataMap();
//        Long orderId = maps.getLong("id");
//        try {
//            log.info("开始执行订单取消任务");
//            XpOrder xpOrder = xpOrderService.getById(orderId);
//            if(Objects.nonNull(xpOrder)){
//                xpOrder.setStatus(OrderStatusEnum.CANCELED.getCode());
//                xpOrderService.updateById(xpOrder);
//                log.info("订单取消状态修改完成");
//                // 消息推送
//                Map<String, Object> params = new HashMap<>();
//                Map<String, Object> content = new HashMap<>();
//                content.put("orderId",xpOrder.getId());
//                content.put("status",OrderStatusEnum.CANCELED.getCode());
//                params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc());
//                params.put("content", JSONObject.toJSONString(content));
//                params.put("timestamp", new Date().getTime()+"");
//                String message = JSONObject.toJSONString(params);
//                SendMessageDTO sendMessageDTO = new SendMessageDTO();
//                sendMessageDTO.setMessage(message);
//                sendMessageDTO.setAppUserId(String.valueOf(xpOrder.getAppUserId()));
//                xpPushMessageClient.sendStatus(sendMessageDTO);
//                log.info("订单取消消息推送完毕");
//            }
//        }catch(Exception e){
//            e.printStackTrace();
//        }
//    }
//
//}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/utils/SpringContextsUtil.java
New file
@@ -0,0 +1,37 @@
package com.xiaopeng.maintenance.job.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextsUtil implements ApplicationContextAware{
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextsUtil.applicationContext == null) {
            SpringContextsUtil.applicationContext = applicationContext;
        }
    }
    // 获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    // 通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }
    // 通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }
    // 通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        System.out.println(getApplicationContext().getBean(name, clazz));
        return getApplicationContext().getBean(name, clazz);
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/OperationRecordsVO.java
@@ -34,7 +34,7 @@
    private Long operationsUserId;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
    @ApiModelProperty(value = "开始剩余电量")
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpChargingRecordVO.java
@@ -31,7 +31,7 @@
    private String operatorEndName;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleCleanVO.java
@@ -31,7 +31,7 @@
    private String operatorEndName;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleDetectionVO.java
@@ -34,7 +34,7 @@
    private String operatorEndName;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehiclePreparationVO.java
@@ -31,7 +31,7 @@
    private String operatorEndName;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/XpVehicleRepairVO.java
@@ -31,7 +31,7 @@
    private String operatorEndName;
    @ApiModelProperty(value = "操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime operationTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskNotificationSupport.java
New file
@@ -0,0 +1,199 @@
package com.xiaopeng.maintenance.schedule;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.constant.AliPushTypeConstant;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.TaskTypeNotificationEnum;
import com.xiaopeng.common.core.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 事故任务通知支撑类
 *
 * <p>用于事故任务相关定时通知的通用方法抽取。</p>
 *
 * @author mitao
 * @since 2026-03-14
 */
@Component
public class AccidentTaskNotificationSupport {
    private static final Logger log = LoggerFactory.getLogger(AccidentTaskNotificationSupport.class);
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpAliPushMessageClient xpAliPushMessageClient;
    /**
     * 解析 handleUserId(逗号分隔)
     */
    public Set<Long> parseUserIds(String handleUserId) {
        if (StringUtils.isBlank(handleUserId)) {
            return Collections.emptySet();
        }
        String[] parts = handleUserId.split(",");
        Set<Long> userIds = new HashSet<>();
        for (String part : parts) {
            if (StringUtils.isBlank(part)) {
                continue;
            }
            try {
                userIds.add(Long.valueOf(part.trim()));
            } catch (NumberFormatException e) {
                log.warn("任务接收人ID非法,handleUserId={}", handleUserId);
            }
        }
        return userIds;
    }
    /**
     * 获取运维人员信息
     */
    public Map<Long, XpOperationsUser> fetchOperationUsers(Set<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return Collections.emptyMap();
        }
        try {
            R<List<XpOperationsUser>> result = xpOperationUserClient.listByIds(new ArrayList<>(userIds));
            if (result == null || CollectionUtils.isEmpty(result.getData())) {
                return Collections.emptyMap();
            }
            return result.getData().stream()
                    .filter(Objects::nonNull)
                    .filter(u -> u.getId() != null)
                    .collect(Collectors.toMap(XpOperationsUser::getId, u -> u, (a, b) -> a));
        } catch (Exception e) {
            log.error("获取运维人员信息失败", e);
            return Collections.emptyMap();
        }
    }
    /**
     * 批量获取车辆信息
     */
    public Map<Long, XpVehicle> fetchVehicles(Set<Long> vehicleIds) {
        if (CollectionUtils.isEmpty(vehicleIds)) {
            return Collections.emptyMap();
        }
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listByIds(new ArrayList<>(vehicleIds));
            if (result == null || CollectionUtils.isEmpty(result.getData())) {
                return Collections.emptyMap();
            }
            return result.getData().stream()
                    .filter(Objects::nonNull)
                    .filter(v -> v.getId() != null)
                    .collect(Collectors.toMap(XpVehicle::getId, v -> v, (a, b) -> a));
        } catch (Exception e) {
            log.error("获取车辆信息失败", e);
            return Collections.emptyMap();
        }
    }
    /**
     * 构建推送 body JSON
     */
    public String buildPushBodyJson(String content, Integer strategyCode, Long taskId) {
        Map<String, Object> param = new HashMap<>();
        param.put("content", content);
        param.put("strategy", strategyCode);
        param.put("type", TaskTypeNotificationEnum.ACCIDENT_TASK.getCode());
        param.put("id", taskId == null ? "0" : taskId.toString());
        return JSONObject.toJSONString(param);
    }
    /**
     * 批量构建通知记录(包含 notificationExt)
     */
    public List<XpOperationsUserNotification> buildNotifications(List<XpOperationsUser> users,
                                                                 Integer notificationType,
                                                                 String content,
                                                                 Long taskId,
                                                                 String bodyJson) {
        if (CollectionUtils.isEmpty(users)) {
            return Collections.emptyList();
        }
        List<XpOperationsUserNotification> notifications = new ArrayList<>();
        for (XpOperationsUser user : users) {
            if (user == null || user.getId() == null) {
                continue;
            }
            XpOperationsUserNotification notification = new XpOperationsUserNotification();
            notification.setNotificationType(notificationType);
            notification.setNotificationContent(content);
            notification.setOperationsUserId(user.getId());
            notification.setDataId(taskId == null ? 0L : taskId);
            notification.setReadStatus(0);
            notification.setNotificationExt(bodyJson);
            notifications.add(notification);
        }
        return notifications;
    }
    /**
     * 推送通知(按 deviceType 分组)
     */
    public int pushToUsersByDeviceType(List<XpOperationsUser> users, String title, String bodyJson) {
        if (CollectionUtils.isEmpty(users)) {
            return 0;
        }
        Map<Integer, List<XpOperationsUser>> operationUserMap = users.stream()
                .filter(u -> u != null && u.getDeviceType() != null && StringUtils.isNotBlank(u.getPhoneNumber()))
                .collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
        int pushCount = 0;
        for (Map.Entry<Integer, List<XpOperationsUser>> entry : operationUserMap.entrySet()) {
            List<XpOperationsUser> groupUsers = entry.getValue();
            if (CollectionUtils.isEmpty(groupUsers)) {
                continue;
            }
            String account = groupUsers.stream()
                    .map(XpOperationsUser::getPhoneNumber)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.joining(","));
            if (StringUtils.isBlank(account)) {
                continue;
            }
            try {
                xpAliPushMessageClient.pushMessage(AliPushMessageDTO.builder()
                        .body(bodyJson)
                        .account(account)
                        .title(title)
                        .pushType(AliPushTypeConstant.MESSAGE)
                        .deviceType(entry.getKey())
                        .build());
                pushCount++;
            } catch (Exception e) {
                log.error("事故任务推送失败,deviceType={}", entry.getKey(), e);
            }
        }
        return pushCount;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskTimeoutCheckScheduleTask.java
New file
@@ -0,0 +1,186 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
import com.xiaopeng.common.core.enums.NotificationTypeEnum;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 事故任务超时检查定时任务
 *
 * <p>每分钟执行一次,判断事故任务是否超时并发送通知。</p>
 *
 * @author mitao
 * @since 2026-03-14
 */
@Deprecated
public class AccidentTaskTimeoutCheckScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(AccidentTaskTimeoutCheckScheduleTask.class);
    private static final String TASK_NAME = "事故任务超时检查定时任务";
    private static final String PUSH_TITLE = "事故超时警示";
    private static final String CONTENT_TEMPLATE =
            "车辆%s在%s发生的紧急事故已超时!";
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpOperationUserNotificationClient xpOperationUserNotificationClient;
    @Autowired
    private AccidentTaskNotificationSupport notificationSupport;
    /**
     * 每分钟执行一次
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeAccidentTaskTimeoutCheck() {
        log.info("========== 开始执行{} ==========", TASK_NAME);
        try {
            LocalDateTime now = LocalDateTime.now();
            List<XpTask> tasks = queryTimeoutAccidentTasks(now);
            if (CollectionUtils.isEmpty(tasks)) {
                log.info("未找到超时事故任务,跳过执行");
                return;
            }
            Set<Long> vehicleIds = tasks.stream()
                    .map(XpTask::getVehicleId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            Map<Long, XpVehicle> vehicleMap = notificationSupport.fetchVehicles(vehicleIds);
            Set<Long> allUserIds = new HashSet<>();
            for (XpTask task : tasks) {
                allUserIds.addAll(notificationSupport.parseUserIds(task.getHandleUserId()));
            }
            Map<Long, XpOperationsUser> userMap = notificationSupport.fetchOperationUsers(allUserIds);
            int notifiedTaskCount = 0;
            int notificationCount = 0;
            int pushCount = 0;
            List<XpOperationsUserNotification> allNotifications = new ArrayList<>();
            for (XpTask task : tasks) {
                try {
                    if (task == null || task.getId() == null) {
                        continue;
                    }
                    Set<Long> userIds = notificationSupport.parseUserIds(task.getHandleUserId());
                    if (CollectionUtils.isEmpty(userIds)) {
                        log.warn("事故任务ID={} 未配置有效处理人,跳过通知", task.getId());
                        continue;
                    }
                    int affectedRows = markTimeoutNoticeSent(task.getId(), now);
                    if (affectedRows <= 0) {
                        continue;
                    }
                    List<XpOperationsUser> users = userIds.stream()
                            .map(userMap::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(users)) {
                        log.warn("事故任务ID={} 未匹配到有效运维人员,跳过通知", task.getId());
                        continue;
                    }
                    String plateNumber = resolvePlateNumber(vehicleMap.get(task.getVehicleId()));
                    String vehicleLocation = StringUtils.defaultString(task.getVehicleLocation());
                    String content = String.format(CONTENT_TEMPLATE, plateNumber, vehicleLocation);
                    String bodyJson = notificationSupport.buildPushBodyJson(
                            content,
                            NotificationTypeEnum.ACCIDENT_TIMEOUT.getCode(),
                            task.getId()
                    );
                    allNotifications.addAll(notificationSupport.buildNotifications(
                            users,
                            NotificationTypeEnum.ACCIDENT_TIMEOUT.getCode(),
                            content,
                            task.getId(),
                            bodyJson
                    ));
                    pushCount += notificationSupport.pushToUsersByDeviceType(users, PUSH_TITLE, bodyJson);
                    notifiedTaskCount++;
                } catch (Exception e) {
                    log.error("处理超时事故任务通知失败,任务ID={}", task == null ? null : task.getId(), e);
                }
            }
            if (!CollectionUtils.isEmpty(allNotifications)) {
                xpOperationUserNotificationClient.addNotificationBatch(allNotifications);
                notificationCount = allNotifications.size();
            }
            log.info("========== {} 执行完成,任务数: {},通知记录数: {},推送次数: {} ==========",
                    TASK_NAME, notifiedTaskCount, notificationCount, pushCount);
        } catch (Exception e) {
            log.error("{}执行失败", TASK_NAME, e);
        }
    }
    /**
     * 查询超时事故任务:endTime < now 且未发送超时通知
     */
    private List<XpTask> queryTimeoutAccidentTasks(LocalDateTime now) {
        return taskMapper.selectList(new LambdaQueryWrapper<XpTask>()
                .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
                .in(XpTask::getStatus,
                        TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                        TaskScheduleConstants.TASK_STATUS_PENDING)
                .isNotNull(XpTask::getEndTime)
                .lt(XpTask::getEndTime, now)
                .eq(XpTask::getExpiredNoticeSent, 0));
    }
    /**
     * 原子更新(抢占式)标记超时通知已发送
     */
    private int markTimeoutNoticeSent(Long taskId, LocalDateTime now) {
        return taskMapper.update(null, new LambdaUpdateWrapper<XpTask>()
                .set(XpTask::getExpiredNoticeSent, 1)
                .eq(XpTask::getId, taskId)
                .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
                .in(XpTask::getStatus,
                        TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                        TaskScheduleConstants.TASK_STATUS_PENDING)
                .isNotNull(XpTask::getEndTime)
                .lt(XpTask::getEndTime, now)
                .eq(XpTask::getExpiredNoticeSent, 0));
    }
    private String resolvePlateNumber(XpVehicle vehicle) {
        return vehicle == null ? "" : StringUtils.defaultString(vehicle.getPlateNumber());
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/AccidentTaskWillTimeoutScheduleTask.java
New file
@@ -0,0 +1,235 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
import com.xiaopeng.common.core.enums.NotificationTypeEnum;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 事故任务即将超时检查定时任务
 *
 * <p>每分钟执行一次,判断事故任务在即将超时窗口内并发送通知。</p>
 *
 * @author mitao
 * @since 2026-03-14
 */
@Deprecated
public class AccidentTaskWillTimeoutScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(AccidentTaskWillTimeoutScheduleTask.class);
    private static final String TASK_NAME = "事故任务即将超时检查定时任务";
    private static final String PUSH_TITLE = "事故即将超时提示";
    private static final String CONTENT_TEMPLATE =
            "车辆%s在%s发生的紧急事故即将超时,剩余时间%s分钟,请及时到达现场!";
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpOperationUserNotificationClient xpOperationUserNotificationClient;
    @Autowired
    private AccidentTaskNotificationSupport notificationSupport;
    /**
     * 每分钟执行一次
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeAccidentTaskWillTimeoutCheck() {
        log.info("========== 开始执行{} ==========", TASK_NAME);
        try {
            XpTaskRule taskRule = getTaskRule();
            Integer processingTimeLimit = taskRule == null ? null : taskRule.getProcessingTimeLimit();
            Integer factorMinutes = calcWillTimeoutFactorMinutes(processingTimeLimit);
            if (factorMinutes == null || factorMinutes <= 0) {
                log.info("未配置有效处理时限或即将超时因子<=0,跳过执行,processingTimeLimit={}", processingTimeLimit);
                return;
            }
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime windowEnd = now.plusMinutes(factorMinutes);
            List<XpTask> tasks = queryWillTimeoutAccidentTasks(now, windowEnd);
            if (CollectionUtils.isEmpty(tasks)) {
                log.info("未找到即将超时事故任务,跳过执行");
                return;
            }
            Set<Long> vehicleIds = tasks.stream()
                    .map(XpTask::getVehicleId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            Map<Long, XpVehicle> vehicleMap = notificationSupport.fetchVehicles(vehicleIds);
            Set<Long> allUserIds = new HashSet<>();
            for (XpTask task : tasks) {
                allUserIds.addAll(notificationSupport.parseUserIds(task.getHandleUserId()));
            }
            Map<Long, XpOperationsUser> userMap = notificationSupport.fetchOperationUsers(allUserIds);
            int notifiedTaskCount = 0;
            int notificationCount = 0;
            int pushCount = 0;
            List<XpOperationsUserNotification> allNotifications = new ArrayList<>();
            for (XpTask task : tasks) {
                try {
                    if (task == null || task.getId() == null) {
                        continue;
                    }
                    Set<Long> userIds = notificationSupport.parseUserIds(task.getHandleUserId());
                    if (CollectionUtils.isEmpty(userIds)) {
                        log.warn("事故任务ID={} 未配置有效处理人,跳过通知", task.getId());
                        continue;
                    }
                    int affectedRows = markWillTimeoutNoticeSent(task.getId(), now, windowEnd);
                    if (affectedRows <= 0) {
                        continue;
                    }
                    List<XpOperationsUser> users = userIds.stream()
                            .map(userMap::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(users)) {
                        log.warn("事故任务ID={} 未匹配到有效运维人员,跳过通知", task.getId());
                        continue;
                    }
                    String plateNumber = resolvePlateNumber(vehicleMap.get(task.getVehicleId()));
                    String vehicleLocation = StringUtils.defaultString(task.getVehicleLocation());
                    String remainMinutes = String.valueOf(calcRemainMinutes(now, task.getEndTime()));
                    String content = String.format(CONTENT_TEMPLATE, plateNumber, vehicleLocation, remainMinutes);
                    String bodyJson = notificationSupport.buildPushBodyJson(
                            content,
                            NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(),
                            task.getId()
                    );
                    allNotifications.addAll(notificationSupport.buildNotifications(
                            users,
                            NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(),
                            content,
                            task.getId(),
                            bodyJson
                    ));
                    pushCount += notificationSupport.pushToUsersByDeviceType(users, PUSH_TITLE, bodyJson);
                    notifiedTaskCount++;
                } catch (Exception e) {
                    log.error("处理即将超时事故任务通知失败,任务ID={}", task == null ? null : task.getId(), e);
                }
            }
            if (!CollectionUtils.isEmpty(allNotifications)) {
                xpOperationUserNotificationClient.addNotificationBatch(allNotifications);
                notificationCount = allNotifications.size();
            }
            log.info("========== {} 执行完成,任务数: {},通知记录数: {},推送次数: {} ==========",
                    TASK_NAME, notifiedTaskCount, notificationCount, pushCount);
        } catch (Exception e) {
            log.error("{}执行失败", TASK_NAME, e);
        }
    }
    /**
     * 获取任务规则配置
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 计算即将超时窗口(分钟):floor(processingTimeLimit * 0.2)
     */
    private Integer calcWillTimeoutFactorMinutes(Integer processingTimeLimit) {
        if (processingTimeLimit == null || processingTimeLimit <= 0) {
            return 0;
        }
        return (int) Math.floor(processingTimeLimit * 0.2d);
    }
    /**
     * 查询即将超时事故任务:now < endTime <= windowEnd 且未发送即将超时通知
     */
    private List<XpTask> queryWillTimeoutAccidentTasks(LocalDateTime now, LocalDateTime windowEnd) {
        return taskMapper.selectList(new LambdaQueryWrapper<XpTask>()
                .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
                .in(XpTask::getStatus,
                        TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                        TaskScheduleConstants.TASK_STATUS_PENDING)
                .isNotNull(XpTask::getEndTime)
                .gt(XpTask::getEndTime, now)
                .le(XpTask::getEndTime, windowEnd)
                .eq(XpTask::getWillTimeoutNoticeSent, 0));
    }
    /**
     * 原子更新(抢占式)标记即将超时通知已发送
     */
    private int markWillTimeoutNoticeSent(Long taskId, LocalDateTime now, LocalDateTime windowEnd) {
        return taskMapper.update(null, new LambdaUpdateWrapper<XpTask>()
                .set(XpTask::getWillTimeoutNoticeSent, 1)
                .eq(XpTask::getId, taskId)
                .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
                .in(XpTask::getStatus,
                        TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                        TaskScheduleConstants.TASK_STATUS_PENDING)
                .isNotNull(XpTask::getEndTime)
                .gt(XpTask::getEndTime, now)
                .le(XpTask::getEndTime, windowEnd)
                .eq(XpTask::getWillTimeoutNoticeSent, 0));
    }
    /**
     * 计算剩余分钟数(向上取整,避免出现 0 分钟)
     */
    private long calcRemainMinutes(LocalDateTime now, LocalDateTime endTime) {
        if (endTime == null) {
            return 0;
        }
        long seconds = Duration.between(now, endTime).getSeconds();
        if (seconds <= 0) {
            return 0;
        }
        return (seconds + 59) / 60;
    }
    private String resolvePlateNumber(XpVehicle vehicle) {
        return vehicle == null ? "" : StringUtils.defaultString(vehicle.getPlateNumber());
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/ClockInScheduleTask.java
@@ -56,13 +56,17 @@
    /**
     * 每5分钟执行一次
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @Scheduled(fixedRate = 60*1000)
    public void clockInScheduleTask() {
        log.info("========== 开始执行打卡检查 ==========");
        try {
            // 查询下班卡在今天的
            List<XpSchedulingDetail> clockOutPlanList = xpSchedulingDetailService.list(Wrappers.lambdaQuery(XpSchedulingDetail.class)
                    .likeRight(XpSchedulingDetail::getPlanTime, LocalDate.now()));
            // 下班卡在昨天的
            List<XpSchedulingDetail> clockOutPlanListLast = xpSchedulingDetailService.list(Wrappers.lambdaQuery(XpSchedulingDetail.class)
                    .likeRight(XpSchedulingDetail::getPlanTime, LocalDate.now().minusDays(1)));
            if(!CollectionUtils.isEmpty(clockOutPlanList)){
                List<Long> fleetIds = clockOutPlanList.stream().map(XpSchedulingDetail::getFleetId).collect(Collectors.toList());
@@ -154,6 +158,47 @@
                }
            }
//            if (!CollectionUtils.isEmpty(clockOutPlanListLast)){
//                List<Long> fleetIds = clockOutPlanListLast.stream().map(XpSchedulingDetail::getFleetId).distinct().collect(Collectors.toList());
//                // 查询所有车队的绑定人员
//                List<XpFleetOperations> xpFleetOperations = xpFleetOperationsClient.getOperationsIdsByFleetIds(fleetIds).getData();
//                // 查询班次
//                List<Long> shiftIds = clockOutPlanListLast.stream().map(XpSchedulingDetail::getShiftId).collect(Collectors.toList());
//                List<XpShift> xpShifts = xpShiftService.list(Wrappers.lambdaQuery(XpShift.class)
//                        .in(XpShift::getId, shiftIds));
//                log.info("========== 查询到{}个车队 ==========", xpFleetOperations.size());
//
//                // 处理下班卡缺卡情况
//                // 过滤当前时间在班次下班打卡时间之后5小时的数据   今日已过下班打卡时间5小时后的班次
//                List<XpShift> shiftListFive = xpShifts.stream().filter(xpShift ->
//                        DateUtils.stringToLocalDateTime(LocalDate.now().minusDays(1) + " " + xpShift.getEndTime() + ":00").plusHours(5).isBefore(LocalDateTime.now())).collect(Collectors.toList());
//                List<Long> fiveIds = shiftListFive.stream().map(XpShift::getId).collect(Collectors.toList());
//                if(!CollectionUtils.isEmpty(fiveIds)){
//                    log.info("========== 进入下班过期的班次 ==========");
//                    // 获取过期当前班次的排班明细  覆盖
//                    List<XpSchedulingDetail> fiveClockOutPlans = clockOutPlanListLast.stream().filter(xpSchedulingDetail -> fiveIds.contains(xpSchedulingDetail.getShiftId())).collect(Collectors.toList());
//                    List<Long> fiveFleetIdIn = fiveClockOutPlans.stream().map(XpSchedulingDetail::getFleetId).collect(Collectors.toList());
//                    // 查询所有车队的绑定人员
//                    if(!CollectionUtils.isEmpty(xpFleetOperations)){
//                        List<Long> operationsUserIdsFive = xpFleetOperations.stream().filter(x -> fiveFleetIdIn.contains(x.getFleetId()))
//                                .map(XpFleetOperations::getOperationsUserId).distinct().collect(Collectors.toList());
//                        // 批量查询人员打卡信息
//                        List<XpClockIn> clockInListFive = xpClockInService.list(Wrappers.lambdaQuery(XpClockIn.class)
//                                .in(XpClockIn::getAppUserId, operationsUserIdsFive)
//                                .isNull(XpClockIn::getClockOutTime));
//                        log.info("========== 获取到{}个人员没有下班打卡信息 ==========", clockInListFive.size());
//                        if(!CollectionUtils.isEmpty(clockInListFive)){
//                            for (XpClockIn xpClockIn : clockInListFive) {
//                                xpClockIn.setClockOutTime(LocalDateTime.now());
//                                xpClockIn.setClockOutResult(3);
//                            }
//                            xpClockInService.updateBatchById(clockInListFive);
//                            log.info("========== 修改为下班缺卡 ==========");
//                        }
//                    }
//                }
//            }
        } catch (Exception e) {
            log.error("执行打卡检查任务执行失败", e);
        }
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/XpTaskService.java
@@ -33,7 +33,7 @@
    PageInfo<TaskPageListVO> pageList(TaskPageListQuery query);
    void saveVehicleAccident(SaveVehicleAccidentDto dto, XpOperationsUser xpOperationsUser);
    XpTask saveVehicleAccident(SaveVehicleAccidentDto dto, XpOperationsUser xpOperationsUser);
    R<TaskAccidentDetailVO> vehicleAccidentDetail(Long id);
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java
@@ -118,6 +118,8 @@
        if (Objects.nonNull(operationsUserEnd)) {
            xpChargingRecordVO.setOperatorEndName(operationsUserEnd.getNickName());
        }
        xpChargingRecordVO.setStartMileageRange(Objects.nonNull(xpChargingRecord.getStartMileageRange()) ? xpChargingRecord.getStartMileageRange() : 0F);
        xpChargingRecordVO.setEndMileageRange(Objects.nonNull(xpChargingRecord.getEndMileageRange()) ? xpChargingRecord.getEndMileageRange() : 0F);
        return xpChargingRecordVO;
    }
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
@@ -104,6 +104,12 @@
    public R<String> clockIn(ClockDTO dto) {
        XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId());
        if (null == xpCheckLocation) {
            return R.fail("打卡地址不存在");
        }
        if (xpCheckLocation.getStatus() == 0) {
            return R.fail("打卡地址已禁用");
        }
        Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat());
        if (null != distance) {
@@ -144,6 +150,12 @@
    public R<String> clockOut(ClockDTO dto) {
        XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId());
        if (null == xpCheckLocation) {
            return R.fail("打卡地址不存在");
        }
        if (xpCheckLocation.getStatus() == 0) {
            return R.fail("打卡地址已禁用");
        }
        Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon() + "," + dto.getUserLat(), xpCheckLocation.getLocationLon() + "," + xpCheckLocation.getLocationLat());
        if (null != distance) {
            Double distance1 = distance.get("WGS84");
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -190,7 +190,7 @@
    @Override
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
    public void saveVehicleAccident(SaveVehicleAccidentDto dto, XpOperationsUser xpOperationsUser) {
    public XpTask saveVehicleAccident(SaveVehicleAccidentDto dto, XpOperationsUser xpOperationsUser) {
        XpTask xpTask = new XpTask();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
@@ -251,11 +251,22 @@
        // 根据事故处理限时配置设置任务截止时间
        LocalDateTime endTime = LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit());
        xpTask.setEndTime(endTime);
        xpTask.setExpiredNoticeSent(0);
        xpTask.setWillTimeoutNoticeSent(0);
        this.save(xpTask);
        if (xpTask.getId() == null && StringUtils.isNotBlank(xpTask.getTaskCode())) {
            XpTask savedTask = this.getOne(new LambdaQueryWrapper<XpTask>()
                    .eq(XpTask::getTaskCode, xpTask.getTaskCode())
                    .last("limit 1"));
            if (savedTask != null && savedTask.getId() != null) {
                xpTask.setId(savedTask.getId());
            }
        }
        // 异步发送消息通知
        String plateNumber = Objects.nonNull(vehicle) ? vehicle.getPlateNumber() : "";
        sendAccidentNotificationAsync(xpTask, plateNumber, taskRule.getProcessingTimeLimit());
        return xpTask;
    }
    /**
@@ -1168,6 +1179,7 @@
        task.setReportTime(LocalDateTime.now());
        task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
        task.setExpiredNoticeSent(0);
        task.setWillTimeoutNoticeSent(0);
        task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
        task.setCreateBy("系统自动生成");
        task.setCreateTime(LocalDateTime.now());
@@ -1179,6 +1191,11 @@
    @Override
    public R assigneeTask(Long id, Long handlerUserId, Long assigneeUserId) {
        XpTask task = this.getById(id);
        // 查询规则 任务结束时间从任务分配时开始计算
        XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
        if (taskRule != null) {
            task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
        }
        task.setAssigneeTime(LocalDateTime.now());
        task.setHandleUserId(handlerUserId.toString());
        task.setVehicleId(null);
@@ -1199,14 +1216,7 @@
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(code);
            // 查询规则
            XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
            if (taskRule != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            task.setTaskCategory(2);
            // todo 临时处理
            task.setHandleUserId("1");
            task.setTaskTypeId(dto.getTaskTypeId());
            task.setTaskUrgencyLevel(dto.getTaskUrgencyLevel());
            task.setTaskDescription(dto.getTaskDescription());
@@ -1217,6 +1227,8 @@
            task.setVehicleLat(dto.getVehicleLat());
            task.setReportUserId(reportUserId);
            task.setReportTime(LocalDateTime.now());
            task.setExpiredNoticeSent(0);
            task.setWillTimeoutNoticeSent(0);
            saveTask.add(task);
        }
        this.saveBatch(saveTask);
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpVehiclePreparationServiceImpl.java
@@ -7,6 +7,7 @@
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.maintenance.XpVehiclePreparation;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.maintenance.model.query.XpVehiclePreparationPageQuery;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.maintenance.XpVehiclePreparationDetailVO;
@@ -20,6 +21,9 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -55,6 +59,8 @@
    private XpOperationUserClient xpOperationUserClient;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Override
@@ -69,6 +75,12 @@
            case 2:
                // 充电记录
                operationRecords = xpChargingRecordMapper.operationRecords(query);
                if(!CollectionUtils.isEmpty(operationRecords)){
                    for (OperationRecordsVO operationRecord : operationRecords) {
                        operationRecord.setStartMileageRange(Objects.isNull(operationRecord.getStartMileageRange())?0.0f:operationRecord.getStartMileageRange());
                        operationRecord.setEndMileageRange(Objects.isNull(operationRecord.getEndMileageRange())?0.0f:operationRecord.getEndMileageRange());
                    }
                }
                break;
            case 3:
                // 检测记录
@@ -88,7 +100,7 @@
            return pageInfo;
        }
        List<Long> userIds = operationRecords.stream().map(OperationRecordsVO::getOperationsUserId).collect(Collectors.toList());
        List<Long> userIds = operationRecords.stream().map(OperationRecordsVO::getOperationsUserId).distinct().collect(Collectors.toList());
        // 查询操作人信息
        R<List<XpOperationsUser>> listR = xpOperationUserClient.listByIds(userIds);
        List<XpOperationsUser> xpOperationsUsers = listR.getData();
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpVehicleRepairServiceImpl.java
@@ -240,5 +240,6 @@
        xpVehicleRepair.setRepairMoneyDetail(dto.getRepairMoneyDetail());
        xpVehicleRepair.setReturnUserId(id);
        xpVehicleRepair.setReturnTime(LocalDateTime.now());
        this.updateById(xpVehicleRepair);
    }
}
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages.properties
@@ -2,4 +2,4 @@
welcome.message=欢迎来到我们的应用程序!
task.not.exist=任务不存在
vehicle.not.exist=车辆不存在
repair.money.too.long=维修费用不能超过200字符
repair.money.too.long=维修费用不能超过10字符
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_en_US.properties
@@ -2,5 +2,5 @@
welcome.message=Welcome to our application!
task.not.exist=The task does not exist.
vehicle.not.exist=The vehicle does not exist.
repair.money.too.long=The maintenance cost cannot exceed 200 characters
repair.money.too.long=The maintenance cost cannot exceed 10 characters
task.rule.not.config=The vehicle accident task allocation rules have not been configured
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -2,5 +2,5 @@
welcome.message=欢迎来到我们的应用程序!
task.not.exist=任务不存在
vehicle.not.exist=车辆不存在
repair.money.too.long=维修费用不能超过200字符
repair.money.too.long=维修费用不能超过10字符
task.rule.not.config=未配置车辆事故任务分配规则
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java
@@ -32,200 +32,8 @@
    @Autowired
    private XpGeoFencingPolygonService xpGeoFencingPolygonService;
    // 自定义线程池配置
    private static final int CORE_POOL_SIZE = 5;  // 核心线程数
    private static final int MAX_POOL_SIZE = 10;  // 最大线程数
    private static final long KEEP_ALIVE_TIME = 60L; // 线程空闲存活时间
    private static final int QUEUE_CAPACITY = 100; // 阻塞队列容量
    // 创建自定义线程池
    private final ExecutorService customThreadPool = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadFactory() {
                private final AtomicInteger threadNumber = new AtomicInteger(1);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "route-check-thread-" + threadNumber.getAndIncrement());
                    t.setDaemon(true); // 设置为守护线程,避免应用关闭时线程未停止
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用线程执行
    );
//    public RouteVo getDistanceAndTime(LonLatDto start, LonLatDto end, String avoidRoads, String fenceId, Integer routePlanRule) {
//        RouteVo routeVo = new RouteVo();
//
//        String url = "https://tsapi.amap.com/v1/route/price?key=" + key + "&extensions=all&start=" + start.getLongitude() + "," + start.getLatitude();
//        HashMap<String, Object> params = new HashMap<>();
//        params.put("extensions", "all");
//        params.put("start", start.getLongitude() + "," + start.getLatitude());
//        params.put("end", end.getLongitude() + "," + end.getLatitude());
//        params.put("routeUserType", 2);
//        if (avoidRoads != null && !avoidRoads.isEmpty()) {
//            params.put("voidPolygons", avoidRoads);
//        }
//        if (routePlanRule != null) {
//            params.put("strategy", routePlanRule);
//        }
//
//        String post = HttpUtils.post(url, JSON.toJSONString(params));
//        System.out.println("高德地图返回结果:" + post);
//
//        JSONObject jsonObject = JSON.parseObject(post);
//        String data = jsonObject.getString("data");
//        JSONObject jsonObject1 = JSON.parseObject(data);
//        JSONArray path = jsonObject1.getJSONArray("paths");
//
//        if (path == null || path.isEmpty()) {
//            return null;
//        }
//
//        // 使用自定义线程池并行处理每个路径的围栏检查
//        List<Future<PathCheckResult>> futures = new ArrayList<>();
//        CountDownLatch latch = new CountDownLatch(path.size()); // 用于等待所有任务完成
//
//        // 提交每个路径的处理任务
//        for (int j = 0; j < path.size(); j++) {
//            final int index = j;
//            JSONObject pathObject = path.getJSONObject(index);
//
//            Future<PathCheckResult> future = customThreadPool.submit(() -> {
//                try {
//                    PathCheckResult result = new PathCheckResult();
//                    result.setIndex(index);
//                    result.setPathObject(pathObject);
//
//                    String points = pathObject.getString("points");
//                    String[] split = points.split(";");
//
//                    // 检查点位是否在围栏内
//                    boolean inFence = checkPointsInFence(split, fenceId);
//                    result.setInFence(inFence);
//
//                    return result;
//                } finally {
//                    latch.countDown(); // 任务完成,计数器减1
//                }
//            });
//
//            futures.add(future);
//        }
//
//        try {
//            // 等待所有任务完成,设置超时时间
//            if (!latch.await(10, TimeUnit.SECONDS)) {
//                System.out.println("路径检查任务超时");
//                // 取消所有未完成的任务
//                for (Future<PathCheckResult> future : futures) {
//                    if (!future.isDone()) {
//                        future.cancel(true);
//                    }
//                }
//            }
//        } catch (InterruptedException e) {
//            Thread.currentThread().interrupt();
//            System.out.println("路径检查任务被中断");
//            return null;
//        }
//
//        // 收集处理结果,找到第一个不在围栏内的路径
//        PathCheckResult validPathResult = null;
//
//        for (Future<PathCheckResult> future : futures) {
//            try {
//                if (future.isDone() && !future.isCancelled()) {
//                    PathCheckResult result = future.get();
//                    if (!result.isInFence()) {
//                        validPathResult = result;
//                        break; // 找到有效的就停止
//                    }
//                }
//            } catch (InterruptedException | ExecutionException e) {
//                System.out.println("获取路径检查结果失败: " + e.getMessage());
//                e.printStackTrace();
//            }
//        }
//
//        // 如果找到了有效路径,处理该路径数据
//        if (validPathResult != null) {
//            JSONObject validPath = validPathResult.getPathObject();
//            String routeID = validPath.getString("routeID");
//            String points = validPath.getString("points");
//            String[] split = points.split(";");
//
//            Integer costTime = validPath.getInteger("costTime");
//            Integer distance = validPath.getInteger("distance");
//
//            routeVo.setDistance(distance);
//            routeVo.setDuration(costTime);
//
//            ArrayList<LonLatDto> lonLatDtos = new ArrayList<>(split.length);
//            for (String s : split) {
//                String[] split1 = s.split(",");
//                LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
//                lonLatDtos.add(lonLatDto);
//            }
//            routeVo.setPolyline(lonLatDtos);
//            routeVo.setRouteID(routeID);
//
//            return routeVo;
//        }
//
//        return null;
//    }
    /**
     * 路径检查结果内部类
     */
    private static class PathCheckResult {
        private int index;
        private JSONObject pathObject;
        private boolean inFence; // true: 所有点都在围栏内, false: 有点不在围栏内
        public int getIndex() {
            return index;
        }
        public void setIndex(int index) {
            this.index = index;
        }
        public JSONObject getPathObject() {
            return pathObject;
        }
        public void setPathObject(JSONObject pathObject) {
            this.pathObject = pathObject;
        }
        public boolean isInFence() {
            return inFence;
        }
        public void setInFence(boolean inFence) {
            this.inFence = inFence;
        }
    }
    /**
     * 检查路径点位是否在围栏内
     */
    private boolean checkPointsInFence(String[] points, String fenceId) {
        // 取5分之一的点位进行检查
        for (int i = 0; i < points.length; i++) {
            if (i % 5 == 0) {
                String[] split1 = points[i].split(",");
                LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                if (!xpGeoFencingPolygonService.isInFence(lonLatDto.getLongitude(), lonLatDto.getLatitude(), fenceId, "operating_area")) {
                    return false; // 有点位不在围栏内
                }
            }
        }
        return true; // 所有检查的点都在围栏内
    }
    /**
     * 根据起始点获取驾车距离 和 时间  以及避让区域
@@ -263,23 +71,22 @@
        JSONObject jsonObject1 = JSON.parseObject(data);
        JSONArray path = jsonObject1.getJSONArray("paths");
        log.info("开始判断导航路径是否在运营区域范围内 {}", fenceId);
        for (Object o : path) {
            JSONObject jsonObject2 = (JSONObject) o;
            String routeID = jsonObject2.getString("routeID");
            String points = jsonObject2.getString("points");
            String[] split = points.split(";");
            int temp=0;
            // 取5分之一的点位
            for (int i = 0; i < split.length; i++) {
                if (i % 10 == 0) {
                    String[] split1 = split[i].split(",");
                    LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                    if (!xpGeoFencingPolygonService.isInFence(lonLatDto.getLongitude(), lonLatDto.getLatitude(), fenceId, "operating_area")) {
                        temp++;
                        break;
                    }
                }
            List<LonLatDto> pointsArray = new ArrayList<>();
            for (String string : split) {
                String[] split1 = string.split(",");
                LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                pointsArray.add(lonLatDto);
            }
            ForkJoinPool pool = new ForkJoinPool(); // 创建ForkJoinPool对象(默认使用处理器的核心数作为线程池大小)
            ArrayPointsTask arrayPointsTask = new ArrayPointsTask(pointsArray, fenceId); // 创建求和任务对象并传入数组作为参数(这里也可以传入数组的一部分作为参数来实现更细粒度的拆分)
            Long temp = pool.invoke(arrayPointsTask); // 提交任务并等待处理完成(也可以使用submit方法提交任务并获取一个Future对象来异步获取结果)
            pool.shutdown(); // 关闭ForkJoinPool(释放资源)
            if(temp==0){
                Integer costTime = jsonObject2.getInteger("costTime");
                Integer distance = jsonObject2.getInteger("distance");
@@ -293,15 +100,54 @@
                }
                routeVo.setPolyline(lonLatDtos);
                routeVo.setRouteID(routeID);
                log.info("导航 {} 在 {} 运营区域内", routeVo.getRouteID(), fenceId);
                return routeVo;
            }
        }
        return null;
    }
    class ArrayPointsTask extends RecursiveTask<Long>{
        //最大定位集合阈值
        private final int THRESHOLD = 10;
        //围栏ID
        String fenceId;
        //定位点集合
        List<LonLatDto> pointsArray;
        public ArrayPointsTask(List<LonLatDto> pointsArray, String fenceId) {
            this.pointsArray = pointsArray;
            this.fenceId = fenceId;
        }
        @Override
        protected Long compute() {
            if(this.pointsArray.size() > THRESHOLD){
                int num = this.pointsArray.size() / 2;
                ArrayPointsTask task1 = new ArrayPointsTask(this.pointsArray.subList(0, num), fenceId);
                ArrayPointsTask task2 = new ArrayPointsTask(this.pointsArray.subList(num, this.pointsArray.size()), fenceId);
                task1.fork();
                task2.fork();
                return task1.join() + task2.join();
            }else{
                long temp = 0;
                for (LonLatDto lonLatDto : this.pointsArray) {
                    if (!xpGeoFencingPolygonService.isInFence(lonLatDto.getLongitude(), lonLatDto.getLatitude(), fenceId, "operating_area")) {
                        temp++;
                        break;
                    }
                }
                return temp;
            }
        }
    }
    /**
     * 线路还原接口
     * 车端使用的数据
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -533,7 +533,7 @@
     */
    @ApiOperation(value = "订单查询轨迹路线")
    @PostMapping("/api/v1/mini-program/order/queryOrderTrajectory")
    public R<List<OrderTrajectory>> queryOrderTrajectory(@RequestParam Long orderId, HttpServletRequest request) {
    public R<List<OrderTrajectory>> queryOrderTrajectory(@RequestParam String orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        List<OrderTrajectory> trajectories = mongoTemplate.find(
                new Query(Criteria.where("orderId").is(orderId))
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/base/AbstractJob.java
@@ -2,6 +2,7 @@
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.order.XpCruiseOrder;
import com.xiaopeng.order.service.IXpCruiseOrderService;
@@ -26,6 +27,7 @@
    protected IXpCruiseOrderService xpCruiseOrderService;
    protected OrderMissionClient orderMissionClient;
    protected XpVehicleClient xpVehicleClient;
    protected UserVehicleClient userVehicleClient;
    public AbstractJob(){
        this.xpOrderService = SpringContextsUtil.getBean(IXpOrderService.class);
        this.xpPushMessageClient = SpringContextsUtil.getBean(XpPushMessageClient.class);
@@ -33,6 +35,7 @@
        this.xpCruiseOrderService = SpringContextsUtil.getBean(IXpCruiseOrderService.class);
        this.orderMissionClient = SpringContextsUtil.getBean(OrderMissionClient.class);
        this.xpVehicleClient = SpringContextsUtil.getBean(XpVehicleClient.class);
        this.userVehicleClient = SpringContextsUtil.getBean(UserVehicleClient.class);
    }
     
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/AddCruiseMissionJob.java
@@ -5,6 +5,7 @@
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.order.job.base.AbstractJob;
import lombok.extern.slf4j.Slf4j;
@@ -29,26 +30,23 @@
            throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        Long orderId = maps.getLong("id");
        try {
            log.info("开始执行添加任务");
            XpCruiseOrder xpCruiseOrder = xpCruiseOrderService.getById(orderId);
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpCruiseOrder.getVehicleId());
            if(xpVehicleR.getData().getState()==0){
                PushOrderDto pushOrderDto = new PushOrderDto();
                pushOrderDto.setVin(xpCruiseOrder.getVehicleVin());
                pushOrderDto.setOrderId("XU" + xpCruiseOrder.getId());
                pushOrderDto.setStartLatitude(xpCruiseOrder.getStartLat());
                pushOrderDto.setStartLongitude(xpCruiseOrder.getStartLon());
                pushOrderDto.setEndLatitude(xpCruiseOrder.getEndLat());
                pushOrderDto.setEndLongitude(xpCruiseOrder.getEndLon());
                pushOrderDto.setOrderType(OrderType.CRUISE_ORDER);
                pushOrderDto.setStage(2);
                // 调用推送服务  orderId + carVin
                orderMissionClient.pushOrder(pushOrderDto);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        String appid = maps.getString("appid");
        XpOrder xpOrder = xpOrderService.getById(orderId);
        // 解绑用户与车辆关系
        CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo();
        XpVehicle vehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        if (vehicle != null) {
            cancelTaxiPassengerVo.setVin(vehicle.getVehicleVin());
            cancelTaxiPassengerVo.setUid(xpOrder.getAppUserId().toString());
            cancelTaxiPassengerVo.setOrderNo(xpOrder.getOrderNumber());
            cancelTaxiPassengerVo.setAppId(appid);
            userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo);
            // 修改车辆接单状态
            vehicle.setState(0);
            xpVehicleClient.updateVehicleById(vehicle);
        }
        xpOrderService.addCruiseMission(vehicle, xpOrder);
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/EndTripJob.java
@@ -3,6 +3,7 @@
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.order.XpOrderTimeoutLog;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.order.job.base.AbstractJob;
import lombok.extern.slf4j.Slf4j;
@@ -25,19 +26,18 @@
            throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        Long orderId = maps.getLong("id");
        try {
        try {
            log.info("开始执行自动结束行程任务");
            XpOrder xpOrder = xpOrderService.getById(orderId);
            xpOrderService.endTrip(xpOrder.getId(), String.valueOf(xpOrder.getAppUserId()));
            if(xpOrder.getStatus() == 7){
                xpOrderService.endTrip(xpOrder.getId(), String.valueOf(xpOrder.getAppUserId()));
            }
            log.info("结束执行自动结束行程任务");
            // 添加超时记录
            XpOrderTimeoutLog xpOrderTimeoutLog = new XpOrderTimeoutLog();
            xpOrderTimeoutLog.setOrderId(xpOrder.getId());
            xpOrderTimeoutLog.setUserId(xpOrder.getAppUserId());
            xpOrderTimeoutLogService.save(xpOrderTimeoutLog);
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId());
            xpOrderService.addCruiseMission(xpVehicleR.getData(),xpOrder);
        }catch(Exception e){
            e.printStackTrace();
        }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -216,13 +216,14 @@
            if (userByMobile == null || userByMobile.getId() == null) {
                return buildEmptyOrderPage(query);
            }
            query.setUid(userByMobile.getId());
            query.setUid(userByMobile.getUid());
        }
        // 1. 构建查询条件
        LambdaQueryWrapper<XpOrder> wrapper = buildQueryWrapper(query);
        if (CollUtil.isNotEmpty(vehicleIdsFilter)) {
            wrapper.in(XpOrder::getVehicleId, vehicleIdsFilter);
        }
        wrapper.eq(Objects.nonNull(query.getUid()), XpOrder::getAppUserId, query.getUid());
        wrapper.orderByDesc(XpOrder::getCreateTime);
        // 2. 分页查询
@@ -366,8 +367,10 @@
            if (userByMobile == null || userByMobile.getId() == null) {
                return new ArrayList<>();
            }
            query.setUid(userByMobile.getId());
            query.setUid(userByMobile.getUid());
        }
        wrapper.eq(Objects.nonNull(query.getUid()), XpOrder::getAppUserId, query.getUid());
        wrapper.orderByDesc(XpOrder::getCreateTime);
        // 2. 查询订单列表
        List<XpOrder> orderList = this.list(wrapper);
@@ -1378,7 +1381,9 @@
        if (xpOrder.getStatus() == 0) {
            return R.fail("订单已取消");
        }
        xpOrder.setStatus(status);
        if(xpOrder.getStatus() >= 8){
            return R.fail("订单已结束");
        }
        switch (status) {
            case 4:
                // 查询起点点位
@@ -1409,37 +1414,51 @@
                xpOrder.setTripBegin(LocalDateTime.now());
                break;
            case 7:
                // 查询自动结束行程时间
                if (xpOrder.getOpenTrunk() != 1) {
                    Integer arriveTime = xpSystemSettingClient.getOrderFinishTime().getData();
                    // 添加自动结束行程定时任务job
                    Map<String, ? extends Object> endTripJobMaps =
                            new ImmutableMap.Builder<String, Long>().
                                    put("id", xpOrder.getId())
                                    .build();
                    QuartzManager.addJob(
                            EndTripJob.class,
                            (EndTripJob.name + xpOrder.getId()).toUpperCase(),
                            TimeJobType.END_TRIP,
                            DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(arriveTime)),
                            endTripJobMaps
                    );
                    // 推送倒计时消息
                    Map<String, Object> params = new HashMap<>();
                    Map<String, Object> content = new HashMap<>();
                    content.put("orderId", xpOrder.getId().toString());
                    content.put("isOpen", 1);
                    params.put("type", PushMessageTypeEnum.OPEN_TRUNK.getDesc());
                    params.put("content", JSONObject.toJSONString(content));
                    params.put("timestamp", new Date().getTime() + "");
                    String message = JSONObject.toJSONString(params);
                    SendMessageDTO sendMessageDTO = new SendMessageDTO();
                    sendMessageDTO.setMessage(message);
                    sendMessageDTO.setAppUserId(String.valueOf(xpOrder.getAppUserId()));
                    xpPushMessageClient.sendStatus(sendMessageDTO);
                }
                Integer arriveTime = xpSystemSettingClient.getOrderFinishTime().getData();
                // 添加自动结束行程定时任务job
                Map<String, ? extends Object> endTripJobMaps =
                        new ImmutableMap.Builder<String, String>()
                                .put("id", xpOrder.getId().toString())
                                .put("appid", appid)
                                .build();
                QuartzManager.addJob(
                        EndTripJob.class,
                        (EndTripJob.name + xpOrder.getId()).toUpperCase(),
                        TimeJobType.END_TRIP,
                        DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(arriveTime)),
                        endTripJobMaps
                );
                // 添加车辆到达终点后限停时间到后驶离的定时任务
                //时间到后添加巡游订单,解绑车辆关系,车辆驶离点位
                XpPoint point = xpPointClient.queryById(xpOrder.getEndPointId()).getData();
                Map<String, ? extends Object> jobMaps =
                        new ImmutableMap.Builder<String, String>()
                                .put("id", xpOrder.getId().toString())
                                .put("appid", appid)
                                .build();
                QuartzManager.addJob(
                        AddCruiseMissionJob.class,
                        (AddCruiseMissionJob.name + xpOrder.getId()).toUpperCase(),
                        TimeJobType.ADD_CRUISE_MISSION,
                        DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(point.getParkingTimeLimit())),
                        jobMaps
                );
                // 推送倒计时消息
                Map<String, Object> params = new HashMap<>();
                Map<String, Object> content = new HashMap<>();
                content.put("orderId", xpOrder.getId().toString());
                content.put("isOpen", 1);
                params.put("type", PushMessageTypeEnum.OPEN_TRUNK.getDesc());
                params.put("content", JSONObject.toJSONString(content));
                params.put("timestamp", new Date().getTime() + "");
                String message = JSONObject.toJSONString(params);
                SendMessageDTO sendMessageDTO = new SendMessageDTO();
                sendMessageDTO.setMessage(message);
                sendMessageDTO.setAppUserId(String.valueOf(xpOrder.getAppUserId()));
                xpPushMessageClient.sendStatus(sendMessageDTO);
                break;
        }
        xpOrder.setStatus(status);
        xpOrder.setAppUserId(null);
        this.updateById(xpOrder);
        return R.ok();
@@ -1474,24 +1493,7 @@
        exactMinutes = new BigDecimal(exactMinutes).setScale(2, RoundingMode.HALF_UP).doubleValue();
        order.setDuration(exactMinutes);
//        order.setMileageKilometers(80.0);
        baseMapper.updateById(order);
        // 解绑用户与车辆关系
        CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo();
        XpVehicle vehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
        if (vehicle != null) {
            cancelTaxiPassengerVo.setVin(vehicle.getVehicleVin());
            cancelTaxiPassengerVo.setUid(uid);
            cancelTaxiPassengerVo.setOrderNo(order.getOrderNumber());
            // TODO 加配置文件取
            cancelTaxiPassengerVo.setAppId(appid);
            userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo);
            // 修改车辆接单状态
            vehicle.setState(0);
            xpVehicleClient.updateVehicleById(vehicle);
        }
        // 根据订单所属开通城市id 查询定价
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId());
@@ -1511,9 +1513,6 @@
            TaxiPricingDemoService.GpsPoint point = new TaxiPricingDemoService.GpsPoint(trajectory.getDistance(), trajectory.getLocatetime());
            gpsPoints.add(point);
        }
//         补充临时轨迹数据
//        List<TaxiPricingDemoService.GpsPoint> gpsPoints = TaxiPricingDemoService.createMockGpsPoints(DateUtils.toDate(order.getCreateTime()));
        TaxiPricingDemoService.PricingConfig config = new TaxiPricingDemoService.PricingConfig();
        JSONArray startingFareJsonArray = JSONArray.parseArray(pricing.getStartingFareJson());
@@ -1532,16 +1531,7 @@
            Double duration1 = order.getDuration();
            com.alibaba.fastjson.JSONObject basicPricingJson = com.alibaba.fastjson.JSONObject.parseObject(pricing.getBasicPricingJson());
//            String num1 = "00:00";
//            String num2 = "23:59";
//            String num3 = basicPricingJson.getString("num1");
//            String num4 = basicPricingJson.getString("num2");
//            String num5 = basicPricingJson.getString("num3");
//            String num6 = basicPricingJson.getString("num4");
//            String num7 = basicPricingJson.getString("num5");
//            config.normals.add(new TaxiPricingDemoService.PricingRule("普通时段", num1, num2, Double.parseDouble(num3), Double.parseDouble(num4), Double.parseDouble(num5), Double.parseDouble(num6), Double.parseDouble(num7)));
//            paramNum4 = basicPricingJson.getDouble("num2");
//            paramNum5 = basicPricingJson.getDouble("num3");
            XpPricingCardVo xpPricingCardVo = xpPricingUtil.calculationFixedPrice(orderAppUserId.toString(), basicPricingJson, null, mileageKilometers, duration1, 0, 0.0, pricing.getIsDynamicPrice(), order.getCityId(), pricing.getDynamicPricingJson());
            XpOrderDetail xpOrderDetail = new XpOrderDetail();
            xpOrderDetail.setDetailType(1);
@@ -1574,11 +1564,7 @@
            order.setOrderMoney(totalPrice);
            order.setAppUserId(null);
            baseMapper.updateById(order);
        } else {
            //动态计费
            if (pricing.getPricingType() == 1) {
@@ -1828,18 +1814,7 @@
                BigDecimal add = order.getDurationMoney().add(fareBreakdownDetailVo.getMoney());
                order.setDurationMoney(add);
            }
//        for (XpOrderDetail xpOrderDetail : xpOrderDetails) {
//            if (xpOrderDetail.getDetailType() == 1) {
//                order.setDurationType(xpOrderDetail.getType());
//                order.setDurationMoney(xpOrderDetail.getMoney());
////                order.setDuration(Double.parseDouble(xpOrderDetail.getDuration() + ""));
//            } else {
//                order.setLongDistanceType(order.getDurationType());
//                order.setLongDistance(xpOrderDetail.getMileage().doubleValue());
//                order.setLongDistancemoney(xpOrderDetail.getMoney());
//            }
//
//        }
            BigDecimal orderMoney = startMoney.add(mileageMoney).add(order.getDurationMoney() != null ? order.getDurationMoney() : BigDecimal.ZERO)
                    .add(order.getLongDistancemoney() != null ? order.getLongDistancemoney() : BigDecimal.ZERO)
@@ -1872,87 +1847,6 @@
        }
        order.setAppUserId(null);
        baseMapper.updateById(order);
        // 结束后的车辆操作
        if (vehicle != null) {
            // 修改车辆状态
            vehicle.setState(0);
            //发送巡游订单
            addCruiseMission(vehicle, order);
//            // 获取配置 根据配置判断是否该收车 如果是则找到收车点位进行收车操作
//            R<XpTaskRule> xpTaskRuleR = xpTaskRuleClient.queryDetail();
//            XpTaskRule xpTaskRule = xpTaskRuleR.getData();
//
//            // 生成新的巡游单子 回到最新巡游的初始起点位置
//            XpCruiseOrder xpCruiseOrders = xpCruiseOrderMapper.selectOne(new LambdaQueryWrapper<XpCruiseOrder>().eq(XpCruiseOrder::getVehicleId, vehicle.getId()).eq(XpCruiseOrder::getStatus, 2).orderByDesc(BaseModel::getCreateTime).last("limit 1"));
//            int temp=0;
//            if(xpCruiseOrders==null){
//                temp=1;
//                //特殊情况 可能没有产生终止的订单  (到站点就被正常接单了)所以找正常订单
//                xpCruiseOrders=xpCruiseOrderMapper.selectOne(new LambdaQueryWrapper<XpCruiseOrder>().eq(XpCruiseOrder::getVehicleId, vehicle.getId()).eq(XpCruiseOrder::getStatus, 3).orderByDesc(BaseModel::getCreateTime).last("limit 1"));
//            }
//            XpCruiseOrder xpCruiseOrder = new XpCruiseOrder();
//            xpCruiseOrder.setVehicleId(vehicle.getId());
//            xpCruiseOrder.setFleetId(vehicle.getFleetId());
//            xpCruiseOrder.setVehicleVin(vehicle.getVehicleVin());
//            xpCruiseOrder.setOrderType(2);
//            // 返程起点是当前正常订单的终点
//            xpCruiseOrder.setStartPointId(order.getEndPointId());
//            xpCruiseOrder.setStartPointName(order.getEndPointName());
//            xpCruiseOrder.setStartLon(order.getEndLon());
//            xpCruiseOrder.setStartLat(order.getEndLat());
//
//            // 完成的直接走终点   终止的走起点
//            if(temp==0){
//                xpCruiseOrder.setEndPointId(xpCruiseOrders.getStartPointId());
//                xpCruiseOrder.setEndPointName(xpCruiseOrders.getStartPointName());
//                xpCruiseOrder.setEndLon(xpCruiseOrders.getStartLon());
//                xpCruiseOrder.setEndLat(xpCruiseOrders.getStartLat());
//            }else {
//                xpCruiseOrder.setEndPointId(xpCruiseOrders.getEndPointId());
//                xpCruiseOrder.setEndPointName(xpCruiseOrders.getEndPointName());
//                xpCruiseOrder.setEndLon(xpCruiseOrders.getEndLon());
//                xpCruiseOrder.setEndLat(xpCruiseOrders.getEndLat());
//            }
//
//
//            xpCruiseOrder.setStatus(0);
//            xpCruiseOrder.setLastOrderId(xpCruiseOrders.getId());
//            xpCruiseOrder.setCreateTime(LocalDateTime.now());
//            xpCruiseOrder.setId("XU"+ IdUtil.fastUUID());
//
//            // 判断当前时分秒是否大于收车时间
//            LocalTime vehicleCollectionTime = xpTaskRule.getVehicleCollectionTime();
//            if (vehicleCollectionTime.isBefore(LocalTime.now())) {
//                vehicle.setOperatingStatus(3);
//                //找到车辆的收车点位 更新终点点位
//                R<XpCruiseOrder> returnCruisePoint = xpCruisePlanClient.findReturnCruisePoint(xpCruiseOrder);
//                xpCruiseOrder = returnCruisePoint.getData();
//            }
//            xpVehicleClient.updateVehicleById(vehicle);
//
//            xpCruiseOrderMapper.insert(xpCruiseOrder);
//
//
//            R<XpPoint> xpPointR = xpPointClient.queryById(order.getEndPointId());
//            XpPoint data = xpPointR.getData();
//            //下发命令
//            // 添加自动结束行程定时任务job
//            Map<String, ? extends Object> jobMaps =
//                    new ImmutableMap.Builder<String, String>().
//                            put("id", xpCruiseOrder.getId())
//                            .build();
//            QuartzManager.addJob(
//                    AddCruiseMissionJob.class,
//                    (AddCruiseMissionJob.name + xpCruiseOrder.getId()).toUpperCase(),
//                    TimeJobType.ADD_CRUISE_MISSION,
//                    DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(data.getParkingTimeLimit())),
//                    jobMaps
//            );
        }
    }
@@ -1964,6 +1858,7 @@
     */
    public void addCruiseMission(XpVehicle vehicle, XpOrder order) {
        log.info("添加巡游订单 {}", vehicle.getPlateNumber());
        if (vehicle.getState() != 0 || vehicle.getOperatingStatus() != 1) {
            log.info("添加巡游单失败,车辆状态限制 {}", vehicle.getState());
            return;
@@ -2048,25 +1943,20 @@
            }
            xpVehicleClient.updateVehicleById(vehicle);
        }
        xpCruiseOrderMapper.insert(xpCruiseOrder);
        R<XpPoint> xpPointR = xpPointClient.queryById(order.getEndPointId());
        XpPoint data = xpPointR.getData();
        //下发命令
        // 添加自动结束行程定时任务job
        Map<String, ? extends Object> jobMaps =
                new ImmutableMap.Builder<String, String>().
                        put("id", xpCruiseOrder.getId() + "")
                        .build();
        QuartzManager.addJob(
                AddCruiseMissionJob.class,
                (AddCruiseMissionJob.name + xpCruiseOrder.getId()).toUpperCase(),
                TimeJobType.ADD_CRUISE_MISSION,
                DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(data.getParkingTimeLimit())),
                jobMaps
        );
        log.info("添加巡游单成功 {}", xpCruiseOrder.getId());
        //下发XUP巡游订单报文
        PushOrderDto pushOrderDto = new PushOrderDto();
        pushOrderDto.setVin(xpCruiseOrder.getVehicleVin());
        pushOrderDto.setOrderId("XU" + xpCruiseOrder.getId());
        pushOrderDto.setStartLatitude(xpCruiseOrder.getStartLat());
        pushOrderDto.setStartLongitude(xpCruiseOrder.getStartLon());
        pushOrderDto.setEndLatitude(xpCruiseOrder.getEndLat());
        pushOrderDto.setEndLongitude(xpCruiseOrder.getEndLon());
        pushOrderDto.setOrderType(OrderType.CRUISE_ORDER);
        pushOrderDto.setStage(2);
        orderMissionClient.pushOrder(pushOrderDto);
        log.info("下发XUP巡游订单报文成功");
    }
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java
@@ -104,7 +104,7 @@
                        .targetValue(account)
                        .title(title)
                        .body(content)
                        .androidExtParameters(body)
//                        .androidExtParameters(body)
                        .androidNotificationChannel(channelIds)
                        .build();
                if(deviceType == 2){
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/XpVehicleVO.java
@@ -24,7 +24,7 @@
    private String fleetName;
    @ApiModelProperty(value = "下次保养时间")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    @JsonFormat(pattern = "yyyy年M月dd日",timezone = "GMT+8")
    private LocalDate nextMaintenanceTime;
    @ApiModelProperty(value = "车辆经度")