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.api.vo.automaticPilot.VehiclePosition;
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.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 运营车辆电量检测定时任务
*
* 根据任务规则配置的充电阈值,从MongoDB读取低电量车辆位置数据,
* 创建充电类型任务并下发给配置角色对应的运维人员。
*
*
* @author mitao
* @since 2026-03-10
*/
@Component
public class VehicleChargingScheduleTask {
private static final Logger log = LoggerFactory.getLogger(VehicleChargingScheduleTask.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 MongoTemplate mongoTemplate;
/**
* 每分钟执行一次电量检测
*/
@Scheduled(cron = "0 * * * * ?")
public void executeChargingDetectionTask() {
log.info("========== 开始执行{}检查 ==========", TASK_NAME);
// 1. 获取任务规则配置
XpTaskRule taskRule = getTaskRule();
if (taskRule == null) {
log.info("未找到任务规则配置,跳过执行");
return;
}
// 2. 判断充电策略功能是否启用
if (taskRule.getChargingStrategy() == null || taskRule.getChargingStrategy() != 1) {
log.info("充电策略未启用,跳过执行");
return;
}
// 3. 获取电量阈值
BigDecimal thresholdDecimal = taskRule.getChargingStrategyBatteryl();
if (thresholdDecimal == null) {
log.warn("充电策略电量阈值未配置,跳过执行");
return;
}
int threshold = thresholdDecimal.intValue();
if (threshold <= 0) {
log.warn("充电策略电量阈值不合法:{},跳过执行", thresholdDecimal);
return;
}
// 4. 获取任务接收角色的运维人员
List operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_CHARGING);
if (CollectionUtils.isEmpty(operationUserIds)) {
log.warn("未找到充电策略任务接收角色的运维人员");
return;
}
String belongUserIds = operationUserIds.stream().map(String::valueOf).collect(Collectors.joining(","));
// 5. 获取所有运营中的车辆,并构建 VIN -> Vehicle 映射
List operatingVehicles = getOperatingVehicles();
if (CollectionUtils.isEmpty(operatingVehicles)) {
log.info("没有运营中的车辆,跳过执行");
return;
}
Map operatingVehicleByVin = operatingVehicles.stream()
.filter(v -> StringUtils.hasLength(v.getVehicleVin()))
.collect(Collectors.toMap(XpVehicle::getVehicleVin, v -> v, (a, b) -> a));
if (operatingVehicleByVin.isEmpty()) {
log.info("运营车辆VIN为空,跳过执行");
return;
}
// 6. 从MongoDB查询电量低于阈值的车辆位置数据(只查运营车辆VIN)
List vins = new ArrayList<>(operatingVehicleByVin.keySet());
Query query = new Query(Criteria.where("vin").in(vins).and("currentElectricity").lt(threshold));
List lowBatteryPositions = mongoTemplate.find(query, VehiclePosition.class);
if (CollectionUtils.isEmpty(lowBatteryPositions)) {
log.info("未找到低电量车辆(阈值={}),跳过执行", threshold);
return;
}
int createdCount = 0;
int skippedCount = 0;
for (VehiclePosition position : lowBatteryPositions) {
if (position == null || !StringUtils.hasLength(position.getVin())) {
continue;
}
XpVehicle vehicle = operatingVehicleByVin.get(position.getVin());
if (vehicle == null || vehicle.getId() == null) {
continue;
}
// 避免重复创建:若存在未完成的充电任务则跳过
if (hasUnfinishedTask(vehicle.getId(), TaskScheduleConstants.TASK_TYPE_CHARGING)) {
skippedCount++;
continue;
}
XpTask task = buildChargingTask(vehicle, position, belongUserIds, taskRule, threshold);
taskMapper.insert(task);
createdCount++;
}
log.info("========== {}执行完成,阈值={},命中={},创建任务数={},跳过(已存在未完成任务)={} ==========",
TASK_NAME, threshold, lowBatteryPositions.size(), createdCount, skippedCount);
}
private XpTaskRule getTaskRule() {
return taskRuleMapper.selectOne(new LambdaQueryWrapper().last("limit 1"));
}
/**
* 获取所有运营中的车辆
*/
private List getOperatingVehicles() {
try {
R> result = xpVehicleClient.listVehicle();
if (result != null && result.getData() != null) {
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();
}
/**
* 根据角色类型获取运维人员ID列表
*/
private List getOperationUserIdsByRoleType(Integer roleType) {
List ruleRoles = taskRuleRoleMapper.selectList(
new LambdaQueryWrapper()
.eq(XpTaskRuleRole::getRuleType, roleType)
);
if (CollectionUtils.isEmpty(ruleRoles)) {
return Collections.emptyList();
}
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)
.filter(Objects::nonNull)
.collect(Collectors.toList())
);
}
} catch (Exception e) {
log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
}
}
return new ArrayList<>(userIds);
}
/**
* 检查是否已存在未完成的同类型任务
*/
private boolean hasUnfinishedTask(Long vehicleId, String taskTypeId) {
XpTask existingTask = taskMapper.selectOne(
new LambdaQueryWrapper()
.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;
}
private XpTask buildChargingTask(XpVehicle vehicle,
VehiclePosition position,
String belongUserIds,
XpTaskRule taskRule,
int threshold) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
Integer currentElectricity = position.getCurrentElectricity();
String desc = String.format("系统自动下发的车辆充电任务,当前电量%s%%,阈值%d%%",
currentElectricity == null ? "-" : String.valueOf(currentElectricity),
threshold);
XpTask task = new XpTask();
task.setTaskCode(taskCode);
task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_CHARGING);
task.setTaskUrgencyLevel(taskRule.getChargingStrategyLevel() != null
? taskRule.getChargingStrategyLevel()
: TaskScheduleConstants.URGENCY_LEVEL_P2);
task.setTaskDescription(desc);
task.setVehicleId(vehicle.getId());
if (position.getPoint() != null) {
task.setVehicleLon(BigDecimal.valueOf(position.getPoint().getX()));
task.setVehicleLat(BigDecimal.valueOf(position.getPoint().getY()));
}
// 常规任务默认限时3天
task.setEndTime(LocalDateTime.now().plusDays(3));
task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING);
task.setHandleUserId(belongUserIds);
task.setReportUserId(0L);
task.setReportTime(LocalDateTime.now());
task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
return task;
}
}