package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
import com.xiaopeng.common.core.enums.NotificationTypeEnum;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
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.util.CollectionUtils;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 事故任务即将超时检查定时任务
*
*
每分钟执行一次,判断事故任务在即将超时窗口内并发送通知。
*
* @author mitao
* @since 2026-03-14
*/
@Deprecated
public class AccidentTaskWillTimeoutScheduleTask {
private static final Logger log = LoggerFactory.getLogger(AccidentTaskWillTimeoutScheduleTask.class);
private static final String TASK_NAME = "事故任务即将超时检查定时任务";
private static final String PUSH_TITLE = "事故即将超时提示";
private static final String CONTENT_TEMPLATE =
"车辆%s在%s发生的紧急事故即将超时,剩余时间%s分钟,请及时到达现场!";
@Autowired
private XpTaskMapper taskMapper;
@Autowired
private XpTaskRuleMapper taskRuleMapper;
@Autowired
private XpOperationUserNotificationClient xpOperationUserNotificationClient;
@Autowired
private AccidentTaskNotificationSupport notificationSupport;
/**
* 每分钟执行一次
*/
@Scheduled(cron = "0 * * * * ?")
public void executeAccidentTaskWillTimeoutCheck() {
log.info("========== 开始执行{} ==========", TASK_NAME);
try {
XpTaskRule taskRule = getTaskRule();
Integer processingTimeLimit = taskRule == null ? null : taskRule.getProcessingTimeLimit();
Integer factorMinutes = calcWillTimeoutFactorMinutes(processingTimeLimit);
if (factorMinutes == null || factorMinutes <= 0) {
log.info("未配置有效处理时限或即将超时因子<=0,跳过执行,processingTimeLimit={}", processingTimeLimit);
return;
}
LocalDateTime now = LocalDateTime.now();
LocalDateTime windowEnd = now.plusMinutes(factorMinutes);
List tasks = queryWillTimeoutAccidentTasks(now, windowEnd);
if (CollectionUtils.isEmpty(tasks)) {
log.info("未找到即将超时事故任务,跳过执行");
return;
}
Set vehicleIds = tasks.stream()
.map(XpTask::getVehicleId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map vehicleMap = notificationSupport.fetchVehicles(vehicleIds);
Set allUserIds = new HashSet<>();
for (XpTask task : tasks) {
allUserIds.addAll(notificationSupport.parseUserIds(task.getHandleUserId()));
}
Map userMap = notificationSupport.fetchOperationUsers(allUserIds);
int notifiedTaskCount = 0;
int notificationCount = 0;
int pushCount = 0;
List allNotifications = new ArrayList<>();
for (XpTask task : tasks) {
try {
if (task == null || task.getId() == null) {
continue;
}
Set userIds = notificationSupport.parseUserIds(task.getHandleUserId());
if (CollectionUtils.isEmpty(userIds)) {
log.warn("事故任务ID={} 未配置有效处理人,跳过通知", task.getId());
continue;
}
int affectedRows = markWillTimeoutNoticeSent(task.getId(), now, windowEnd);
if (affectedRows <= 0) {
continue;
}
List users = userIds.stream()
.map(userMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(users)) {
log.warn("事故任务ID={} 未匹配到有效运维人员,跳过通知", task.getId());
continue;
}
String plateNumber = resolvePlateNumber(vehicleMap.get(task.getVehicleId()));
String vehicleLocation = StringUtils.defaultString(task.getVehicleLocation());
String remainMinutes = String.valueOf(calcRemainMinutes(now, task.getEndTime()));
String content = String.format(CONTENT_TEMPLATE, plateNumber, vehicleLocation, remainMinutes);
String bodyJson = notificationSupport.buildPushBodyJson(
content,
NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(),
task.getId()
);
allNotifications.addAll(notificationSupport.buildNotifications(
users,
NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(),
content,
task.getId(),
bodyJson
));
pushCount += notificationSupport.pushToUsersByDeviceType(users, PUSH_TITLE, bodyJson);
notifiedTaskCount++;
} catch (Exception e) {
log.error("处理即将超时事故任务通知失败,任务ID={}", task == null ? null : task.getId(), e);
}
}
if (!CollectionUtils.isEmpty(allNotifications)) {
xpOperationUserNotificationClient.addNotificationBatch(allNotifications);
notificationCount = allNotifications.size();
}
log.info("========== {} 执行完成,任务数: {},通知记录数: {},推送次数: {} ==========",
TASK_NAME, notifiedTaskCount, notificationCount, pushCount);
} catch (Exception e) {
log.error("{}执行失败", TASK_NAME, e);
}
}
/**
* 获取任务规则配置
*/
private XpTaskRule getTaskRule() {
return taskRuleMapper.selectOne(new LambdaQueryWrapper().last("limit 1"));
}
/**
* 计算即将超时窗口(分钟):floor(processingTimeLimit * 0.2)
*/
private Integer calcWillTimeoutFactorMinutes(Integer processingTimeLimit) {
if (processingTimeLimit == null || processingTimeLimit <= 0) {
return 0;
}
return (int) Math.floor(processingTimeLimit * 0.2d);
}
/**
* 查询即将超时事故任务:now < endTime <= windowEnd 且未发送即将超时通知
*/
private List queryWillTimeoutAccidentTasks(LocalDateTime now, LocalDateTime windowEnd) {
return taskMapper.selectList(new LambdaQueryWrapper()
.eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
.in(XpTask::getStatus,
TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
TaskScheduleConstants.TASK_STATUS_PENDING)
.isNotNull(XpTask::getEndTime)
.gt(XpTask::getEndTime, now)
.le(XpTask::getEndTime, windowEnd)
.eq(XpTask::getWillTimeoutNoticeSent, 0));
}
/**
* 原子更新(抢占式)标记即将超时通知已发送
*/
private int markWillTimeoutNoticeSent(Long taskId, LocalDateTime now, LocalDateTime windowEnd) {
return taskMapper.update(null, new LambdaUpdateWrapper()
.set(XpTask::getWillTimeoutNoticeSent, 1)
.eq(XpTask::getId, taskId)
.eq(XpTask::getTaskCategory, TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)
.in(XpTask::getStatus,
TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
TaskScheduleConstants.TASK_STATUS_PENDING)
.isNotNull(XpTask::getEndTime)
.gt(XpTask::getEndTime, now)
.le(XpTask::getEndTime, windowEnd)
.eq(XpTask::getWillTimeoutNoticeSent, 0));
}
/**
* 计算剩余分钟数(向上取整,避免出现 0 分钟)
*/
private long calcRemainMinutes(LocalDateTime now, LocalDateTime endTime) {
if (endTime == null) {
return 0;
}
long seconds = Duration.between(now, endTime).getSeconds();
if (seconds <= 0) {
return 0;
}
return (seconds + 59) / 60;
}
private String resolvePlateNumber(XpVehicle vehicle) {
return vehicle == null ? "" : StringUtils.defaultString(vehicle.getPlateNumber());
}
}