liujie
2026-06-09 c96fbe6b0aeb413a17c429133ee6d81d138144b9
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/InsuranceInspectionScheduleTask.java
@@ -2,17 +2,21 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetOperationsClient;
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.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
import com.xiaopeng.common.core.utils.SensitiveDataCryptoUtil;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.service.VehicleLocationFillService;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +24,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
@@ -62,6 +67,12 @@
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    @Autowired
    private XpFleetOperationsClient xpFleetOperationsClient;
    @Autowired
    private VehicleLocationFillService vehicleLocationFillService;
    /**
     * 每天凌晨1点执行一次
@@ -127,13 +138,14 @@
     */
    private int createExpireTasks(XpTaskRule taskRule) {
        // 1. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_INSURANCE);
        if (CollectionUtils.isEmpty(operationUserIds)) {
        List<Long> roleOperationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_INSURANCE);
        if (CollectionUtils.isEmpty(roleOperationUserIds)) {
            log.warn("未找到年检/保险任务接收角色的运维人员");
            return 0;
        }
        log.info("找到 {} 个运维人员可接收年检/保险任务", operationUserIds.size());
        Set<Long> roleOperationUserIdSet = new HashSet<>(roleOperationUserIds);
        log.info("找到 {} 个运维人员可接收年检/保险任务", roleOperationUserIdSet.size());
        // 2. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
@@ -144,16 +156,30 @@
        log.info("找到 {} 辆运营中的车辆", vehicles.size());
        Map<String, VehiclePosition> positionByVin = vehicleLocationFillService.loadPositionByVin(
                vehicles.stream()
                        .map(XpVehicle::getVehicleVin)
                        .filter(StringUtils::hasLength)
                        .distinct()
                        .collect(Collectors.toList())
        );
        Map<Long, Long> vehicleIdToFleetIdCache = new HashMap<>();
        Map<Long, Set<Long>> fleetIdToOperationUserIdsCache = new HashMap<>();
        int totalCreatedCount = 0;
        // 3. 检查保险到期
        totalCreatedCount += checkAndCreateInsuranceExpireTasks(vehicles, operationUserIds, taskRule);
        totalCreatedCount += checkAndCreateInsuranceExpireTasks(vehicles, roleOperationUserIdSet, taskRule,
                vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache, positionByVin);
        // 4. 检查年检到期
        totalCreatedCount += checkAndCreateInspectionExpireTasks(vehicles, operationUserIds, taskRule);
        totalCreatedCount += checkAndCreateInspectionExpireTasks(vehicles, roleOperationUserIdSet, taskRule,
                vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache, positionByVin);
        // 5. 检查运营资质到期
        totalCreatedCount += checkAndCreateQualificationExpireTasks(vehicles, operationUserIds, taskRule);
        totalCreatedCount += checkAndCreateQualificationExpireTasks(vehicles, roleOperationUserIdSet, taskRule,
                vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache, positionByVin);
        return totalCreatedCount;
    }
@@ -162,11 +188,15 @@
     * 检查并创建保险到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param roleOperationUserIds 配置角色下的运维人员ID集合
     * @param taskRule         任务规则
     * @param vehicleIdToFleetIdCache 车辆->车队缓存
     * @param fleetIdToOperationUserIdsCache 车队->人员缓存
     * @return 创建的任务数量
     */
    private int checkAndCreateInsuranceExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
    private int checkAndCreateInsuranceExpireTasks(List<XpVehicle> vehicles, Set<Long> roleOperationUserIds, XpTaskRule taskRule,
                                                   Map<Long, Long> vehicleIdToFleetIdCache, Map<Long, Set<Long>> fleetIdToOperationUserIdsCache,
                                                   Map<String, VehiclePosition> positionByVin) {
        if (taskRule.getInsuranceExpires() == null || taskRule.getInsuranceExpires() <= 0) {
            log.info("保险到期提醒天数未配置,跳过保险到期检查");
            return 0;
@@ -186,10 +216,13 @@
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                roleOperationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_INSURANCE_EXPIRE,
                "保险即将到期"
                "保险即将到期",
                vehicleIdToFleetIdCache,
                fleetIdToOperationUserIdsCache,
                positionByVin
        );
    }
@@ -197,11 +230,15 @@
     * 检查并创建年检到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param roleOperationUserIds 配置角色下的运维人员ID集合
     * @param taskRule         任务规则
     * @param vehicleIdToFleetIdCache 车辆->车队缓存
     * @param fleetIdToOperationUserIdsCache 车队->人员缓存
     * @return 创建的任务数量
     */
    private int checkAndCreateInspectionExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
    private int checkAndCreateInspectionExpireTasks(List<XpVehicle> vehicles, Set<Long> roleOperationUserIds, XpTaskRule taskRule,
                                                    Map<Long, Long> vehicleIdToFleetIdCache, Map<Long, Set<Long>> fleetIdToOperationUserIdsCache,
                                                    Map<String, VehiclePosition> positionByVin) {
        if (taskRule.getInsuranceInspectionExpires() == null || taskRule.getInsuranceInspectionExpires() <= 0) {
            log.info("年检到期提醒天数未配置,跳过年检到期检查");
            return 0;
@@ -221,10 +258,13 @@
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                roleOperationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_INSPECTION_EXPIRE,
                "年检即将到期"
                "年检即将到期",
                vehicleIdToFleetIdCache,
                fleetIdToOperationUserIdsCache,
                positionByVin
        );
    }
@@ -232,11 +272,15 @@
     * 检查并创建运营资质到期任务
     *
     * @param vehicles         车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param roleOperationUserIds 配置角色下的运维人员ID集合
     * @param taskRule         任务规则
     * @param vehicleIdToFleetIdCache 车辆->车队缓存
     * @param fleetIdToOperationUserIdsCache 车队->人员缓存
     * @return 创建的任务数量
     */
    private int checkAndCreateQualificationExpireTasks(List<XpVehicle> vehicles, List<Long> operationUserIds, XpTaskRule taskRule) {
    private int checkAndCreateQualificationExpireTasks(List<XpVehicle> vehicles, Set<Long> roleOperationUserIds, XpTaskRule taskRule,
                                                       Map<Long, Long> vehicleIdToFleetIdCache, Map<Long, Set<Long>> fleetIdToOperationUserIdsCache,
                                                       Map<String, VehiclePosition> positionByVin) {
        if (taskRule.getOperationQualificationExpires() == null || taskRule.getOperationQualificationExpires() <= 0) {
            log.info("运营资质到期提醒天数未配置,跳过运营资质到期检查");
            return 0;
@@ -256,10 +300,13 @@
        return createTasksForExpireType(
                needWarnVehicles,
                operationUserIds,
                roleOperationUserIds,
                taskRule,
                TaskScheduleConstants.TASK_TYPE_QUALIFICATION_EXPIRE,
                "运营资质即将到期"
                "运营资质即将到期",
                vehicleIdToFleetIdCache,
                fleetIdToOperationUserIdsCache,
                positionByVin
        );
    }
@@ -310,23 +357,33 @@
     * 为指定到期类型创建任务
     *
     * @param vehicles         需要提醒的车辆列表
     * @param operationUserIds 运维人员ID列表
     * @param roleOperationUserIds 配置角色下的运维人员ID集合
     * @param taskRule         任务规则
     * @param taskTypeId       任务类型ID
     * @param taskDescription  任务描述
     * @param vehicleIdToFleetIdCache 车辆->车队缓存
     * @param fleetIdToOperationUserIdsCache 车队->人员缓存
     * @return 创建的任务数量
     */
    private int createTasksForExpireType(List<XpVehicle> vehicles, List<Long> operationUserIds,
                                          XpTaskRule taskRule, String taskTypeId, String taskDescription) {
    private int createTasksForExpireType(List<XpVehicle> vehicles, Set<Long> roleOperationUserIds,
                                         XpTaskRule taskRule, String taskTypeId, String taskDescription,
                                         Map<Long, Long> vehicleIdToFleetIdCache,
                                         Map<Long, Set<Long>> fleetIdToOperationUserIdsCache,
                                         Map<String, VehiclePosition> positionByVin) {
        List<XpTask> tasksToSave = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        // 将所有配置角色的运维人员ID拼接,任务分配给该角色下的所有运维人员
        String belongUserIds = operationUserIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        int skippedCount = 0;
        for (XpVehicle vehicle : vehicles) {
            List<Long> handleUserIds = resolveHandleUserIds(vehicle.getId(), taskTypeId, roleOperationUserIds,
                    vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache);
            if (CollectionUtils.isEmpty(handleUserIds)) {
                skippedCount++;
                continue;
            }
            String belongUserIds = handleUserIds.stream().map(String::valueOf).collect(Collectors.joining(","));
            XpTask task = new XpTask();
            // 生成任务编号
@@ -343,6 +400,8 @@
            // 设置车辆信息
            task.setVehicleId(vehicle.getId());
            String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
            vehicleLocationFillService.fillFromVin(task, vin, positionByVin);
            // 设置任务时限 常规任务限时3天
            task.setEndTime(LocalDateTime.now().plusDays(3));
@@ -350,7 +409,7 @@
            // 设置任务状态为待到达
            task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING);
            // 设置任务接收人(配置角色下的所有运维人员都可以看到)
            // 设置任务接收人(车辆所属车队关联人员与配置角色人员取交集)
            task.setHandleUserId(belongUserIds);
            task.setReportTime(LocalDateTime.now());
            task.setReportUserId(0L);
@@ -367,10 +426,68 @@
            }
            log.info("成功创建 {} 个{}提醒任务", tasksToSave.size(), taskDescription);
        }
        if (skippedCount > 0) {
            log.warn("{}提醒任务:跳过 {} 辆车(无可分配处理人)", taskDescription, skippedCount);
        }
        return tasksToSave.size();
    }
    private List<Long> resolveHandleUserIds(Long vehicleId, String taskTypeId, Set<Long> roleOperationUserIds,
                                           Map<Long, Long> vehicleIdToFleetIdCache,
                                           Map<Long, Set<Long>> fleetIdToOperationUserIdsCache) {
        if (vehicleId == null) {
            log.warn("定时任务分配处理人失败:vehicleId为空,taskTypeId={}", taskTypeId);
            return Collections.emptyList();
        }
        Long fleetId = vehicleIdToFleetIdCache.get(vehicleId);
        if (!vehicleIdToFleetIdCache.containsKey(vehicleId)) {
            try {
                R<Long> fleetIdResult = xpVehicleClient.findFleetIdByVehicleId(vehicleId);
                fleetId = fleetIdResult != null ? fleetIdResult.getData() : null;
            } catch (Exception e) {
                log.error("根据车辆查询车队失败,vehicleId={}, taskTypeId={}", vehicleId, taskTypeId, e);
                fleetId = null;
            }
            vehicleIdToFleetIdCache.put(vehicleId, fleetId);
        }
        if (fleetId == null) {
            log.warn("定时任务跳过:车辆未关联车队,vehicleId={}, taskTypeId={}", vehicleId, taskTypeId);
            return Collections.emptyList();
        }
        Set<Long> fleetOperationUserIds = fleetIdToOperationUserIdsCache.get(fleetId);
        if (!fleetIdToOperationUserIdsCache.containsKey(fleetId)) {
            try {
                R<List<Long>> opsResult = xpFleetOperationsClient.getOperationsIdsByFleetId(fleetId);
                List<Long> opsIds = opsResult != null ? opsResult.getData() : null;
                fleetOperationUserIds = CollectionUtils.isEmpty(opsIds) ? Collections.emptySet() : new HashSet<>(opsIds);
            } catch (Exception e) {
                log.error("根据车队查询关联人员失败,fleetId={}, vehicleId={}, taskTypeId={}", fleetId, vehicleId, taskTypeId, e);
                fleetOperationUserIds = Collections.emptySet();
            }
            fleetIdToOperationUserIdsCache.put(fleetId, fleetOperationUserIds);
        }
        if (CollectionUtils.isEmpty(fleetOperationUserIds)) {
            log.warn("定时任务跳过:车队未关联人员,fleetId={}, vehicleId={}, taskTypeId={}", fleetId, vehicleId, taskTypeId);
            return Collections.emptyList();
        }
        List<Long> finalUserIds = fleetOperationUserIds.stream()
                .filter(roleOperationUserIds::contains)
                .sorted()
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(finalUserIds)) {
            log.warn("定时任务跳过:车队人员与角色人员无交集,fleetId={}, vehicleId={}, taskTypeId={}", fleetId, vehicleId, taskTypeId);
            return Collections.emptyList();
        }
        return finalUserIds;
    }
    /**
     * 根据角色类型获取运维人员ID列表
     *