liujie
2026-02-27 0f0ccc465e40b446b2ea5610af144d2e6544da26
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi
7个文件已添加
3个文件已修改
2326 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/resources/mapper/account/SysUserMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/SysUser.java 4 ●●●●● 补丁 | 查看 | 原始文档 | 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/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 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/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-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/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
@@ -209,7 +209,7 @@
                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(data, content, xpTask);
                    pushAliMessage(value, content, xpTask);
                }
            }
        }
@@ -227,7 +227,7 @@
        param.put("content", content);
        param.put("strategy", NotificationTypeEnum.ACCIDENT_NEW.getCode());
        param.put("type", TaskTypeNotificationEnum.ACCIDENT_TASK.getCode());
        param.put("id", xpTask.getId());
        param.put("id", xpTask.getId().toString());
        xpAliPushMessageClient.pushMessage(AliPushMessageDTO.builder()
                .body(JSONObject.toJSONString(param))
                .account(account).title("车辆事故警示")