puzhibing
2026-02-28 29a93fd7f24250b5dd854a150252bf0722c890ce
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi
12个文件已添加
37个文件已修改
2991 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserNotificationController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpOperationsUserNotificationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpOperationsUserNotificationServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/SysUserMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/AliPushMessageDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpOperationUserNotificationClientFallbackFactory.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserNotificationClient.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/SysUser.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTaskRule.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpHistoryEndPointsVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpHistoryStartPointsVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/AliPushTypeConstant.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/NotificationTypeEnum.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/TaskTypeNotificationEnum.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/constant/I18nKeyConstant.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/constant/TaskScheduleConstants.java 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/InsuranceInspectionScheduleTask.java 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/RoutinePreparationScheduleTask.java 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleCleanScheduleTask.java 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleCollectionScheduleTask.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleInspectionScheduleTask.java 355 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleMaintenanceScheduleTask.java 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_en_US.properties 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_zh_CN.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpInvitationCodeController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpRegionController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpCruisePlanServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpRegionServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderHistoryListVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaxiPricingDemoService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/resources/mapper/order/XpOrderMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserController.java
@@ -200,7 +200,7 @@
    }
    @ApiOperation(value = "运维人员管理重置密码", tags = {"管理后台-运维执行"})
    @DeleteMapping("/api/v1/operationsUser/rePassword")
    @PostMapping("/api/v1/operationsUser/rePassword")
    public R<String> rePassword(@RequestBody RePasswordDTO dto) {
        xpOperationsUserService.rePassword(dto);
        return R.ok();
@@ -215,7 +215,7 @@
    @ApiOperation(value = "运维人员管理导出列表", tags = {"管理后台-运维执行"})
    @GetMapping("/api/v1/operationsUser/exportOperationsUser")
    @PostMapping("/api/v1/operationsUser/exportOperationsUser")
    public void exportOperationsUser(@RequestBody XpOperationsUserQuery query, HttpServletResponse response){
        xpOperationsUserService.exportOperationsUser(query, response);
    }
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserNotificationController.java
@@ -11,6 +11,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * 运维人员消息通知表 前端控制器
@@ -70,4 +72,11 @@
        notificationService.lambdaUpdate().set(XpOperationsUserNotification::getReadStatus, 1).eq(XpOperationsUserNotification::getId, notificationId).update();
        return R.ok();
    }
    @PostMapping(value = "/inner/v1/notification/addBatch")
    @ApiOperation(value = "批量添加通知", tags = {"内部接口"})
    public R<?> addNotificationBatch(@RequestBody List<XpOperationsUserNotification> notifications) {
        notificationService.addNotificationBatch(notifications);
        return R.ok();
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpOperationsUserNotificationService.java
@@ -5,6 +5,8 @@
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.common.core.web.page.PageInfo;
import java.util.List;
/**
 * <p>
 * 运维人员消息通知表 服务类
@@ -23,4 +25,10 @@
    PageInfo<XpOperationsUserNotification> selectNotificationPageByUserId(
        XpOperationsUserNotificationPageQuery query
    );
    /**
     * 批量添加通知
     * @param notifications
     */
    void addNotificationBatch(List<XpOperationsUserNotification> notifications);
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpOperationsUserNotificationServiceImpl.java
@@ -7,7 +7,12 @@
import com.xiaopeng.account.service.IXpOperationsUserNotificationService;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * <p>
@@ -43,4 +48,10 @@
        return pageInfo;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    @ShardingSphereTransactionType(TransactionType.BASE)
    public void addNotificationBatch(List<XpOperationsUserNotification> notifications) {
        saveBatch(notifications);
    }
}
xp-robotaxi-account-boot/src/main/resources/mapper/account/SysUserMapper.xml
@@ -115,7 +115,7 @@
    <select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
        select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
    </select>
    <select id="getList" resultMap="SysUserResult">
    <select id="getList" resultType="com.xiaopeng.api.model.account.SysUser">
        select su.*,sr.role_name, sur.role_id from sys_user su
        left join sys_user_role sur on su.id= sur.user_id
        left join sys_role sr on sr.id = sur.role_id where 1=1
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/AliPushMessageDTO.java
@@ -2,11 +2,14 @@
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Builder
@Accessors(chain = true)
@ApiModel(value = "ali推送消息AliPushMessageDTO")
public class AliPushMessageDTO implements Serializable {
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpOperationUserNotificationClientFallbackFactory.java
New file
@@ -0,0 +1,32 @@
package com.xiaopeng.api.factory.account;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
public class XpOperationUserNotificationClientFallbackFactory implements FallbackFactory<XpOperationUserNotificationClient> {
    @Override
    public XpOperationUserNotificationClient create(Throwable cause) {
        return new XpOperationUserNotificationClient() {
            @Override
            public R<?> addNotificationBatch(List<XpOperationsUserNotification> notifications) {
                return null;
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java
@@ -14,6 +14,7 @@
import com.xiaopeng.api.query.operation.PointPageListListQuery;
import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo;
import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo;
import com.xiaopeng.api.vo.order.XpPointInfoVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import lombok.extern.slf4j.Slf4j;
@@ -23,6 +24,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@Slf4j
@Component
@@ -101,6 +103,12 @@
                log.error("获取地图点位失败:{}", cause.getMessage());
                return R.ok(new ArrayList<>());
            }
            @Override
            public R<List<XpPoint>> getPointsInfo(Set<Long> points) {
                log.error("获取点位信息失败:{}", cause.getMessage());
                return null;
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserNotificationClient.java
New file
@@ -0,0 +1,37 @@
package com.xiaopeng.api.feignClient.account;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.factory.account.XpAppUserClientFallbackFactory;
import com.xiaopeng.api.factory.account.XpOperationUserNotificationClientFallbackFactory;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
 * 小程序用户Feign客户端
 *
 * @author mitao
 */
@FeignClient(contextId = "XpOperationUserNotificationClient",
        value = ServiceNameConstantsEnum.XP_ACCOUNT_SERVICE,
        fallbackFactory = XpOperationUserNotificationClientFallbackFactory.class)
public interface XpOperationUserNotificationClient {
    /**
     * 批量添加运维用户
     * @param notifications
     * @return
     */
    @PostMapping(value = "/inner/v1/notification/addBatch")
    public R<?> addNotificationBatch(@RequestBody List<XpOperationsUserNotification> notifications);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java
@@ -14,6 +14,7 @@
import com.xiaopeng.api.query.operation.PointPageListListQuery;
import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo;
import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo;
import com.xiaopeng.api.vo.order.XpPointInfoVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import com.xiaopeng.common.core.web.page.PageInfo;
@@ -22,6 +23,7 @@
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
@FeignClient(contextId = "XpPointClient", value = ServiceNameConstantsEnum.XP_OPERATION_SERVICE, fallbackFactory = XpPointClientFallbackFactory.class)
public interface XpPointClient {
@@ -74,6 +76,7 @@
    R<List<XpPoint>> pointMap(@RequestBody PointMapQuery query);
    @PostMapping("/base/v1/mini-program/inner/point/getPointsInfo")
    R<List<XpPoint>> getPointsInfo(@RequestBody Set<Long> points);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/SysUser.java
@@ -86,6 +86,10 @@
    @TableField(exist = false)
    private Long deptId;
    @ApiModelProperty(value = "角色名称")
    @TableField(exist = false)
    private String roleName;
    public boolean isAdmin()
    {
        return isAdmin(this.id);
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTaskRule.java
@@ -4,9 +4,11 @@
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.LocalTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -42,7 +44,8 @@
    private Integer vehicleInspectionFrequency;
    @ApiModelProperty(value = "车辆日常检测 任务下放时间")
    private LocalDateTime vehicleInspectionTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleInspectionTime;
    @ApiModelProperty(value = "车辆日常监测 紧急程度 0=P0 1=P1 2=P2 3=P3")
    private Integer vehicleInspectionLevel;
@@ -51,7 +54,8 @@
    private Boolean vehicleCollection;
    @ApiModelProperty(value = "车辆收车每日时间")
    private LocalDateTime vehicleCollectionTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleCollectionTime;
    @ApiModelProperty(value = "车辆保养周期是否启用")
    private Boolean maintenanceCycle;
@@ -63,7 +67,8 @@
    private Integer maintenanceCycleTimeType;
    @ApiModelProperty(value = "车辆保养周期 任务下放时间")
    private LocalDateTime maintenanceCycleTaskTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime maintenanceCycleTaskTime;
    @ApiModelProperty(value = "车辆保养周期 紧急程度 0=P0 1=P1 2=P2 3=P3")
    private Integer maintenanceCycleLevel;
@@ -78,7 +83,11 @@
    private Integer chargingStrategyLevel;
    @ApiModelProperty(value = "车身清洁保养周期X")
    private Integer vehicleCleanTime;
    private Integer vehicleCleanCycle;
    @ApiModelProperty(value = "车身清洁保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleCleanTime;
    @ApiModelProperty(value = "车身清洁保养周期X 1=天 2=月")
    private Integer vehicleCleanTimeType;
@@ -87,7 +96,11 @@
    private Integer vehicleCleanLevel;
    @ApiModelProperty(value = "日常整备保养周期X")
    private Integer routineMaintenanceTime;
    private Integer routineMaintenanceCycle;
    @ApiModelProperty(value = "日常整备保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime routineMaintenanceTime;
    @ApiModelProperty(value = "日常整备保养周期X 1=天 2=月")
    private Integer routineMaintenanceTimeType;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpHistoryEndPointsVo.java
@@ -25,4 +25,10 @@
    @ApiModelProperty("站点id")
    private Long id;
    @ApiModelProperty("停靠时间")
    private Integer time;
    private Long electronicAreaId;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpHistoryStartPointsVo.java
@@ -25,4 +25,10 @@
    @ApiModelProperty("站点id")
    private Long id;
    @ApiModelProperty("停靠时间")
    private Integer time;
    private Long electronicAreaId;
}
xp-robotaxi-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -15,6 +15,7 @@
com.xiaopeng.api.factory.account.SysUserRoleClientFallbackFactory
com.xiaopeng.api.factory.account.SysUserClientFallbackFactory
com.xiaopeng.api.factory.account.XpOperationUserClientFallbackFactory
com.xiaopeng.api.factory.account.XpOperationUserNotificationClientFallbackFactory
com.xiaopeng.api.factory.operation.XpElectronicClientFallbackFactory
com.xiaopeng.api.factory.operation.XpOpenCityClientFallbackFactory
com.xiaopeng.api.factory.operation.XpCouponClientFallbackFactory
@@ -38,4 +39,5 @@
com.xiaopeng.api.factory.operation.SmsClientFallbackFactory
com.xiaopeng.api.factory.maintenance.XpTaskHandleDetailRoutineClientFallbackFactory
com.xiaopeng.api.factory.maintenance.XpTaskClientFallbackFactory
com.xiaopeng.api.factory.maintenance.XpTaskRuleClientFallbackFactory
com.xiaopeng.api.factory.maintenance.XpTaskRuleClientFallbackFactory
com.xiaopeng.api.factory.pushMessage.XpAliPushMessageClientFallbackFactory
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/AliPushTypeConstant.java
New file
@@ -0,0 +1,18 @@
package com.xiaopeng.common.core.constant;
/**
 * @author MiTao
 * @date 2026-02-27 18:03
 *
 */
public interface AliPushTypeConstant {
    /**
     * 表示通知
     */
    String NOTICE = "NOTICE";
    /**
     * 表示消息
     */
    String MESSAGE = "MESSAGE";
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/NotificationTypeEnum.java
New file
@@ -0,0 +1,48 @@
package com.xiaopeng.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * 通知类型枚举
 * 1=新增事故,2=事故即将超时提示,3=事故超时提示,4=排班信息变动,5=事故状态变更,
 * 6=新增任务,7=任务过期提示,8=版本更新提示,9=变更协议政策内容,
 * 10=变更协议内容并需用户授权,11=打卡成功通知,12=强制更新
 */
@Getter
@AllArgsConstructor
public enum NotificationTypeEnum {
    ACCIDENT_NEW(1, "新增事故"),
    ACCIDENT_WILL_TIMEOUT(2, "事故即将超时提示"),
    ACCIDENT_TIMEOUT(3, "事故超时提示"),
    SCHEDULE_CHANGED(4, "排班信息变动"),
    ACCIDENT_STATUS_CHANGED(5, "事故状态变更"),
    TASK_NEW(6, "新增任务"),
    TASK_EXPIRED(7, "任务过期提示"),
    VERSION_UPDATE_TIP(8, "版本更新提示"),
    POLICY_CONTENT_CHANGED(9, "变更协议政策内容"),
    AGREEMENT_CHANGED_NEED_AUTH(10, "变更协议内容并需用户授权"),
    CLOCK_IN_SUCCESS(11, "打卡成功通知"),
    FORCE_UPDATE(12, "强制更新");
    private final Integer code;
    private final String desc;
    public static NotificationTypeEnum getEnumByCode(Integer code) {
        if (code == null) {
            return null;
        }
        for (NotificationTypeEnum e : NotificationTypeEnum.values()) {
            if (e.code.equals(code)) {
                return e;
            }
        }
        return null;
    }
    public static String getEnumDescByCode(Integer code) {
        NotificationTypeEnum e = getEnumByCode(code);
        return e == null ? "" : e.getDesc();
    }
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/TaskTypeNotificationEnum.java
New file
@@ -0,0 +1,37 @@
package com.xiaopeng.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TaskTypeNotificationEnum {
    VEHICLE_INSPECTION(1, "车辆检测"),
    VEHICLE_CHARGING(2, "车辆充电"),
    VEHICLE_PREPARATION(3, "车辆整备"),
    VEHICLE_WASH(4, "整备洗车"),
    VEHICLE_REPAIR(5, "车辆维修"),
    OTHER_ROUTINE_TASK(6, "其他常规任务"),
    ACCIDENT_TASK(7, "事故任务");
    private final Integer code;
    private final String desc;
    public static TaskTypeNotificationEnum getEnumByCode(Integer code) {
        if (code == null) {
            return null;
        }
        for (TaskTypeNotificationEnum e : TaskTypeNotificationEnum.values()) {
            if (e.code.equals(code)) {
                return e;
            }
        }
        return null;
    }
    public static String getEnumDescByCode(Integer code) {
        TaskTypeNotificationEnum e = getEnumByCode(code);
        return e == null ? "" : e.getDesc();
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.account.GetUserCouponDto;
import com.xiaopeng.api.dto.map.OrderRouteAndFareDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
@@ -468,14 +469,14 @@
        // 根据id获取手机号
        Boolean isPriorityDispatch = false;
        R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
        if (xpAppUserR.getCode() == R.SUCCESS) {
            Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
            if (count > 0) {
                isPriorityDispatch = true;
            }
        }
        Boolean isPriorityDispatch = true;
//        R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
//        if (xpAppUserR.getCode() == R.SUCCESS) {
//            Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
//            if (count > 0) {
//                isPriorityDispatch = true;
//            }
//        }
        // 生成订单数据
        XpOrder xpOrder = new XpOrder();
@@ -709,7 +710,7 @@
            dto.setMessage("{\"type\":\"VEHICLE_LOCATION\",\"content\":\"{\\\"orderId\\\":\\\"" + orderId + "\\\",\\\"lon\\\":113.385848,\\\"lat\\\":23.156841}\",\"timestamp\":\"176569945845456\"}");
            xpPushMessageClient.sendVehicleLocation(dto);
            //发送车辆到达起点的轨迹
            RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
            OrderRouteAndFareDto routeAndFareDto = new OrderRouteAndFareDto();
            LonLatDto destination = new LonLatDto();
            destination.setLongitude(xpOrder.getStartLon());
            destination.setLatitude(xpOrder.getStartLat());
@@ -718,8 +719,9 @@
            origin.setLongitude(113.385848);
            origin.setLatitude(23.156841);
            routeAndFareDto.setOrigin(origin);
            routeAndFareDto.setOpenCityId(2L);
            RouteVo routeVo = xpMapClient.getDistanceAndDuration(routeAndFareDto).getData();
            routeAndFareDto.setRouteType(1);
            routeAndFareDto.setOrderId(orderId);
            RouteVo routeVo = xpMapClient.getOrderDistanceAndDuration(routeAndFareDto).getData();
            com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
            jsonObject.put("type", "ORDER_ROUTE");
            jsonObject.put("timestamp", System.currentTimeMillis());
@@ -782,12 +784,12 @@
            dto.setMessage("{\"type\":\"ORDER_STATUS\",\"content\":\"{\\\"orderId\\\":\\\"" + orderId + "\\\",\\\"status\\\":5}\",\"timestamp\":\"176569945845456\"}");
            xpPushMessageClient.sendStatus(dto);
            //推送起点到终点的行驶轨迹
            RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
            routeAndFareDto = new RouteAndFareDto();
            OrderRouteAndFareDto routeAndFareDto = new OrderRouteAndFareDto();
            routeAndFareDto.setDestination(new LonLatDto(xpOrder.getEndLon(), xpOrder.getEndLat()));
            routeAndFareDto.setOrigin(new LonLatDto(xpOrder.getStartLon(), xpOrder.getStartLat()));
            routeAndFareDto.setOpenCityId(2L);
            RouteVo routeVo = xpMapClient.getDistanceAndDuration(routeAndFareDto).getData();
            routeAndFareDto.setRouteType(2);
            routeAndFareDto.setOrderId(orderId);
            RouteVo routeVo = xpMapClient.getOrderDistanceAndDuration(routeAndFareDto).getData();
            com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
            jsonObject.put("type", "ORDER_ROUTE");
            jsonObject.put("timestamp", System.currentTimeMillis());
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java
@@ -264,14 +264,18 @@
        if (holiday) {
            if (realDistance > rule.getDouble("num6")) {
                longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6"));
                longDistanceOne = new BigDecimal(longDistanceOne).setScale(2,RoundingMode.HALF_UP).doubleValue();
            }
            if (realDistance > rule.getDouble("num9")) {
                longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9"));
                longDistanceTwo = new BigDecimal(longDistanceTwo).setScale(2,RoundingMode.HALF_UP).doubleValue();
            }
            if (realDistance > rule.getDouble("num12")) {
                longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12"));
                longDistanceThree = new BigDecimal(longDistanceThree).setScale(2,RoundingMode.HALF_UP).doubleValue();
            }
            amount = rule.getDouble("num29") + new BigDecimal(exceedDistance * rule.getDouble("num30")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
            amount = rule.getDouble("num29") + new BigDecimal(exceedDistance * rule.getDouble("num30")).setScale(2,RoundingMode.HALF_UP).doubleValue() + new BigDecimal(exceedTime * rule.getDouble("num31")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
            // 起步价
@@ -299,14 +303,17 @@
            if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) {
                if (realDistance > rule.getDouble("num6")) {
                    longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6"));
                    longDistanceOne = new BigDecimal(longDistanceOne).setScale(2,RoundingMode.HALF_UP).doubleValue();
                }
                if (realDistance > rule.getDouble("num9")) {
                    longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9"));
                    longDistanceTwo = new BigDecimal(longDistanceTwo).setScale(2,RoundingMode.HALF_UP).doubleValue();
                }
                if (realDistance > rule.getDouble("num12")) {
                    longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12"));
                    longDistanceThree = new BigDecimal(longDistanceThree).setScale(2,RoundingMode.HALF_UP).doubleValue();
                }
                amount = rule.getDouble("num15") + new BigDecimal(exceedDistance * rule.getDouble("num16")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                amount = rule.getDouble("num15") + new BigDecimal(exceedDistance * rule.getDouble("num16")).setScale(2,RoundingMode.HALF_UP).doubleValue() + new BigDecimal(exceedTime * rule.getDouble("num17")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                // 起步价
@@ -355,14 +362,17 @@
                if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) {
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6"));
                        longDistanceOne = new BigDecimal(longDistanceOne).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9"));
                        longDistanceTwo = new BigDecimal(longDistanceTwo).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12"));
                        longDistanceThree = new BigDecimal(longDistanceThree).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    amount = rule.getDouble("num23") + new BigDecimal(exceedDistance * rule.getDouble("num24")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    amount = rule.getDouble("num23") + new BigDecimal(exceedDistance * rule.getDouble("num24")).setScale(2,RoundingMode.HALF_UP).doubleValue() + new BigDecimal(exceedTime * rule.getDouble("num25")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
@@ -393,14 +403,17 @@
                    //其他时间段的计算
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6"));
                        longDistanceOne = new BigDecimal(longDistanceOne).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9"));
                        longDistanceTwo = new BigDecimal(longDistanceTwo).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12"));
                        longDistanceThree = new BigDecimal(longDistanceThree).setScale(2,RoundingMode.HALF_UP).doubleValue();
                    }
                    amount = rule.getDouble("num1") + new BigDecimal(exceedDistance * rule.getDouble("num4")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    amount = rule.getDouble("num1") + new BigDecimal(exceedDistance * rule.getDouble("num4")).setScale(2,RoundingMode.HALF_UP).doubleValue() + new BigDecimal(exceedTime * rule.getDouble("num5")).setScale(2,RoundingMode.HALF_UP).doubleValue() + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
@@ -433,6 +446,7 @@
        // 订单总额
        Double startPrice = xpPricingCardVo.getStartPrice();
        amount=new BigDecimal(amount).setScale(2,RoundingMode.HALF_UP).doubleValue();
        // 先减去起步价重新计算
        amount = amount - startPrice;
        // 动态计算费用
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/constant/I18nKeyConstant.java
@@ -11,4 +11,5 @@
    String VEHICLE_NOT_EXIST = "vehicle.not.exist";
    String TASK_RULE_NOT_CONFIG = "task.rule.not.config";
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/constant/TaskScheduleConstants.java
New file
@@ -0,0 +1,169 @@
package com.xiaopeng.maintenance.constant;
/**
 * 定时任务常量类
 *
 * @author mitao
 * @since 2026-02-27
 */
public class TaskScheduleConstants {
    private TaskScheduleConstants() {
        // 私有构造函数,防止实例化
    }
    /**
     * 任务类别 - 常规任务
     */
    public static final Integer TASK_CATEGORY_ROUTINE = 1;
    /**
     * 任务类别 - 事故任务
     */
    public static final Integer TASK_CATEGORY_ACCIDENT = 2;
    /**
     * 任务类型 - 车辆检查
     */
    public static final String TASK_TYPE_DETECTION = "1";
    /**
     * 任务类型 - 车辆充电
     */
    public static final String TASK_TYPE_CHARGING = "2";
    /**
     * 任务类型 - 车辆整备
     */
    public static final String TASK_TYPE_PREPARATION = "3";
    /**
     * 任务类型 - 整备洗车
     */
    public static final String TASK_TYPE_CLEAN = "4";
    /**
     * 任务类型 - 车辆维修
     */
    public static final String TASK_TYPE_REPAIR = "5";
    /**
     * 任务类型 - 车辆保养
     */
    public static final String TASK_TYPE_MAINTENANCE = "6";
    /**
     * 任务类型 - 车辆保险到期
     */
    public static final String TASK_TYPE_INSURANCE_EXPIRE = "7";
    /**
     * 任务类型 - 车辆年检到期
     */
    public static final String TASK_TYPE_INSPECTION_EXPIRE = "8";
    /**
     * 任务类型 - 车辆运营资质到期
     */
    public static final String TASK_TYPE_QUALIFICATION_EXPIRE = "9";
    /**
     * 任务状态 - 待到达
     */
    public static final Integer TASK_STATUS_PENDING_ARRIVE = 0;
    /**
     * 任务状态 - 待处理
     */
    public static final Integer TASK_STATUS_PENDING = 1;
    /**
     * 任务状态 - 已完成
     */
    public static final Integer TASK_STATUS_COMPLETED = 2;
    /**
     * 任务状态 - 已取消
     */
    public static final Integer TASK_STATUS_CANCELLED = 3;
    /**
     * 任务规则角色类型 - 车辆日常检测任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_INSPECTION = 1;
    /**
     * 任务规则角色类型 - 车辆每日收车
     */
    public static final Integer RULE_ROLE_TYPE_COLLECTION = 2;
    /**
     * 任务规则角色类型 - 车辆保养周期任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_MAINTENANCE = 3;
    /**
     * 任务规则角色类型 - 充电策略任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_CHARGING = 4;
    /**
     * 任务规则角色类型 - 车身清洁任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_CLEAN = 5;
    /**
     * 任务规则角色类型 - 日常整备任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_PREPARATION = 6;
    /**
     * 任务规则角色类型 - 年检/保险策略任务接收角色
     */
    public static final Integer RULE_ROLE_TYPE_INSURANCE = 7;
    /**
     * 任务规则角色类型 - 事故任务上报角色
     */
    public static final Integer RULE_ROLE_TYPE_ACCIDENT_REPORT = 8;
    /**
     * 任务规则角色类型 - 事故任务处理角色
     */
    public static final Integer RULE_ROLE_TYPE_ACCIDENT_HANDLE = 9;
    /**
     * 定时任务执行状态 - 成功
     */
    public static final Integer SCHEDULE_STATUS_SUCCESS = 1;
    /**
     * 定时任务执行状态 - 失败
     */
    public static final Integer SCHEDULE_STATUS_FAILED = 0;
    /**
     * 定时任务创建者名称
     */
    public static final String SCHEDULE_CREATOR = "系统定时任务";
    /**
     * 任务紧急程度 - P0
     */
    public static final Integer URGENCY_LEVEL_P0 = 0;
    /**
     * 任务紧急程度 - P1
     */
    public static final Integer URGENCY_LEVEL_P1 = 1;
    /**
     * 任务紧急程度 - P2
     */
    public static final Integer URGENCY_LEVEL_P2 = 2;
    /**
     * 任务紧急程度 - P3
     */
    public static final Integer URGENCY_LEVEL_P3 = 3;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/TaskRuleDetailDto.java
@@ -1,5 +1,6 @@
package com.xiaopeng.maintenance.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -7,7 +8,8 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.LocalTime;
import java.util.List;
@Data
@@ -21,7 +23,8 @@
    @ApiModelProperty(value = "车辆日常监测 频次")
    private Integer vehicleInspectionFrequency;
    @ApiModelProperty(value = "车辆日常检测 任务下放时间")
    private LocalDateTime vehicleInspectionTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleInspectionTime;
    @ApiModelProperty(value = "车辆日常监测 紧急程度 0=P0 1=P1 2=P2 3=P3")
    private Integer vehicleInspectionLevel;
@@ -30,7 +33,8 @@
    private Boolean vehicleCollection;
    @ApiModelProperty(value = "车辆收车每日时间")
    private LocalDateTime vehicleCollectionTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleCollectionTime;
    @ApiModelProperty(value = "车辆保养周期是否启用")
    private Boolean maintenanceCycle;
@@ -42,7 +46,8 @@
    private Integer maintenanceCycleTimeType;
    @ApiModelProperty(value = "车辆保养周期 任务下放时间")
    private LocalDateTime maintenanceCycleTaskTime;
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime maintenanceCycleTaskTime;
    @ApiModelProperty(value = "车辆保养周期 紧急程度 0=P0 1=P1 2=P2 3=P3")
    private Integer maintenanceCycleLevel;
@@ -57,7 +62,11 @@
    private Integer chargingStrategyLevel;
    @ApiModelProperty(value = "车身清洁保养周期X")
    private Integer vehicleCleanTime;
    private Integer vehicleCleanCycle;
    @ApiModelProperty(value = "车身清洁保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime vehicleCleanTime;
    @ApiModelProperty(value = "车身清洁保养周期X 1=天 2=月")
    private Integer vehicleCleanTimeType;
@@ -66,7 +75,11 @@
    private Integer vehicleCleanLevel;
    @ApiModelProperty(value = "日常整备保养周期X")
    private Integer routineMaintenanceTime;
    private Integer routineMaintenanceCycle;
    @ApiModelProperty(value = "日常整备保养任务下放时间")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime routineMaintenanceTime;
    @ApiModelProperty(value = "日常整备保养周期X 1=天 2=月")
    private Integer routineMaintenanceTimeType;
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/InsuranceInspectionScheduleTask.java
New file
@@ -0,0 +1,433 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 年检/保险策略定时任务
 * <p>
 * 根据任务规则配置,在车辆保险、年检、运营资质即将到期时自动创建提醒任务,
 * 并下发给关联角色的运维人员
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class InsuranceInspectionScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(InsuranceInspectionScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "年检/保险策略定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    /**
     * 每天凌晨1点执行一次
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断年检/保险策略功能是否启用
     * 3. 获取所有运营中的车辆
     * 4. 分别检查三种到期情况并创建任务:
     *    - 保险到期
     *    - 年检到期
     *    - 运营资质到期
     * </p>
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void executeInsuranceInspectionTask() {
        log.info("========== 开始执行年检/保险策略定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断年检/保险策略功能是否启用
        if (!Boolean.TRUE.equals(taskRule.getInsurance())) {
            log.info("年检/保险策略功能未启用,跳过执行");
            return;
        }
        log.info("开始执行年检/保险策略任务下发,保险提前{}天, 年检提前{}天, 运营资质提前{}天, 紧急程度: P{}",
                taskRule.getInsuranceExpires(),
                taskRule.getInsuranceInspectionExpires(),
                taskRule.getOperationQualificationExpires(),
                taskRule.getInsuranceLevel());
        try {
            // 执行任务创建
            int createdCount = createExpireTasks(taskRule);
            log.info("========== 年检/保险策略定时任务执行完成,创建任务数: {} ==========", createdCount);
        } catch (Exception e) {
            log.error("年检/保险策略定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 创建到期提醒任务
     *
     * @param taskRule 任务规则配置
     * @return 创建的任务数量
     */
    private int createExpireTasks(XpTaskRule taskRule) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_INSURANCE);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到年检/保险任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收年检/保险任务", operationUserIds.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需创建到期提醒任务");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        int totalCreatedCount = 0;
        // 3. 检查保险到期
        totalCreatedCount += checkAndCreateInsuranceExpireTasks(vehicles, operationUserIds, taskRule);
        // 4. 检查年检到期
        totalCreatedCount += checkAndCreateInspectionExpireTasks(vehicles, operationUserIds, taskRule);
        // 5. 检查运营资质到期
        totalCreatedCount += checkAndCreateQualificationExpireTasks(vehicles, operationUserIds, taskRule);
        return totalCreatedCount;
    }
    /**
     * 检查并创建保险到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int checkAndCreateInsuranceExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        if (taskRule.getInsuranceExpires() == null || taskRule.getInsuranceExpires() <= 0) {
            log.info("保险到期提醒天数未配置,跳过保险到期检查");
            return 0;
        }
        List<XpVehicle> needWarnVehicles = vehicles.stream()
                .filter(v -> isWithinExpireWarningDays(v.getInsuranceEndTime(), taskRule.getInsuranceExpires()))
                .filter(v -> !hasUnfinishedTask(v.getId(), TaskScheduleConstants.TASK_TYPE_INSURANCE_EXPIRE))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(needWarnVehicles)) {
            log.info("没有需要保险到期提醒的车辆");
            return 0;
        }
        log.info("找到 {} 辆车辆需要保险到期提醒", needWarnVehicles.size());
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_INSURANCE_EXPIRE,
                "保险即将到期"
        );
    }
    /**
     * 检查并创建年检到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int checkAndCreateInspectionExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        if (taskRule.getInsuranceInspectionExpires() == null || taskRule.getInsuranceInspectionExpires() <= 0) {
            log.info("年检到期提醒天数未配置,跳过年检到期检查");
            return 0;
        }
        List<XpVehicle> needWarnVehicles = vehicles.stream()
                .filter(v -> isWithinExpireWarningDays(v.getAnnualInspectionDate(), taskRule.getInsuranceInspectionExpires()))
                .filter(v -> !hasUnfinishedTask(v.getId(), TaskScheduleConstants.TASK_TYPE_INSPECTION_EXPIRE))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(needWarnVehicles)) {
            log.info("没有需要年检到期提醒的车辆");
            return 0;
        }
        log.info("找到 {} 辆车辆需要年检到期提醒", needWarnVehicles.size());
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_INSPECTION_EXPIRE,
                "年检即将到期"
        );
    }
    /**
     * 检查并创建运营资质到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int checkAndCreateQualificationExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        if (taskRule.getOperationQualificationExpires() == null || taskRule.getOperationQualificationExpires() <= 0) {
            log.info("运营资质到期提醒天数未配置,跳过运营资质到期检查");
            return 0;
        }
        List<XpVehicle> needWarnVehicles = vehicles.stream()
                .filter(v -> isWithinExpireWarningDays(v.getQualificationExpireTime(), taskRule.getOperationQualificationExpires()))
                .filter(v -> !hasUnfinishedTask(v.getId(), TaskScheduleConstants.TASK_TYPE_QUALIFICATION_EXPIRE))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(needWarnVehicles)) {
            log.info("没有需要运营资质到期提醒的车辆");
            return 0;
        }
        log.info("找到 {} 辆车辆需要运营资质到期提醒", needWarnVehicles.size());
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_QUALIFICATION_EXPIRE,
                "运营资质即将到期"
        );
    }
    /**
     * 判断是否在到期提醒范围内
     *
     * @param expireTime        到期时间
     * @param daysBeforeExpire 提前X天提醒
     * @return 是否需要提醒
     */
    private boolean isWithinExpireWarningDays(LocalDateTime expireTime, Integer daysBeforeExpire) {
        if (expireTime == null || daysBeforeExpire == null || daysBeforeExpire <= 0) {
            return false;
        }
        LocalDate today = LocalDate.now();
        LocalDate expireDate = expireTime.toLocalDate();
        long daysUntilExpire = ChronoUnit.DAYS.between(today, expireDate);
        // 距离到期天数 <= 配置的提前天数,且未过期(daysUntilExpire >= 0)
        return daysUntilExpire >= 0 && daysUntilExpire <= daysBeforeExpire;
    }
    /**
     * 检查是否已存在未完成的同类型任务
     * <p>
     * 避免重复创建到期提醒任务
     * </p>
     *
     * @param vehicleId  车辆ID
     * @param taskTypeId 任务类型ID
     * @return 是否存在未完成任务
     */
    private boolean hasUnfinishedTask(Long vehicleId, String taskTypeId) {
        XpTask existingTask = taskMapper.selectOne(
                new LambdaQueryWrapper<XpTask>()
                        .eq(XpTask::getVehicleId, vehicleId)
                        .eq(XpTask::getTaskTypeId, taskTypeId)
                        .in(XpTask::getStatus,
                                TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                                TaskScheduleConstants.TASK_STATUS_PENDING)
                        .last("limit 1")
        );
        return existingTask != null;
    }
    /**
     * 为指定到期类型创建任务
     *
     * @param vehicles         需要提醒的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @param taskTypeId       任务类型ID
     * @param taskDescription  任务描述
     * @return 创建的任务数量
     */
    private int createTasksForExpireType(List<XpVehicle> vehicles, List<Long> operationUserIds,
                                          XpTaskRule taskRule, String taskTypeId, String taskDescription) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        for (XpVehicle vehicle : vehicles) {
            XpTask task = new XpTask();
            // 生成任务编号
            String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(taskCode);
            // 设置任务基本信息
            task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
            task.setTaskTypeId(taskTypeId);
            task.setTaskUrgencyLevel(taskRule.getInsuranceLevel() != null
                    ? taskRule.getInsuranceLevel()
                    : TaskScheduleConstants.URGENCY_LEVEL_P2);
            task.setTaskDescription("系统自动下发的" + taskDescription + "提醒任务");
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            // 设置任务时限
            if (taskRule.getProcessingTimeLimit() != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            task.setBelongUserIds(belongUserIds);
            // 设置创建信息
            task.setReportTime(LocalDateTime.now());
            task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
            tasksToSave.add(task);
        }
        // 批量保存任务
        if (!CollectionUtils.isEmpty(tasksToSave)) {
            for (XpTask task : tasksToSave) {
                taskMapper.insert(task);
            }
            log.info("成功创建 {} 个{}提醒任务", tasksToSave.size(), taskDescription);
        }
        return tasksToSave.size();
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *
     * @param roleType 角色类型
     * @return 运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        // 1. 获取该类型对应的角色ID
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        // 2. 获取角色对应的运维人员
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/RoutinePreparationScheduleTask.java
New file
@@ -0,0 +1,395 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 日常整备定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动创建日常整备任务,
 * 并下发给关联角色的运维人员
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class RoutinePreparationScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(RoutinePreparationScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "日常整备定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    /**
     * 每分钟检查一次是否需要执行任务下发
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断日常整备功能是否启用(周期值 > 0)
     * 3. 判断当前时间是否为配置的任务下发时间
     * 4. 查询所有运营中的车辆
     * 5. 根据整备周期判断是否需要创建任务
     * 6. 创建任务并分配给相关角色的运维人员
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executePreparationTask() {
        log.info("========== 开始执行日常整备定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断日常整备功能是否启用(周期值 > 0 表示启用)
        if (!isEnabled(taskRule)) {
            log.info("日常整备功能未启用(周期值未配置或为0),跳过执行");
            return;
        }
        // 3. 判断当前时间是否为配置的任务下发时间
        if (!isExecuteTime(taskRule.getRoutineMaintenanceTime())) {
            log.debug("当前时间不是任务下发时间,跳过执行");
            return;
        }
        // 计算整备周期(天数)
        int cycleDays = calculateCycleDays(taskRule.getRoutineMaintenanceCycle(), taskRule.getRoutineMaintenanceTimeType());
        log.info("开始执行日常整备任务下发,整备周期: {}天, 紧急程度: P{}",
                cycleDays,
                taskRule.getRoutineMaintenanceLevel());
        try {
            // 执行任务创建
            int createdCount = createPreparationTasks(taskRule, cycleDays);
            log.info("========== 日常整备定时任务执行完成,创建任务数: {} ==========", createdCount);
        } catch (Exception e) {
            log.error("日常整备定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 判断日常整备功能是否启用
     * <p>
     * 根据周期值判断:周期值 > 0 则认为启用
     * </p>
     *
     * @param taskRule 任务规则
     * @return 是否启用
     */
    private boolean isEnabled(XpTaskRule taskRule) {
        return taskRule.getRoutineMaintenanceCycle() != null
                && taskRule.getRoutineMaintenanceCycle() > 0;
    }
    /**
     * 判断当前时间是否为任务下发时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("任务下发时间未配置");
            return false;
        }
        LocalTime now = LocalTime.now();
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
    /**
     * 计算整备周期天数
     * <p>
     * 根据周期类型转换为天数:
     * - 类型1:天,直接返回周期数值
     * - 类型2:月,按30天/月计算
     * </p>
     *
     * @param cycleTime     周期数值
     * @param cycleTimeType 周期类型:1=天, 2=月
     * @return 周期天数
     */
    private int calculateCycleDays(Integer cycleTime, Integer cycleTimeType) {
        // 默认周期为1
        if (cycleTime == null || cycleTime <= 0) {
            cycleTime = 1;
        }
        // 默认为天
        if (cycleTimeType == null || cycleTimeType == 1) {
            return cycleTime;
        } else {
            // 月(按30天计算)
            return cycleTime * 30;
        }
    }
    /**
     * 创建日常整备任务
     *
     * @param taskRule  任务规则配置
     * @param cycleDays 整备周期(天数)
     * @return 创建的任务数量
     */
    private int createPreparationTasks(XpTaskRule taskRule, int cycleDays) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_PREPARATION);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到日常整备任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收日常整备任务", operationUserIds.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需创建整备任务");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        // 3. 过滤出今日需要整备的车辆
        List<XpVehicle> needPreparationVehicles = filterVehiclesNeedPreparation(vehicles, cycleDays);
        if (CollectionUtils.isEmpty(needPreparationVehicles)) {
            log.info("今日没有需要整备的车辆");
            return 0;
        }
        log.info("过滤后有 {} 辆车辆需要今日整备", needPreparationVehicles.size());
        // 4. 创建任务并分配给运维人员
        return createTasksAndAssign(needPreparationVehicles, operationUserIds, taskRule);
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *
     * @param roleType 角色类型
     * @return 运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        // 1. 获取该类型对应的角色ID
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        // 2. 获取角色对应的运维人员
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 过滤出需要整备的车辆
     * <p>
     * 根据整备周期判断:查询车辆上次整备任务时间,
     * 如果距今天数 >= 配置周期,则需要整备
     * </p>
     *
     * @param vehicles  所有车辆
     * @param cycleDays 整备周期(天数)
     * @return 需要整备的车辆列表
     */
    private List<XpVehicle> filterVehiclesNeedPreparation(List<XpVehicle> vehicles, int cycleDays) {
        LocalDate today = LocalDate.now();
        List<XpVehicle> needPreparationVehicles = new ArrayList<>();
        for (XpVehicle vehicle : vehicles) {
            // 查询该车辆最近一次整备任务
            XpTask lastTask = taskMapper.selectOne(
                    new LambdaQueryWrapper<XpTask>()
                            .eq(XpTask::getVehicleId, vehicle.getId())
                            .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ROUTINE)
                            .eq(XpTask::getTaskTypeId, TaskScheduleConstants.TASK_TYPE_PREPARATION)
                            .orderByDesc(XpTask::getCreateTime)
                            .last("limit 1")
            );
            // 如果从未整备过,需要整备
            if (lastTask == null || lastTask.getCreateTime() == null) {
                needPreparationVehicles.add(vehicle);
                continue;
            }
            // 计算距离上次整备的天数
            LocalDate lastPreparationDate = lastTask.getCreateTime().toLocalDate();
            long daysSinceLastPreparation = java.time.temporal.ChronoUnit.DAYS.between(lastPreparationDate, today);
            // 如果距离上次整备天数 >= 配置周期,则需要整备
            if (daysSinceLastPreparation >= cycleDays) {
                needPreparationVehicles.add(vehicle);
            }
        }
        return needPreparationVehicles;
    }
    /**
     * 创建任务并分配给运维人员
     *
     * @param vehicles         需要整备的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int createTasksAndAssign(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        for (XpVehicle vehicle : vehicles) {
            XpTask task = new XpTask();
            // 生成任务编号
            String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(taskCode);
            // 设置任务基本信息
            task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
            task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_PREPARATION);
            task.setTaskUrgencyLevel(taskRule.getRoutineMaintenanceLevel() != null
                    ? taskRule.getRoutineMaintenanceLevel()
                    : TaskScheduleConstants.URGENCY_LEVEL_P2);
            task.setTaskDescription("系统自动下发的日常整备任务");
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            // 设置任务时限
            if (taskRule.getProcessingTimeLimit() != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            task.setBelongUserIds(belongUserIds);
            // 设置创建信息
            task.setReportTime(LocalDateTime.now());
            task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
            tasksToSave.add(task);
        }
        // 批量保存任务
        if (!CollectionUtils.isEmpty(tasksToSave)) {
            for (XpTask task : tasksToSave) {
                taskMapper.insert(task);
            }
            log.info("成功创建 {} 个日常整备任务", tasksToSave.size());
        }
        return tasksToSave.size();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleCleanScheduleTask.java
New file
@@ -0,0 +1,395 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 车身清洁定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动创建车身清洁任务,
 * 并下发给关联角色的运维人员
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class VehicleCleanScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleCleanScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "车身清洁定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    /**
     * 每分钟检查一次是否需要执行任务下发
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断车身清洁功能是否启用(周期值 > 0)
     * 3. 判断当前时间是否为配置的任务下发时间
     * 4. 查询所有运营中的车辆
     * 5. 根据清洁周期判断是否需要创建任务
     * 6. 创建任务并分配给相关角色的运维人员
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeCleanTask() {
        log.info("========== 开始执行车身清洁定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断车身清洁功能是否启用(周期值 > 0 表示启用)
        if (!isEnabled(taskRule)) {
            log.info("车身清洁功能未启用(周期值未配置或为0),跳过执行");
            return;
        }
        // 3. 判断当前时间是否为配置的任务下发时间
        if (!isExecuteTime(taskRule.getVehicleCleanTime())) {
            log.debug("当前时间不是任务下发时间,跳过执行");
            return;
        }
        // 计算清洁周期(天数)
        int cycleDays = calculateCycleDays(taskRule.getVehicleCleanCycle(), taskRule.getVehicleCleanTimeType());
        log.info("开始执行车身清洁任务下发,清洁周期: {}天, 紧急程度: P{}",
                cycleDays,
                taskRule.getVehicleCleanLevel());
        try {
            // 执行任务创建
            int createdCount = createCleanTasks(taskRule, cycleDays);
            log.info("========== 车身清洁定时任务执行完成,创建任务数: {} ==========", createdCount);
        } catch (Exception e) {
            log.error("车身清洁定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 判断车身清洁功能是否启用
     * <p>
     * 根据周期值判断:周期值 > 0 则认为启用
     * </p>
     *
     * @param taskRule 任务规则
     * @return 是否启用
     */
    private boolean isEnabled(XpTaskRule taskRule) {
        return taskRule.getVehicleCleanCycle() != null
                && taskRule.getVehicleCleanCycle() > 0;
    }
    /**
     * 判断当前时间是否为任务下发时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("任务下发时间未配置");
            return false;
        }
        LocalTime now = LocalTime.now();
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
    /**
     * 计算清洁周期天数
     * <p>
     * 根据周期类型转换为天数:
     * - 类型1:天,直接返回周期数值
     * - 类型2:月,按30天/月计算
     * </p>
     *
     * @param cycleTime     周期数值
     * @param cycleTimeType 周期类型:1=天, 2=月
     * @return 周期天数
     */
    private int calculateCycleDays(Integer cycleTime, Integer cycleTimeType) {
        // 默认周期为1
        if (cycleTime == null || cycleTime <= 0) {
            cycleTime = 1;
        }
        // 默认为天
        if (cycleTimeType == null || cycleTimeType == 1) {
            return cycleTime;
        } else {
            // 月(按30天计算)
            return cycleTime * 30;
        }
    }
    /**
     * 创建车身清洁任务
     *
     * @param taskRule  任务规则配置
     * @param cycleDays 清洁周期(天数)
     * @return 创建的任务数量
     */
    private int createCleanTasks(XpTaskRule taskRule, int cycleDays) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_CLEAN);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到车身清洁任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收车身清洁任务", operationUserIds.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需创建清洁任务");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        // 3. 过滤出今日需要清洁的车辆
        List<XpVehicle> needCleanVehicles = filterVehiclesNeedClean(vehicles, cycleDays);
        if (CollectionUtils.isEmpty(needCleanVehicles)) {
            log.info("今日没有需要清洁的车辆");
            return 0;
        }
        log.info("过滤后有 {} 辆车辆需要今日清洁", needCleanVehicles.size());
        // 4. 创建任务并分配给运维人员
        return createTasksAndAssign(needCleanVehicles, operationUserIds, taskRule);
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *
     * @param roleType 角色类型
     * @return 运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        // 1. 获取该类型对应的角色ID
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        // 2. 获取角色对应的运维人员
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 过滤出需要清洁的车辆
     * <p>
     * 根据清洁周期判断:查询车辆上次清洁任务时间,
     * 如果距今天数 >= 配置周期,则需要清洁
     * </p>
     *
     * @param vehicles  所有车辆
     * @param cycleDays 清洁周期(天数)
     * @return 需要清洁的车辆列表
     */
    private List<XpVehicle> filterVehiclesNeedClean(List<XpVehicle> vehicles, int cycleDays) {
        LocalDate today = LocalDate.now();
        List<XpVehicle> needCleanVehicles = new ArrayList<>();
        for (XpVehicle vehicle : vehicles) {
            // 查询该车辆最近一次清洁任务
            XpTask lastTask = taskMapper.selectOne(
                    new LambdaQueryWrapper<XpTask>()
                            .eq(XpTask::getVehicleId, vehicle.getId())
                            .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ROUTINE)
                            .eq(XpTask::getTaskTypeId, TaskScheduleConstants.TASK_TYPE_CLEAN)
                            .orderByDesc(XpTask::getCreateTime)
                            .last("limit 1")
            );
            // 如果从未清洁过,需要清洁
            if (lastTask == null || lastTask.getCreateTime() == null) {
                needCleanVehicles.add(vehicle);
                continue;
            }
            // 计算距离上次清洁的天数
            LocalDate lastCleanDate = lastTask.getCreateTime().toLocalDate();
            long daysSinceLastClean = java.time.temporal.ChronoUnit.DAYS.between(lastCleanDate, today);
            // 如果距离上次清洁天数 >= 配置周期,则需要清洁
            if (daysSinceLastClean >= cycleDays) {
                needCleanVehicles.add(vehicle);
            }
        }
        return needCleanVehicles;
    }
    /**
     * 创建任务并分配给运维人员
     *
     * @param vehicles         需要清洁的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int createTasksAndAssign(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        for (XpVehicle vehicle : vehicles) {
            XpTask task = new XpTask();
            // 生成任务编号
            String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(taskCode);
            // 设置任务基本信息
            task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
            task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_CLEAN);
            task.setTaskUrgencyLevel(taskRule.getVehicleCleanLevel() != null
                    ? taskRule.getVehicleCleanLevel()
                    : TaskScheduleConstants.URGENCY_LEVEL_P2);
            task.setTaskDescription("系统自动下发的车身清洁任务");
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            // 设置任务时限
            if (taskRule.getProcessingTimeLimit() != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            task.setBelongUserIds(belongUserIds);
            // 设置创建信息
            task.setReportTime(LocalDateTime.now());
            task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
            tasksToSave.add(task);
        }
        // 批量保存任务
        if (!CollectionUtils.isEmpty(tasksToSave)) {
            for (XpTask task : tasksToSave) {
                taskMapper.insert(task);
            }
            log.info("成功创建 {} 个车身清洁任务", tasksToSave.size());
        }
        return tasksToSave.size();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleCollectionScheduleTask.java
New file
@@ -0,0 +1,188 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
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.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 车辆收车定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动向所有车辆发起收车指令
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class VehicleCollectionScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleCollectionScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "车辆收车定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    /**
     * 每分钟检查一次是否需要执行收车任务
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断收车功能是否启用
     * 3. 判断当前时间是否为配置的收车时间
     * 4. 获取所有运营中的车辆
     * 5. 向每辆车发送收车指令
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeCollectionTask() {
        log.info("========== 开始执行车辆收车定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断收车功能是否启用
        if (!Boolean.TRUE.equals(taskRule.getVehicleCollection())) {
            log.info("车辆收车功能未启用,跳过执行");
            return;
        }
        // 3. 判断当前时间是否为收车时间
        if (!isExecuteTime(taskRule.getVehicleCollectionTime())) {
            log.debug("当前时间不是收车时间,跳过执行");
            return;
        }
        log.info("开始执行车辆收车任务");
        try {
            // 执行收车逻辑
            int processedCount = executeVehicleCollection();
            log.info("========== 车辆收车定时任务执行完成,处理车辆数: {} ==========", processedCount);
        } catch (Exception e) {
            log.error("车辆收车定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 判断当前时间是否为收车时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("收车时间未配置");
            return false;
        }
        LocalTime now = LocalTime.now();
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
    /**
     * 执行车辆收车逻辑
     *
     * @return 处理的车辆数量
     */
    private int executeVehicleCollection() {
        // 1. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需执行收车");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆需要收车", vehicles.size());
        // 2. 向每辆车发送收车指令
        int processedCount = 0;
        for (XpVehicle vehicle : vehicles) {
            try {
                sendCollectionCommand(vehicle);
                processedCount++;
            } catch (Exception e) {
                log.error("向车辆[{}]发送收车指令失败", vehicle.getPlateNumber(), e);
            }
        }
        return processedCount;
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 向车辆发送收车指令
     * <p>
     * TODO: 具体发送收车指令的实现
     * 可能需要:
     * 1. 调用车辆控制接口发送收车指令
     * 2. 更新车辆状态为"已收车"
     * </p>
     *
     * @param vehicle 车辆信息
     */
    private void sendCollectionCommand(XpVehicle vehicle) {
        // TODO: 具体发送收车指令的实现
        log.info("向车辆[{}]发送收车指令", vehicle.getPlateNumber());
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleInspectionScheduleTask.java
New file
@@ -0,0 +1,355 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 车辆日常检测定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动创建车辆日常检测任务,
 * 并下发给关联角色的运维人员
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class VehicleInspectionScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleInspectionScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "车辆日常检测定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    /**
     * 每分钟检查一次是否需要执行任务下发
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断日常检测功能是否启用
     * 3. 判断当前时间是否为配置的任务下发时间
     * 4. 查询所有运营中的车辆
     * 5. 根据检测频次判断是否需要创建任务
     * 6. 创建任务并分配给相关角色的运维人员
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeInspectionTask() {
        log.info("========== 开始执行车辆日常检测定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断日常检测功能是否启用
        if (!Boolean.TRUE.equals(taskRule.getVehicleInspection())) {
            log.info("车辆日常检测功能未启用,跳过执行");
            return;
        }
        // 3. 判断当前时间是否为配置的任务下发时间
        if (!isExecuteTime(taskRule.getVehicleInspectionTime())) {
            log.debug("当前时间不是任务下发时间,跳过执行");
            return;
        }
        log.info("开始执行车辆日常检测任务下发,检测频次: 每{}天/次, 紧急程度: P{}",
                taskRule.getVehicleInspectionFrequency(),
                taskRule.getVehicleInspectionLevel());
        try {
            // 执行任务创建
            int createdCount = createInspectionTasks(taskRule);
            log.info("========== 车辆日常检测定时任务执行完成,创建任务数: {} ==========", createdCount);
        } catch (Exception e) {
            log.error("车辆日常检测定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 判断当前时间是否为任务下发时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("任务下发时间未配置");
            return false;
        }
        LocalTime now = LocalTime.now();
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
    /**
     * 创建车辆日常检测任务
     *
     * @param taskRule 任务规则配置
     * @return 创建的任务数量
     */
    private int createInspectionTasks(XpTaskRule taskRule) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_INSPECTION);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到车辆日常检测任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收日常检测任务", operationUserIds.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需创建检测任务");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        // 3. 过滤出今日需要检测的车辆
        List<XpVehicle> needInspectionVehicles = filterVehiclesNeedInspection(vehicles, taskRule.getVehicleInspectionFrequency());
        if (CollectionUtils.isEmpty(needInspectionVehicles)) {
            log.info("今日没有需要检测的车辆");
            return 0;
        }
        log.info("过滤后有 {} 辆车辆需要今日检测", needInspectionVehicles.size());
        // 4. 创建任务并分配给运维人员
        return createTasksAndAssign(needInspectionVehicles, operationUserIds, taskRule);
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *
     * @param roleType 角色类型
     * @return 运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        // 1. 获取该类型对应的角色ID
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        // 2. 获取角色对应的运维人员
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 过滤出需要检测的车辆
     * <p>
     * 根据检测频次判断:vehicleInspectionFrequency 表示每X天检测一次
     * 查询车辆上次检测时间,如果距今天数 >= 频次配置,则需要检测
     * </p>
     *
     * @param vehicles            所有车辆
     * @param inspectionFrequency 检测频次(天)
     * @return 需要检测的车辆列表
     */
    private List<XpVehicle> filterVehiclesNeedInspection(List<XpVehicle> vehicles, Integer inspectionFrequency) {
        // 默认每天检测一次
        if (inspectionFrequency == null || inspectionFrequency <= 0) {
            inspectionFrequency = 1;
        }
        LocalDate today = LocalDate.now();
        List<XpVehicle> needInspectionVehicles = new ArrayList<>();
        for (XpVehicle vehicle : vehicles) {
            // 查询该车辆最近一次检测任务
            XpTask lastTask = taskMapper.selectOne(
                    new LambdaQueryWrapper<XpTask>()
                            .eq(XpTask::getVehicleId, vehicle.getId())
                            .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ROUTINE)
                            .eq(XpTask::getTaskTypeId, TaskScheduleConstants.TASK_TYPE_DETECTION)
                            .orderByDesc(XpTask::getCreateTime)
                            .last("limit 1")
            );
            // 如果从未检测过,需要检测
            if (lastTask == null || lastTask.getCreateTime() == null) {
                needInspectionVehicles.add(vehicle);
                continue;
            }
            // 计算距离上次检测的天数
            LocalDate lastInspectionDate = lastTask.getCreateTime().toLocalDate();
            long daysSinceLastInspection = java.time.temporal.ChronoUnit.DAYS.between(lastInspectionDate, today);
            // 如果距离上次检测天数 >= 频次配置,则需要检测
            if (daysSinceLastInspection >= inspectionFrequency) {
                needInspectionVehicles.add(vehicle);
            }
        }
        return needInspectionVehicles;
    }
    /**
     * 创建任务并分配给运维人员
     *
     * @param vehicles        需要检测的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule        任务规则
     * @return 创建的任务数量
     */
    private int createTasksAndAssign(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        for (XpVehicle vehicle : vehicles) {
            XpTask task = new XpTask();
            // 生成任务编号
            String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(taskCode);
            // 设置任务基本信息
            task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
            task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_DETECTION);
            task.setTaskUrgencyLevel(taskRule.getVehicleInspectionLevel() != null
                    ? taskRule.getVehicleInspectionLevel()
                    : TaskScheduleConstants.URGENCY_LEVEL_P2);
            task.setTaskDescription("系统自动下发的车辆日常检测任务");
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            // 设置任务时限
            if (taskRule.getProcessingTimeLimit() != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            task.setBelongUserIds(belongUserIds);
            // 设置创建信息
            task.setReportTime(LocalDateTime.now());
            task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
            tasksToSave.add(task);
        }
        // 批量保存任务
        if (!CollectionUtils.isEmpty(tasksToSave)) {
            for (XpTask task : tasksToSave) {
                taskMapper.insert(task);
            }
            log.info("成功创建 {} 个车辆日常检测任务", tasksToSave.size());
        }
        return tasksToSave.size();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleMaintenanceScheduleTask.java
New file
@@ -0,0 +1,381 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 车辆保养周期定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动创建车辆保养任务,
 * 并下发给关联角色的运维人员
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class VehicleMaintenanceScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleMaintenanceScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "车辆保养周期定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    /**
     * 每分钟检查一次是否需要执行任务下发
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断保养周期功能是否启用
     * 3. 判断当前时间是否为配置的任务下发时间
     * 4. 查询所有运营中的车辆
     * 5. 根据保养周期判断是否需要创建任务
     * 6. 创建任务并分配给相关角色的运维人员
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeMaintenanceTask() {
        log.info("========== 开始执行车辆保养周期定时任务检查 ==========");
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断保养周期功能是否启用
        if (!Boolean.TRUE.equals(taskRule.getMaintenanceCycle())) {
            log.info("车辆保养周期功能未启用,跳过执行");
            return;
        }
        // 3. 判断当前时间是否为配置的任务下发时间
        if (!isExecuteTime(taskRule.getMaintenanceCycleTaskTime())) {
            log.debug("当前时间不是任务下发时间,跳过执行");
            return;
        }
        // 计算保养周期(天数)
        int cycleDays = calculateCycleDays(taskRule.getMaintenanceCycleTime(), taskRule.getMaintenanceCycleTimeType());
        log.info("开始执行车辆保养周期任务下发,保养周期: {}天, 紧急程度: P{}",
                cycleDays,
                taskRule.getMaintenanceCycleLevel());
        try {
            // 执行任务创建
            int createdCount = createMaintenanceTasks(taskRule, cycleDays);
            log.info("========== 车辆保养周期定时任务执行完成,创建任务数: {} ==========", createdCount);
        } catch (Exception e) {
            log.error("车辆保养周期定时任务执行失败", e);
        }
    }
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 判断当前时间是否为任务下发时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("任务下发时间未配置");
            return false;
        }
        LocalTime now = LocalTime.now();
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
    /**
     * 计算保养周期天数
     * <p>
     * 根据周期类型转换为天数:
     * - 类型1:天,直接返回周期数值
     * - 类型2:月,按30天/月计算
     * </p>
     *
     * @param cycleTime     周期数值
     * @param cycleTimeType 周期类型:1=天, 2=月
     * @return 周期天数
     */
    private int calculateCycleDays(Integer cycleTime, Integer cycleTimeType) {
        // 默认周期为1
        if (cycleTime == null || cycleTime <= 0) {
            cycleTime = 1;
        }
        // 默认为天
        if (cycleTimeType == null || cycleTimeType == 1) {
            return cycleTime;
        } else {
            // 月(按30天计算)
            return cycleTime * 30;
        }
    }
    /**
     * 创建车辆保养任务
     *
     * @param taskRule  任务规则配置
     * @param cycleDays 保养周期(天数)
     * @return 创建的任务数量
     */
    private int createMaintenanceTasks(XpTaskRule taskRule, int cycleDays) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_MAINTENANCE);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到车辆保养任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收保养任务", operationUserIds.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需创建保养任务");
            return 0;
        }
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        // 3. 过滤出今日需要保养的车辆
        List<XpVehicle> needMaintenanceVehicles = filterVehiclesNeedMaintenance(vehicles, cycleDays);
        if (CollectionUtils.isEmpty(needMaintenanceVehicles)) {
            log.info("今日没有需要保养的车辆");
            return 0;
        }
        log.info("过滤后有 {} 辆车辆需要今日保养", needMaintenanceVehicles.size());
        // 4. 创建任务并分配给运维人员
        return createTasksAndAssign(needMaintenanceVehicles, operationUserIds, taskRule);
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *
     * @param roleType 角色类型
     * @return 运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        // 1. 获取该类型对应的角色ID
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        // 2. 获取角色对应的运维人员
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 过滤出需要保养的车辆
     * <p>
     * 根据保养周期判断:查询车辆上次保养任务时间,
     * 如果距今天数 >= 配置周期,则需要保养
     * </p>
     *
     * @param vehicles  所有车辆
     * @param cycleDays 保养周期(天数)
     * @return 需要保养的车辆列表
     */
    private List<XpVehicle> filterVehiclesNeedMaintenance(List<XpVehicle> vehicles, int cycleDays) {
        LocalDate today = LocalDate.now();
        List<XpVehicle> needMaintenanceVehicles = new ArrayList<>();
        for (XpVehicle vehicle : vehicles) {
            // 查询该车辆最近一次保养任务
            XpTask lastTask = taskMapper.selectOne(
                    new LambdaQueryWrapper<XpTask>()
                            .eq(XpTask::getVehicleId, vehicle.getId())
                            .eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ROUTINE)
                            .eq(XpTask::getTaskTypeId, TaskScheduleConstants.TASK_TYPE_MAINTENANCE)
                            .orderByDesc(XpTask::getCreateTime)
                            .last("limit 1")
            );
            // 如果从未保养过,需要保养
            if (lastTask == null || lastTask.getCreateTime() == null) {
                needMaintenanceVehicles.add(vehicle);
                continue;
            }
            // 计算距离上次保养的天数
            LocalDate lastMaintenanceDate = lastTask.getCreateTime().toLocalDate();
            long daysSinceLastMaintenance = java.time.temporal.ChronoUnit.DAYS.between(lastMaintenanceDate, today);
            // 如果距离上次保养天数 >= 配置周期,则需要保养
            if (daysSinceLastMaintenance >= cycleDays) {
                needMaintenanceVehicles.add(vehicle);
            }
        }
        return needMaintenanceVehicles;
    }
    /**
     * 创建任务并分配给运维人员
     *
     * @param vehicles         需要保养的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param taskRule         任务规则
     * @return 创建的任务数量
     */
    private int createTasksAndAssign(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        for (XpVehicle vehicle : vehicles) {
            XpTask task = new XpTask();
            // 生成任务编号
            String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(taskCode);
            // 设置任务基本信息
            task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
            task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_MAINTENANCE);
            task.setTaskUrgencyLevel(taskRule.getMaintenanceCycleLevel() != null
                    ? taskRule.getMaintenanceCycleLevel()
                    : TaskScheduleConstants.URGENCY_LEVEL_P2);
            task.setTaskDescription("系统自动下发的车辆保养任务");
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            // 设置任务时限
            if (taskRule.getProcessingTimeLimit() != null) {
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            task.setBelongUserIds(belongUserIds);
            // 设置创建信息
            task.setReportTime(LocalDateTime.now());
            task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
            tasksToSave.add(task);
        }
        // 批量保存任务
        if (!CollectionUtils.isEmpty(tasksToSave)) {
            for (XpTask task : tasksToSave) {
                taskMapper.insert(task);
            }
            log.info("成功创建 {} 个车辆保养任务", tasksToSave.size());
        }
        return tasksToSave.size();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -1,17 +1,27 @@
package com.xiaopeng.maintenance.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO;
import com.xiaopeng.api.feignClient.account.SysUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.SysUser;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.maintenance.*;
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.NotificationTypeEnum;
import com.xiaopeng.common.core.enums.TaskTypeEnum;
import com.xiaopeng.common.core.enums.TaskTypeNotificationEnum;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.maintenance.constant.I18nKeyConstant;
@@ -25,6 +35,8 @@
import com.xiaopeng.maintenance.model.query.WorkBenchQuery;
import com.xiaopeng.maintenance.model.query.XpTaskRecordPageQuery;
import com.xiaopeng.maintenance.model.vo.*;
import com.xiaopeng.maintenance.service.IXpTaskRuleRoleService;
import com.xiaopeng.maintenance.service.IXpTaskRuleService;
import com.xiaopeng.maintenance.service.XpTaskService;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.apache.commons.lang3.StringUtils;
@@ -57,22 +69,6 @@
    private RedissonClient redissonClient;
    @Autowired
    private XpErrorTypeMapper errorTypeMapper;
    /**
     * 常规任务类型映射
     */
    private static final Map<String, String> TASK_TYPE_MAP = new java.util.HashMap<>();
    static {
        TASK_TYPE_MAP.put("1", "车辆检查");
        TASK_TYPE_MAP.put("2", "车辆充电");
        TASK_TYPE_MAP.put("3", "车辆整备");
        TASK_TYPE_MAP.put("4", "整备洗车");
        TASK_TYPE_MAP.put("5", "车辆维修");
        TASK_TYPE_MAP.put("6", "车辆保养");
        TASK_TYPE_MAP.put("7", "车辆保险到期");
        TASK_TYPE_MAP.put("8", "车辆年检到期");
        TASK_TYPE_MAP.put("9", "车辆运营资质到期");
    }
    @Autowired
    private XpTaskHandleDetailMapper xpTaskHandleDetailMapper;
@@ -98,6 +94,14 @@
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpTaskRuleMapper xpTaskRuleMapper;
    @Autowired
    private XpAliPushMessageClient xpAliPushMessageClient;
    @Autowired
    private XpOperationUserNotificationClient xpOperationUserNotificationClient;
    @Autowired
    private IXpTaskRuleRoleService xpTaskRuleRoleService;
    @Autowired
    private IXpTaskRuleService xpTaskRuleService;
    @Override
    public PageInfo<WorkBenchVO> workbench(WorkBenchQuery query) {
@@ -162,14 +166,74 @@
        xpTask.setTaskDescription(dto.getTaskDescription());
        // todo 获取车辆当前位置
//        xpTask.setVehicleLocation();
//        xpTask.setVehicleId();
       xpTask.setVehicleId(dto.getId());
//        xpTask.setVehicleLon();
//        xpTask.setVehicleLat();
        xpTask.setReportUserId(xpOperationsUser.getId());
        xpTask.setReportTime(LocalDateTime.now());
        xpTask.setStatus(0);
        xpTask.setCreateBy(xpOperationsUser.getNickName());
        XpTaskRule taskRule = xpTaskRuleService.lambdaQuery().last("limit 1").one();
        XpTaskRuleRole taskRuleRole = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, 9).last("limit 1").one();
        if (Objects.isNull(taskRuleRole) || Objects.isNull(taskRule)) {
            throw new ServiceException("未配置车辆事故任务分配规则", I18nKeyConstant.TASK_RULE_NOT_CONFIG);
        }
        R<List<XpOperationsRole>> xpOperationsRoleResult = xpOperationUserClient.getXpOperationsRoleByRoleId(taskRuleRole.getRoleId());
        if (CollUtil.isNotEmpty(xpOperationsRoleResult.getData())) {
            List<Long> userIds = xpOperationsRoleResult.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList());
            xpTask.setHandleUserId(StringUtils.join(userIds, ","));
        }
        this.save(xpTask);
        //发送消息通知
        List<Long> userIds = Arrays.stream(xpTask.getHandleUserId().split(",")).map(Long::valueOf).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(userIds)) {
            R<List<XpOperationsUser>> xpOperationUserResult = xpOperationUserClient.listByIds(userIds);
            List<XpOperationsUser> data = xpOperationUserResult.getData();
            if (CollUtil.isNotEmpty(data)) {
                R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(dto.getId());
                XpVehicle vehicle = xpVehicleR.getData();
                String plateNumber = Objects.nonNull(vehicle) ? vehicle.getPlateNumber() : "";
                // 车辆${车牌号}在${车辆位置信息}发生了紧急事故,请在${到达现场限时}内到达现场!${APP链接}
                String content = String.format("车辆%s在%s发生了紧急事故,请在%d分钟内到达现场!", plateNumber, xpTask.getVehicleLocation(), taskRule.getProcessingTimeLimit());
                List<XpOperationsUserNotification> notifications = data.stream().map(item -> {
                    XpOperationsUserNotification notification = new XpOperationsUserNotification();
                    notification.setOperationsUserId(item.getId());
                    notification.setNotificationContent(content);
                    notification.setReadStatus(0);
                    notification.setCreateTime(LocalDateTime.now());
                    return notification;
                }).collect(Collectors.toList());
                xpOperationUserNotificationClient.addNotificationBatch(notifications);
                // 发送阿里云推送
                Map<Integer, List<XpOperationsUser>> operationUserMap = data.stream().collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
                for (Map.Entry<Integer, List<XpOperationsUser>> integerListEntry : operationUserMap.entrySet()) {
                    List<XpOperationsUser> value = integerListEntry.getValue();
                    pushAliMessage(value, content, xpTask);
                }
            }
        }
    }
    /**
     * 推送阿里云通知
     * @param data
     * @param content
     * @param xpTask
     */
    private void pushAliMessage(List<XpOperationsUser> data, String content, XpTask xpTask) {
        String account = data.stream().map(XpOperationsUser::getPhoneNumber).filter(Objects::nonNull).collect(Collectors.joining(","));
        Map<String, Object> param = new HashMap<>();
        param.put("content", content);
        param.put("strategy", NotificationTypeEnum.ACCIDENT_NEW.getCode());
        param.put("type", TaskTypeNotificationEnum.ACCIDENT_TASK.getCode());
        param.put("id", xpTask.getId().toString());
        xpAliPushMessageClient.pushMessage(AliPushMessageDTO.builder()
                .body(JSONObject.toJSONString(param))
                .account(account).title("车辆事故警示")
                .pushType(AliPushTypeConstant.NOTICE)
                        .deviceType(data.get(0).getDeviceType())
                .build());
    }
    @Override
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages.properties
@@ -1,4 +1,5 @@
# 中文
welcome.message=欢迎来到我们的应用程序!
task.not.exist=任务不存在
vehicle.not.exist=车辆不存在
vehicle.not.exist=车辆不存在
task.rule.not.config=未配置车辆事故任务分配规则
xp-robotaxi-maintenance-boot/src/main/resources/i18n/messages_en_US.properties
@@ -2,3 +2,4 @@
welcome.message=Welcome to our application!
task.not.exist=The task does not exist.
vehicle.not.exist=The vehicle does not exist.
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
@@ -1,4 +1,5 @@
# 中文
welcome.message=欢迎来到我们的应用程序!
task.not.exist=任务不存在
vehicle.not.exist=车辆不存在
vehicle.not.exist=车辆不存在
task.rule.not.config=未配置车辆事故任务分配规则
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -121,17 +121,8 @@
        // 存储redis
        redisService.setCacheObject(CacheConstants.ORDER_ROUTE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo);
        // 查询订单信息
        XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData();
        // 封装消息内容
        Integer routeType = dto.getRouteType();
        if(routeType == 2){
            routeType = 3;
        }else {
            routeType = xpOrder.getStatus() == 4 ? 1 : 2;
        }
        // 存储路线偏离缓存,后续进行轨迹截取
        redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +routeType, routeVo);
        redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo);
        return R.ok(routeVo);
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java
@@ -57,8 +57,10 @@
    @ApiOperation(value = "获取所有运营中的区域", response = XpElectronicFenceArea.class)
    @PostMapping("/listElectronicFenceArea")
    public R<List<XpElectronicFenceArea>> listElectronicFenceArea() {
        R<List<XpElectronicFenceArea>> listR = xpPointClient.listElectronicFenceArea();
        return xpPointClient.listElectronicFenceArea();
        return listR;
    }
    @ApiOperation(value = "点位管理分页列表", response = XpPoint.class)
    @PostMapping("/pageList")
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpInvitationCodeController.java
@@ -109,7 +109,7 @@
        HttpServletResponse response = WebUtils.response();
        ServletOutputStream outputStream = null;
        try {
            String fileName = URLEncoder.encode("邀请码导入模板.xls", "utf-8");
            String fileName = URLEncoder.encode("邀请码导入模板.xlsx", "utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setHeader("content-Type", "application/vnd.ms-excel");
            response.setHeader("Pragma", "no-cache");
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointController.java
@@ -6,6 +6,7 @@
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo;
import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo;
import com.xiaopeng.api.vo.order.XpPointInfoVo;
import com.xiaopeng.api.vo.order.XpPointsSearchVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.controller.BaseController;
@@ -21,6 +22,7 @@
import javax.validation.Valid;
import java.util.List;
import java.util.Set;
@Slf4j
@RestController
@@ -79,6 +81,12 @@
    }
    @PostMapping("/inner/point/getPointsInfo")
    public R<List<XpPoint>> getPointsInfo(@RequestBody Set<Long> points){
        List<XpPoint> xpPoints = xpPointService.listByIds(points);
        return R.ok(xpPoints);
    }
    /**
     * 获取下单最大距离 最小距离
     */
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpRegionController.java
@@ -78,7 +78,7 @@
                redisService.deleteObject(CacheConstants.XP_REGION);
            }
        }else {
            list = regionService.list(Wrappers.lambdaQuery(XpRegion.class).ne(XpRegion::getParentId, 0).orderByAsc(XpRegion::getInitial));
            list = regionService.list(Wrappers.lambdaQuery(XpRegion.class).ne(XpRegion::getParentId, 0).isNotNull(XpRegion::getInitial).orderByAsc(XpRegion::getInitial));
            redisService.setCacheList(CacheConstants.XP_REGION_CITY, list);
        }
        List<XpOpenCity> openCities = openCityService.list(new LambdaQueryWrapper<XpOpenCity>().eq(XpOpenCity::getStatus,1));
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpCruisePlanServiceImpl.java
@@ -21,10 +21,7 @@
import com.xiaopeng.operation.dto.XpCruisePlanEditDTO;
import com.xiaopeng.operation.mapper.XpCruisePlanMapper;
import com.xiaopeng.operation.query.CruisePlanQuery;
import com.xiaopeng.operation.service.IXpCruisePlanPointService;
import com.xiaopeng.operation.service.IXpCruisePlanService;
import com.xiaopeng.operation.service.IXpCruisePlanVehicleService;
import com.xiaopeng.operation.service.IXpPointService;
import com.xiaopeng.operation.service.*;
import com.xiaopeng.operation.vo.XpCruisePlanDetailVO;
import com.xiaopeng.operation.vo.XpCruisePlanPointVO;
import com.xiaopeng.operation.vo.XpCruisePlanVehicleVO;
@@ -51,7 +48,7 @@
    private final IXpCruisePlanPointService xpCruisePlanPointService;
    private final IXpCruisePlanVehicleService xpCruisePlanVehicleService;
    private final IXpPointService xpPointService;
    private final XpPointService xpPointService;
    private final XpVehicleClient xpVehicleClient;
    private final XpFleetVehicleClient xpFleetVehicleClient;
    private final XpFleetClient xpFleetClient;
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -176,6 +176,8 @@
            xpHistoryStartPointsVo.setId(xpPoint.getId());
            double distanced = FastDistanceCalculator.distanceFastKm(historyStartPointsDto.getLat(), historyStartPointsDto.getLon(), xpPoint.getPointLat(), xpPoint.getPointLon());
            xpHistoryStartPointsVo.setDistance(distanced);
            xpHistoryStartPointsVo.setTime(xpPoint.getParkingTimeLimit());
            xpHistoryStartPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
            resultList.add(xpHistoryStartPointsVo);
        }
@@ -196,6 +198,8 @@
            xpHistoryEndPointsVo.setId(xpPoint.getId());
            double distanced = FastDistanceCalculator.distanceFastKm(historyEndPointsDto.getLat(), historyEndPointsDto.getLon(), xpPoint.getPointLat(), xpPoint.getPointLon());
            xpHistoryEndPointsVo.setDistance(distanced);
            xpHistoryEndPointsVo.setTime(xpPoint.getParkingTimeLimit());
            xpHistoryEndPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
            resultList.add(xpHistoryEndPointsVo);
        }
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java
@@ -469,14 +469,14 @@
        // 根据id获取手机号
        Boolean isPriorityDispatch = false;
        R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
        if (xpAppUserR.getCode() == R.SUCCESS) {
            Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
            if (count > 0) {
                isPriorityDispatch = true;
            }
        }
        Boolean isPriorityDispatch = true;
//        R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
//        if (xpAppUserR.getCode() == R.SUCCESS) {
//            Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
//            if (count > 0) {
//                isPriorityDispatch = true;
//            }
//        }
        // 生成订单数据
        XpOrder xpOrder = new XpOrder();
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpRegionServiceImpl.java
@@ -68,7 +68,7 @@
                "G", "H", "I", "J", "K", "L",
                "M", "N", "O", "P", "Q", "R",
                "S", "T", "U", "V", "W", "X", "Y", "Z"};
        List<XpRegion> tRegions = this.baseMapper.selectList(Wrappers.lambdaQuery(XpRegion.class));
        List<XpRegion> tRegions = this.baseMapper.selectList(Wrappers.lambdaQuery(XpRegion.class).isNotNull(XpRegion::getInitial));
        List<List<XpRegion>> result = new ArrayList<>();
        for (String initial : initials) {
            List<XpRegion> init = tRegions.stream().filter(e -> e.getInitial().equals(initial) && !e.getCode().contains("0000")).collect(Collectors.toList());
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -400,6 +400,17 @@
        String uid = request.getHeader("XP-Uid");
        return xpOrderService.fareBreakdown(orderId,uid);
    }
    /**+
     * 获取行程费用明细
     */
    @PutMapping("/api/v1/mini-program/order/feeDescription/{orderId}")
    @ApiOperation(value = "获取费用说明", tags = {"小程序-获取费用说明"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<String> feeDescription(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return xpOrderService.feeDescription(orderId,uid);
    }
    /**
     * 订单评价
     */
@@ -460,19 +471,13 @@
     * 订单查询实时轨迹路线
     */
    @ApiOperation(value = "订单查询实时轨迹路线")
    @PostMapping("/api/v1/order/queryOrderRoute")
    public R<RouteVo> queryOrderRoute(@Validated @RequestBody QueryOrderRouteDTO dto) {
    @PostMapping("/api/v1/mini-program/order/queryOrderRoute")
    public R<RouteVo> queryOrderRoute(@Validated @RequestBody QueryOrderRouteDTO dto, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        // 查询订单信息
        XpOrder xpOrder = xpOrderService.getById(dto.getOrderId());
        // 封装消息内容
        Integer routeType = dto.getRouteType();
        if(routeType == 2){
            routeType = 3;
        }else {
            routeType = xpOrder.getStatus() == 4 ? 1 : 2;
        }
//        XpOrder xpOrder = xpOrderService.getById(dto.getOrderId());
        // 获取路线规划
        RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +routeType);
        RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType());
        return R.ok(routeVo);
    }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderHistoryListVO.java
@@ -35,5 +35,34 @@
    private LocalDateTime time;
    private Long startPointId;
    private String  startPointName;
    private Double  startLon;
    private Double  startLat;
    private Long xpElectronicFenceAreaId;
    private Integer startParkingTimeLimit;
    private Long endPointId;
    private String  endPointName;
    private Double endLat;
    private Double endLon;
    private Integer endParkingTimeLimit;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderService.java
@@ -196,4 +196,6 @@
    void popOrder(PopOrderDto dto);
    OrderDetailVo orderDetail(Long orderId, String uid);
    R<String> feeDescription(Long orderId, String uid);
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -696,9 +696,19 @@
    public XpOrderHistoryVO getHistoryOrderList(XpHistoryOrderListQuery query, String uid) {
        Page<XpOrderHistoryListVO> page = new Page<>(query.getPageNum(), query.getPageSize());
        List<XpOrderHistoryListVO> list = this.baseMapper.getHistoryOrderList(page, query, Long.parseLong(uid));
        XpOrderHistoryVO xpOrderHistoryVO = new XpOrderHistoryVO();
        if(list.isEmpty()){
            return xpOrderHistoryVO;
        }
        Set<Long> points = list.stream().map(XpOrderHistoryListVO::getStartPointId).collect(Collectors.toSet());
        points.addAll(list.stream().map(XpOrderHistoryListVO::getEndPointId).collect(Collectors.toSet()));
        // 查询 点位信息
        R<List<XpPoint>> pointInfoVos = xpPointClient.getPointsInfo(points);
        List<XpPoint> pointList = pointInfoVos.getData();
        xpOrderHistoryVO.setTotal((int) page.getTotal());
        // 根据time分组
@@ -708,6 +718,28 @@
            xpOrderHistoryDateVO.setDateLabel(entry.getKey());
            List<XpOrderHistoryListVO> value = entry.getValue();
            value = value.stream().sorted(Comparator.comparing(XpOrderHistoryListVO::getTime).reversed()).collect(Collectors.toList());
            for (XpOrderHistoryListVO xpOrderHistoryListVO : value) {
                XpPoint xpPoint = pointList.stream().filter(e -> e.getId().equals(xpOrderHistoryListVO.getStartPointId())).findFirst().orElse(null);
                if(xpPoint!=null){
                    xpOrderHistoryListVO.setStartPointName(xpPoint.getPointName());
                    xpOrderHistoryListVO.setStartLat(xpPoint.getPointLat());
                    xpOrderHistoryListVO.setStartLon(xpPoint.getPointLon());
                    xpOrderHistoryListVO.setXpElectronicFenceAreaId(xpPoint.getElectronicAreaId());
                    xpOrderHistoryListVO.setStartParkingTimeLimit(xpPoint.getParkingTimeLimit());
                }
                xpPoint = pointList.stream().filter(e -> e.getId().equals(xpOrderHistoryListVO.getEndPointId())).findFirst().orElse(null);
                if(xpPoint!=null){
                    xpOrderHistoryListVO.setEndPointName(xpPoint.getPointName());
                    xpOrderHistoryListVO.setEndLat(xpPoint.getPointLat());
                    xpOrderHistoryListVO.setEndLon(xpPoint.getPointLon());
                    xpOrderHistoryListVO.setXpElectronicFenceAreaId(xpPoint.getElectronicAreaId());
                    xpOrderHistoryListVO.setEndParkingTimeLimit(xpPoint.getParkingTimeLimit());
                }
            }
            xpOrderHistoryDateVO.setOrders(value);
            return xpOrderHistoryDateVO;
        }).collect(Collectors.toList());
@@ -952,6 +984,7 @@
        order.setStatus(8);
        order.setAppUserId(null);
        order.setTripEnd(LocalDateTime.now());
        order.setMileageKilometers(80.0);
        baseMapper.updateById(order);
        // 解绑用户与车辆关系
        CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo();
@@ -1009,9 +1042,8 @@
            // 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值
            config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(Double.parseDouble(basicPricingJson.getString("num12")), 999999, Double.parseDouble(basicPricingJson.getString("num13"))));
        }
        for (Object o : startingFareJsonArray) {
            // 转化为jsonObject
            JSONObject jsonObject = (JSONObject) o;
        for (int i = 0; i < startingFareJsonArray.size(); i++) {
            com.alibaba.fastjson.JSONObject jsonObject = startingFareJsonArray.getJSONObject(i);
            String num1 = jsonObject.getString("num1");
            String num2 = jsonObject.getString("num2");
            String num3 = jsonObject.getString("num3");
@@ -1021,7 +1053,6 @@
            String num7 = jsonObject.getString("num7");
            config.normals.add(new TaxiPricingDemoService.PricingRule("普通时段", num1, num2, Double.parseDouble(num3), Double.parseDouble(num4), Double.parseDouble(num5), Double.parseDouble(num6), Double.parseDouble(num7)));
        }
        TaxiPricingDemoService.TaxiPricingService service = new TaxiPricingDemoService.TaxiPricingService();
        Map<String, BigDecimal> bill = service.calculateOrderPrice(DateUtils.toDate(order.getCreateTime()), order.getMileageKilometers(), gpsPoints, config);
@@ -1091,8 +1122,9 @@
                } else if (key.contains("节假")) {
                    startDetail.setType(4);
                }
                startDetail.setDuration(Double.valueOf(key.split("=")[1]));
                startDetail.setMileage(new BigDecimal(key.split("&")[1]));
                String durationAndMileage = key.split("\\|")[2];
                startDetail.setDuration(Double.valueOf(durationAndMileage.split("&")[0]));
                startDetail.setMileage(new BigDecimal(durationAndMileage.split("&")[1]));
                start.add(startDetail);
            }
            // 封装里程费
@@ -1100,8 +1132,8 @@
                FareBreakdownDetailVo mileageDetail = new FareBreakdownDetailVo();
                if (key.contains("普通时段")) {
                    mileageDetail.setType(1);
                    String key1 = "distance普通时段" + key.split("\\|")[1];
                    String key2 = "普通时段" + key.split("\\|")[1];
                    String key1 = "distance普通时段|" + key.split("\\|")[1];
                    String key2 = "普通时段|" + key.split("\\|")[1];
                    BigDecimal distance = bill.get(key1);
                    BigDecimal money = bill.get(key2);
                    mileageDetail.setMileage(distance);
@@ -1109,8 +1141,8 @@
                    mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
                } else if (key.contains("夜间行驶")) {
                    mileageDetail.setType(2);
                    String key1 = "distance夜间行驶" + key.split("\\|")[1];
                    String key2 = "夜间行驶" + key.split("\\|")[1];
                    String key1 = "distance夜间行驶|" + key.split("\\|")[1];
                    String key2 = "夜间行驶|" + key.split("\\|")[1];
                    BigDecimal distance = bill.get(key1);
                    BigDecimal money = bill.get(key2);
                    mileageDetail.setMileage(distance);
@@ -1119,8 +1151,8 @@
                    mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
                } else if (key.contains("高峰期")) {
                    mileageDetail.setType(3);
                    String key1 = "distance高峰期" + key.split("\\|")[1];
                    String key2 = "高峰期" + key.split("\\|")[1];
                    String key1 = "distance高峰期|" + key.split("\\|")[1];
                    String key2 = "高峰期|" + key.split("\\|")[1];
                    BigDecimal distance = bill.get(key1);
                    BigDecimal money = bill.get(key2);
                    mileageDetail.setMileage(distance);
@@ -1129,8 +1161,8 @@
                    mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate)));
                } else if (key.contains("节假")) {
                    mileageDetail.setType(4);
                    String key1 = "distance节假日" + key.split("\\|")[1];
                    String key2 = "节假日" + key.split("\\|")[1];
                    String key1 = "distance节假日|" + key.split("\\|")[1];
                    String key2 = "节假日|" + key.split("\\|")[1];
                    BigDecimal distance = bill.get(key1);
                    BigDecimal money = bill.get(key2);
                    mileageDetail.setMileage(distance);
@@ -1178,16 +1210,19 @@
        for (XpOrderDetail xpOrderDetail : xpOrderDetails) {
            xpOrderDetailMapper.insert(xpOrderDetail);
        }
        for (FareBreakdownDetailVo fareBreakdownDetailVo : duration) {
            order.setDurationType(fareBreakdownDetailVo.getType());
            order.setDurationMoney(fareBreakdownDetailVo.getMoney());
            order.setDuration(Double.parseDouble(fareBreakdownDetailVo.getDuration() + ""));
        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());
            }
        }
        for (FareBreakdownDetailVo fareBreakdownDetailVo : longDistance) {
            order.setLongDistanceType(fareBreakdownDetailVo.getType());
            order.setLongDistance(fareBreakdownDetailVo.getMileage().doubleValue());
            order.setLongDistancemoney(fareBreakdownDetailVo.getMoney());
        }
        BigDecimal orderMoney = startMoney.add(mileageMoney).add(order.getDurationMoney() != null ? order.getDurationMoney() : BigDecimal.ZERO)
                .add(order.getLongDistancemoney() != null ? order.getLongDistancemoney() : BigDecimal.ZERO)
                .add(order.getWaitMoney()!=null?order.getWaitMoney():BigDecimal.ZERO);
@@ -1478,7 +1513,7 @@
            detail.setTime(xpOrderDetail.getTime());
            detail.setMileage(xpOrderDetail.getMileage());
            detail.setDuration(xpOrderDetail.getDuration());
            if (xpOrderDetail.getType() == 1){
            if (xpOrderDetail.getDetailType() == 1){
                start.add(detail);
            }else{
                mileage.add(detail);
@@ -1490,6 +1525,7 @@
            durationDetail.setType(order.getDurationType());
            durationDetail.setDuration(order.getDuration());
            durationDetail.setMoney(order.getDurationMoney());
            duration.add(durationDetail);
        }
        List<FareBreakdownDetailVo> longDistance = new ArrayList<>();
@@ -1531,6 +1567,7 @@
        // mvp版本不做支付 使用优惠券抵扣为0元订单处理 处理订单状态即可
        XpOrder order = this.getById(dto.getOrderId());
        order.setStatus(9);
        order.setAppUserId(null);
        baseMapper.updateById(order);
        return R.ok();
    }
@@ -1655,6 +1692,18 @@
        return orderDetailVo;
    }
    @Override
    public R<String> feeDescription(Long orderId, String uid) {
        XpOrder order = this.getById(orderId);
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId());
        XpPricing pricing = pricingByCityId.getData();
        if (pricing == null) {
            throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS);
        }else{
            return R.ok(pricing.getPriceRule());
        }
    }
    /**
     * 成功操作下发命令后调用 用于弹出redis队列的获取成功的数据
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaxiPricingDemoService.java
@@ -41,7 +41,7 @@
            BigDecimal totalMileage = BigDecimal.valueOf(totalMileageKm);
            PricingRule baseRule = config.findMatchedRule(orderTime);
            result.put("起步价(" + baseRule.ruleName + ")"+"|"+baseRule.start+"-"+baseRule.end+"="+baseRule.baseMin+"&"+baseRule.baseKm, baseRule.baseFare);
            result.put("起步价(" + baseRule.ruleName + ")"+"|"+baseRule.start+"-"+baseRule.end+"|"+baseRule.baseMin+"&"+baseRule.baseKm, baseRule.baseFare);
            if (totalMileage.compareTo(baseRule.baseKm) <= 0) {
                return formatMap(result);
xp-robotaxi-order-boot/src/main/resources/mapper/order/XpOrderMapper.xml
@@ -45,7 +45,7 @@
        ORDER BY date ASC
    </select>
    <select id="getHistoryOrderList" resultType="com.xiaopeng.order.model.vo.XpOrderHistoryListVO">
        select start_point_name as originAddress, end_point_name as destinationAddress,
        select start_point_name as originAddress, end_point_name as destinationAddress,start_point_id as startPointId,end_point_id as endPointId,
        DATE_FORMAT(create_time, '%H:%i') AS orderTime, pay_money as amount,create_time as time,DATE_FORMAT(create_time,'%Y-%m-%d') AS timeStr,
        id as orderId from xp_order where app_user_id = #{uid} and is_deleted = 0 and status in (9,10)
        <if test="query.status != null">
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
@@ -48,7 +48,7 @@
        if(routeType == 2){
            routeType = 3;
        }else {
            routeType = xpOrder.getStatus() == 4 ? 1 : 2;
            routeType = xpOrder.getStatus() == 3 ? 1 : 2;
        }
        // 获取路线规划
        RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +routeType);