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.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;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 日常整备定时任务
*
* 根据任务规则配置,在指定时间自动创建日常整备任务,
* 并下发给关联角色的运维人员
*
*
* @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;
@Autowired
private XpFleetOperationsClient xpFleetOperationsClient;
@Autowired
private VehicleLocationFillService vehicleLocationFillService;
/**
* 每分钟检查一次是否需要执行任务下发
*
* 检查逻辑:
* 1. 获取任务规则配置
* 2. 判断日常整备功能是否启用(周期值 > 0)
* 3. 判断当前时间是否为配置的任务下发时间
* 4. 查询所有运营中的车辆
* 5. 根据整备周期判断是否需要创建任务
* 6. 创建任务并分配给相关角色的运维人员
*
*/
@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().last("limit 1"));
}
/**
* 判断日常整备功能是否启用
*
* 根据周期值判断:周期值 > 0 则认为启用
*
*
* @param taskRule 任务规则
* @return 是否启用
*/
private boolean isEnabled(XpTaskRule taskRule) {
return taskRule.getRoutineMaintenanceCycle() != null
&& taskRule.getRoutineMaintenanceCycle() > 0;
}
/**
* 判断当前时间是否为任务下发时间
*
* 精确到分钟级别的时间匹配
*
*
* @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();
}
/**
* 计算整备周期天数
*
* 根据周期类型转换为天数:
* - 类型1:天,直接返回周期数值
* - 类型2:月,按30天/月计算
*
*
* @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 roleOperationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_PREPARATION);
if (CollectionUtils.isEmpty(roleOperationUserIds)) {
log.warn("未找到日常整备任务接收角色的运维人员");
return 0;
}
Set roleOperationUserIdSet = new HashSet<>(roleOperationUserIds);
log.info("找到 {} 个运维人员可接收日常整备任务", roleOperationUserIdSet.size());
// 2. 获取所有运营中的车辆
List vehicles = getOperatingVehicles();
if (CollectionUtils.isEmpty(vehicles)) {
log.info("没有运营中的车辆,无需创建整备任务");
return 0;
}
log.info("找到 {} 辆运营中的车辆", vehicles.size());
// 3. 过滤出今日需要整备的车辆
List needPreparationVehicles = filterVehiclesNeedPreparation(vehicles, cycleDays);
if (CollectionUtils.isEmpty(needPreparationVehicles)) {
log.info("今日没有需要整备的车辆");
return 0;
}
log.info("过滤后有 {} 辆车辆需要今日整备", needPreparationVehicles.size());
// 4. 创建任务并分配给运维人员
Map vehicleIdToFleetIdCache = new HashMap<>();
Map> fleetIdToOperationUserIdsCache = new HashMap<>();
return createTasksAndAssign(needPreparationVehicles, roleOperationUserIdSet, taskRule,
vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache);
}
/**
* 根据角色类型获取运维人员ID列表
*
* @param roleType 角色类型
* @return 运维人员ID列表
*/
private List getOperationUserIdsByRoleType(Integer roleType) {
// 1. 获取该类型对应的角色ID
List ruleRoles = taskRuleRoleMapper.selectList(
new LambdaQueryWrapper()
.eq(XpTaskRuleRole::getRuleType, roleType)
);
if (CollectionUtils.isEmpty(ruleRoles)) {
return Collections.emptyList();
}
// 2. 获取角色对应的运维人员
Set userIds = new HashSet<>();
for (XpTaskRuleRole ruleRole : ruleRoles) {
try {
R> 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 getOperatingVehicles() {
try {
R> 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();
}
/**
* 过滤出需要整备的车辆
*
* 根据整备周期判断:查询车辆上次整备任务时间,
* 如果距今天数 >= 配置周期,则需要整备
*
*
* @param vehicles 所有车辆
* @param cycleDays 整备周期(天数)
* @return 需要整备的车辆列表
*/
private List filterVehiclesNeedPreparation(List vehicles, int cycleDays) {
LocalDate today = LocalDate.now();
List needPreparationVehicles = new ArrayList<>();
for (XpVehicle vehicle : vehicles) {
// 查询该车辆最近一次整备任务
XpTask lastTask = taskMapper.selectOne(
new LambdaQueryWrapper()
.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 roleOperationUserIds 配置角色下的运维人员ID集合
* @param taskRule 任务规则
* @param vehicleIdToFleetIdCache 车辆->车队缓存
* @param fleetIdToOperationUserIdsCache 车队->人员缓存
* @return 创建的任务数量
*/
private int createTasksAndAssign(List vehicles, Set roleOperationUserIds, XpTaskRule taskRule,
Map vehicleIdToFleetIdCache,
Map> fleetIdToOperationUserIdsCache) {
List tasksToSave = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
int skippedCount = 0;
Map positionByVin = vehicleLocationFillService.loadPositionByVin(
vehicles.stream()
.map(XpVehicle::getVehicleVin)
.filter(StringUtils::hasLength)
.distinct()
.collect(Collectors.toList())
);
for (XpVehicle vehicle : vehicles) {
List handleUserIds = resolveHandleUserIds(vehicle.getId(), roleOperationUserIds,
vehicleIdToFleetIdCache, fleetIdToOperationUserIdsCache);
if (CollectionUtils.isEmpty(handleUserIds)) {
skippedCount++;
continue;
}
String belongUserIds = handleUserIds.stream().map(String::valueOf).collect(Collectors.joining(","));
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());
String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
vehicleLocationFillService.fillFromVin(task, vin, positionByVin);
// 设置任务时限 常规任务限时3天
task.setEndTime(LocalDateTime.now().plusDays(3));
// 设置任务状态为待到达
task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING);
// 设置任务接收人(车辆所属车队关联人员与配置角色人员取交集)
task.setHandleUserId(belongUserIds);
task.setReportTime(LocalDateTime.now());
task.setReportUserId(0L);
// 设置创建信息
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());
}
if (skippedCount > 0) {
log.warn("日常整备任务:跳过 {} 辆车(无可分配处理人)", skippedCount);
}
return tasksToSave.size();
}
private List resolveHandleUserIds(Long vehicleId, Set roleOperationUserIds,
Map vehicleIdToFleetIdCache,
Map> fleetIdToOperationUserIdsCache) {
if (vehicleId == null) {
log.warn("日常整备定时任务分配处理人失败:vehicleId为空");
return Collections.emptyList();
}
Long fleetId = vehicleIdToFleetIdCache.get(vehicleId);
if (!vehicleIdToFleetIdCache.containsKey(vehicleId)) {
try {
R fleetIdResult = xpVehicleClient.findFleetIdByVehicleId(vehicleId);
fleetId = fleetIdResult != null ? fleetIdResult.getData() : null;
} catch (Exception e) {
log.error("日常整备定时任务:根据车辆查询车队失败,vehicleId={}", vehicleId, e);
fleetId = null;
}
vehicleIdToFleetIdCache.put(vehicleId, fleetId);
}
if (fleetId == null) {
log.warn("日常整备定时任务跳过:车辆未关联车队,vehicleId={}", vehicleId);
return Collections.emptyList();
}
Set fleetOperationUserIds = fleetIdToOperationUserIdsCache.get(fleetId);
if (!fleetIdToOperationUserIdsCache.containsKey(fleetId)) {
try {
R> opsResult = xpFleetOperationsClient.getOperationsIdsByFleetId(fleetId);
List opsIds = opsResult != null ? opsResult.getData() : null;
fleetOperationUserIds = CollectionUtils.isEmpty(opsIds) ? Collections.emptySet() : new HashSet<>(opsIds);
} catch (Exception e) {
log.error("日常整备定时任务:根据车队查询关联人员失败,fleetId={}, vehicleId={}", fleetId, vehicleId, e);
fleetOperationUserIds = Collections.emptySet();
}
fleetIdToOperationUserIdsCache.put(fleetId, fleetOperationUserIds);
}
if (CollectionUtils.isEmpty(fleetOperationUserIds)) {
log.warn("日常整备定时任务跳过:车队未关联人员,fleetId={}, vehicleId={}", fleetId, vehicleId);
return Collections.emptyList();
}
List finalUserIds = fleetOperationUserIds.stream()
.filter(roleOperationUserIds::contains)
.sorted()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(finalUserIds)) {
log.warn("日常整备定时任务跳过:车队人员与角色人员无交集,fleetId={}, vehicleId={}", fleetId, vehicleId);
return Collections.emptyList();
}
return finalUserIds;
}
}