“mitao”
2026-03-15 fb836c56d462e676b20328a9d6369f05c49c36c5
bugfix
3个文件已添加
4个文件已修改
465 ■■■■■ 已修改文件
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/AccidentTaskQuartzScheduler.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/AccidentTimeoutJob.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/AccidentWillTimeoutJob.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/TaskCleanDetailVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpTaskMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java
@@ -7,6 +7,7 @@
import com.xiaopeng.api.model.maintenance.XpErrorType;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskHandleDetailRoutine;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.order.XpCruiseOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.account.LoginOperationUser;
@@ -17,9 +18,12 @@
import com.xiaopeng.maintenance.dto.HandleVehicleAccidentDto;
import com.xiaopeng.maintenance.dto.SaveRoutineDetailDto;
import com.xiaopeng.maintenance.dto.SaveVehicleAccidentDto;
import com.xiaopeng.maintenance.job.AccidentTaskQuartzScheduler;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.model.query.TaskPageListQuery;
import com.xiaopeng.maintenance.model.query.XpTaskRecordPageQuery;
import com.xiaopeng.maintenance.model.vo.*;
import com.xiaopeng.maintenance.schedule.InsuranceInspectionScheduleTask;
import com.xiaopeng.maintenance.service.IXpErrorTypeService;
import com.xiaopeng.maintenance.service.XpTaskService;
import com.xiaopeng.maintenance.vo.TaskCleanDetailVO;
@@ -30,6 +34,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
@@ -65,6 +70,14 @@
    private XpFleetOperationsClient xpFleetOperationsClient;
    @Resource
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private InsuranceInspectionScheduleTask insuranceInspectionScheduleTask;
    @Resource
    private XpTaskRuleMapper xpTaskRuleMapper;
    @Resource
    private AccidentTaskQuartzScheduler accidentTaskQuartzScheduler;
    @ApiOperation(value = "事故类型列表", response = XpErrorType.class, tags = "运维APP-任务中心")
    @GetMapping("/api/v1/task/errorTypeList")
@@ -131,7 +144,15 @@
    public R saveVehicleAccident(@RequestBody SaveVehicleAccidentDto dto) {
        LoginOperationUser loginOperationUser = tokenService.getLoginOperationUser();
        XpOperationsUser xpOperationsUser = loginOperationUser.getXpOperationsUser();
        xpTaskService.saveVehicleAccident(dto, xpOperationsUser);
        XpTask task = xpTaskService.saveVehicleAccident(dto, xpOperationsUser);
        try {
            XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
            Integer processingTimeLimit = taskRule == null ? null : taskRule.getProcessingTimeLimit();
            accidentTaskQuartzScheduler.scheduleAccidentTimeoutJobs(task.getId(), task.getEndTime(), processingTimeLimit);
        } catch (Exception e) {
            log.error("事故任务Quartz调度失败,taskId={}", task == null ? null : task.getId(), e);
        }
        return R.ok();
    }
@@ -235,7 +256,12 @@
        XpTask xpTask = xpTaskService.queryInfoByVehicleId(vehicleId);
        return R.ok(xpTask);
    }
    @ApiOperation(value = "执行保险年检检测定时任务", tags = {"定时任务"})
    @GetMapping("/base/task/insurance/execute")
    public R<?> executeInsuranceTask() {
        insuranceInspectionScheduleTask.executeInsuranceInspectionTask();
        return R.ok();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/AccidentTaskQuartzScheduler.java
New file
@@ -0,0 +1,78 @@
package com.xiaopeng.maintenance.job;
import com.xiaopeng.maintenance.job.base.QuartzManager;
import com.xiaopeng.maintenance.job.base.TimeJobType;
import com.xiaopeng.maintenance.job.jobs.AccidentTimeoutJob;
import com.xiaopeng.maintenance.job.jobs.AccidentWillTimeoutJob;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * 事故任务 Quartz 调度器
 *
 * <p>用于为单条事故任务调度“即将超时/超时”单次触发 Job。</p>
 *
 * @author mitao
 * @since 2026-03-15
 */
@Component
public class AccidentTaskQuartzScheduler {
    private static final String JOB_NAME_WILL_TIMEOUT_PREFIX = "ACCIDENT_WILL_TIMEOUT_";
    private static final String JOB_NAME_TIMEOUT_PREFIX = "ACCIDENT_TIMEOUT_";
    /**
     * 调度事故任务通知
     *
     * @param taskId             事故任务ID
     * @param endTime            截止时间
     * @param processingTimeLimit 处理时限(分钟)
     */
    public void scheduleAccidentTimeoutJobs(Long taskId, LocalDateTime endTime, Integer processingTimeLimit) {
        if (taskId == null || taskId <= 0 || endTime == null) {
            return;
        }
        Map<String, Object> jobParam = new HashMap<>();
        jobParam.put("taskId", taskId);
        Integer factorMinutes = calcWillTimeoutFactorMinutes(processingTimeLimit);
        if (factorMinutes != null && factorMinutes > 0) {
            LocalDateTime willTimeoutTime = endTime.minusMinutes(factorMinutes);
            QuartzManager.addJob(
                    AccidentWillTimeoutJob.class,
                    JOB_NAME_WILL_TIMEOUT_PREFIX + taskId,
                    TimeJobType.ACCIDENT_WILL_TIMEOUT,
                    toDate(willTimeoutTime),
                    jobParam
            );
        }
        QuartzManager.addJob(
                AccidentTimeoutJob.class,
                JOB_NAME_TIMEOUT_PREFIX + taskId,
                TimeJobType.ACCIDENT_TIMEOUT,
                toDate(endTime),
                jobParam
        );
    }
    /**
     * 即将超时因子(分钟):floor(processingTimeLimit * 0.2)
     */
    private Integer calcWillTimeoutFactorMinutes(Integer processingTimeLimit) {
        if (processingTimeLimit == null || processingTimeLimit <= 0) {
            return 0;
        }
        return (int) Math.floor(processingTimeLimit * 0.2d);
    }
    private Date toDate(LocalDateTime time) {
        return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/AccidentTimeoutJob.java
New file
@@ -0,0 +1,132 @@
package com.xiaopeng.maintenance.job.jobs;
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.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.job.base.AbstractJob;
import com.xiaopeng.maintenance.job.utils.SpringContextsUtil;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.schedule.AccidentTaskNotificationSupport;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 事故任务超时通知Job(单次触发)
 *
 * <p>触发后按任务处理人推送“超时”消息,并保存通知记录。</p>
 *
 * @author mitao
 * @since 2026-03-15
 */
public class AccidentTimeoutJob extends AbstractJob {
    private static final String PUSH_TITLE = "事故超时提醒";
    private static final String CONTENT_TEMPLATE =
            "车辆%s在%s发生的紧急事故已超时!";
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        long taskIdValue = maps == null ? 0L : maps.getLongValue("taskId");
        if (taskIdValue <= 0) {
            logger.warn("AccidentTimeoutJob 缺少有效taskId");
            return;
        }
        Long taskId = taskIdValue;
        XpTaskMapper taskMapper = SpringContextsUtil.getBean(XpTaskMapper.class);
        AccidentTaskNotificationSupport support = SpringContextsUtil.getBean(AccidentTaskNotificationSupport.class);
        XpOperationUserNotificationClient notificationClient = SpringContextsUtil.getBean(XpOperationUserNotificationClient.class);
        XpTask task = taskMapper.selectById(taskId);
        if (!isValidAccidentTask(task)) {
            return;
        }
        LocalDateTime now = LocalDateTime.now();
        if (task.getEndTime() == null || !task.getEndTime().isBefore(now)) {
            return;
        }
        int affectedRows = taskMapper.update(null, new LambdaUpdateWrapper<XpTask>()
                .set(XpTask::getExpiredNoticeSent, 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)
                .lt(XpTask::getEndTime, now)
                .eq(XpTask::getExpiredNoticeSent, 0));
        if (affectedRows <= 0) {
            return;
        }
        Set<Long> userIds = support.parseUserIds(task.getHandleUserId());
        if (CollectionUtils.isEmpty(userIds)) {
            logger.warn("AccidentTimeoutJob 事故任务未配置有效处理人,taskId={}", taskId);
            return;
        }
        Map<Long, XpOperationsUser> userMap = support.fetchOperationUsers(userIds);
        List<XpOperationsUser> users = userIds.stream()
                .map(userMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(users)) {
            logger.warn("AccidentTimeoutJob 未匹配到有效运维人员,taskId={}", taskId);
            return;
        }
        String plateNumber = "";
        if (task.getVehicleId() != null) {
            Map<Long, XpVehicle> vehicleMap = support.fetchVehicles(Collections.singleton(task.getVehicleId()));
            XpVehicle vehicle = vehicleMap.get(task.getVehicleId());
            plateNumber = vehicle == null ? "" : StringUtils.defaultString(vehicle.getPlateNumber());
        }
        String vehicleLocation = StringUtils.defaultString(task.getVehicleLocation());
        String content = String.format(CONTENT_TEMPLATE, plateNumber, vehicleLocation);
        String bodyJson = support.buildPushBodyJson(content, NotificationTypeEnum.ACCIDENT_TIMEOUT.getCode(), taskId);
        List<XpOperationsUserNotification> notifications = support.buildNotifications(
                users,
                NotificationTypeEnum.ACCIDENT_TIMEOUT.getCode(),
                content,
                taskId,
                bodyJson
        );
        if (!CollectionUtils.isEmpty(notifications)) {
            notificationClient.addNotificationBatch(notifications);
        }
        support.pushToUsersByDeviceType(users, PUSH_TITLE, bodyJson);
    }
    private boolean isValidAccidentTask(XpTask task) {
        if (task == null || task.getId() == null) {
            return false;
        }
        if (!Objects.equals(task.getTaskCategory(), TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)) {
            return false;
        }
        return Objects.equals(task.getStatus(), TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE)
                || Objects.equals(task.getStatus(), TaskScheduleConstants.TASK_STATUS_PENDING);
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/job/jobs/AccidentWillTimeoutJob.java
New file
@@ -0,0 +1,148 @@
package com.xiaopeng.maintenance.job.jobs;
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.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.job.base.AbstractJob;
import com.xiaopeng.maintenance.job.utils.SpringContextsUtil;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.schedule.AccidentTaskNotificationSupport;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.util.CollectionUtils;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 事故任务即将超时通知Job(单次触发)
 *
 * <p>触发后按任务处理人推送“即将超时”消息,并保存通知记录。</p>
 *
 * @author mitao
 * @since 2026-03-15
 */
public class AccidentWillTimeoutJob extends AbstractJob {
    private static final String PUSH_TITLE = "事故即将超时提醒";
    private static final String CONTENT_TEMPLATE =
            "车辆%s在%s发生的紧急事故即将超时,剩余时间%s分钟,请及时到达现场!";
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap maps = context.getMergedJobDataMap();
        long taskIdValue = maps == null ? 0L : maps.getLongValue("taskId");
        if (taskIdValue <= 0) {
            logger.warn("AccidentWillTimeoutJob 缺少有效taskId");
            return;
        }
        Long taskId = taskIdValue;
        XpTaskMapper taskMapper = SpringContextsUtil.getBean(XpTaskMapper.class);
        AccidentTaskNotificationSupport support = SpringContextsUtil.getBean(AccidentTaskNotificationSupport.class);
        XpOperationUserNotificationClient notificationClient = SpringContextsUtil.getBean(XpOperationUserNotificationClient.class);
        XpTask task = taskMapper.selectById(taskId);
        if (!isValidAccidentTask(task)) {
            return;
        }
        LocalDateTime now = LocalDateTime.now();
        if (task.getEndTime() == null || !task.getEndTime().isAfter(now)) {
            return;
        }
        int affectedRows = taskMapper.update(null, new LambdaUpdateWrapper<XpTask>()
                .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)
                .eq(XpTask::getWillTimeoutNoticeSent, 0));
        if (affectedRows <= 0) {
            return;
        }
        Set<Long> userIds = support.parseUserIds(task.getHandleUserId());
        if (CollectionUtils.isEmpty(userIds)) {
            logger.warn("AccidentWillTimeoutJob 事故任务未配置有效处理人,taskId={}", taskId);
            return;
        }
        Map<Long, XpOperationsUser> userMap = support.fetchOperationUsers(userIds);
        List<XpOperationsUser> users = userIds.stream()
                .map(userMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(users)) {
            logger.warn("AccidentWillTimeoutJob 未匹配到有效运维人员,taskId={}", taskId);
            return;
        }
        String plateNumber = "";
        if (task.getVehicleId() != null) {
            Map<Long, XpVehicle> vehicleMap = support.fetchVehicles(Collections.singleton(task.getVehicleId()));
            XpVehicle vehicle = vehicleMap.get(task.getVehicleId());
            plateNumber = vehicle == null ? "" : StringUtils.defaultString(vehicle.getPlateNumber());
        }
        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 = support.buildPushBodyJson(content, NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(), taskId);
        List<XpOperationsUserNotification> notifications = support.buildNotifications(
                users,
                NotificationTypeEnum.ACCIDENT_WILL_TIMEOUT.getCode(),
                content,
                taskId,
                bodyJson
        );
        if (!CollectionUtils.isEmpty(notifications)) {
            notificationClient.addNotificationBatch(notifications);
        }
        support.pushToUsersByDeviceType(users, PUSH_TITLE, bodyJson);
    }
    private boolean isValidAccidentTask(XpTask task) {
        if (task == null || task.getId() == null) {
            return false;
        }
        if (!Objects.equals(task.getTaskCategory(), TaskScheduleConstants.TASK_CATEGORY_ACCIDENT)) {
            return false;
        }
        return Objects.equals(task.getStatus(), TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE)
                || Objects.equals(task.getStatus(), TaskScheduleConstants.TASK_STATUS_PENDING);
    }
    /**
     * 计算剩余分钟数(向上取整,避免出现 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;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/model/vo/TaskCleanDetailVO.java
@@ -54,4 +54,11 @@
    @ApiModelProperty(value = "操作人名称")
    private String operationsUserName;
    @ApiModelProperty(value = "处理人姓名")
    private String handleUserName;
    @ApiModelProperty(value = "处理时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime handleTime;
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -778,7 +778,10 @@
        xpTaskHandleDetailRoutine.setOperationsUserId(id);
        xpTaskHandleDetailRoutineMapper.insert(xpTaskHandleDetailRoutine);
        //更新任务状态为已完成
        lambdaUpdate().set(XpTask::getStatus, 2).eq(XpTask::getId, dto.getId()).update();
        lambdaUpdate().set(XpTask::getStatus, 2).set(XpTask::getHandleUserId, id)
                .set(XpTask::getHandleTime, LocalDateTime.now())
                .eq(XpTask::getId, dto.getId())
                .update();
        return R.ok();
    }
@@ -812,6 +815,7 @@
        res.setTaskCategory(task.getTaskCategory());
        res.setTaskTypeId(task.getTaskTypeId());
        res.setStatus(task.getStatus());
        res.setHandleTime(task.getHandleTime());
        XpVehicleClean vehicleDetection = vehicleCleanMapper.selectOne(new LambdaQueryWrapper<XpVehicleClean>()
                .eq(XpVehicleClean::getTaskId, id).last("limit 1"));
        if (vehicleDetection != null) {
@@ -821,6 +825,7 @@
                XpOperationsUser operationsUser = xpOperationUserClient.queryById(vehicleDetection.getOperationsUserId()).getData();
                if (operationsUser != null) {
                    res.setOperationsUserName(operationsUser.getNickName());
                    res.setHandleUserName(operationsUser.getNickName());
                }
            }
        }
@@ -851,12 +856,59 @@
                return R.fail("任务已被处理");
            }
            Long vehicleId = task.getVehicleId();
            String plateNumber = "";
            try {
                if (vehicleId != null) {
                    R<XpVehicle> vehicleResult = xpVehicleClient.queryById(vehicleId);
                    XpVehicle vehicle = vehicleResult == null ? null : vehicleResult.getData();
                    if (vehicle != null && org.apache.commons.lang3.StringUtils.isNotBlank(vehicle.getPlateNumber())) {
                        plateNumber = vehicle.getPlateNumber();
                    }
                }
            } catch (Exception e) {
                log.error("confirmArrive查询车辆信息失败,taskId={}, vehicleId={}", id, vehicleId, e);
            }
            String taskCode = org.apache.commons.lang3.StringUtils.isNotBlank(task.getTaskCode())
                    ? task.getTaskCode()
                    : String.valueOf(task.getId());
            // 更新任务
            task.setStatus(1);
            task.setVehicleId(null);
            task.setHandleUserId(userId.toString());
            this.updateById(task);
            // 保存通知记录 + 推送阿里云 NOTICE 消息(不影响主流程)
            try {
                String content = String.format("你处理的%s关联车辆:%s状态由待到达变更为待处理", taskCode, plateNumber);
                String bodyJson = buildTaskStatusChangedPushBodyJson(content, id);
                XpOperationsUserNotification notification = new XpOperationsUserNotification();
                notification.setNotificationType(NotificationTypeEnum.ACCIDENT_STATUS_CHANGED.getCode());
                notification.setNotificationContent(content);
                notification.setOperationsUserId(userId);
                notification.setDataId(id);
                notification.setReadStatus(0);
                notification.setNotificationExt(bodyJson);
                xpOperationUserNotificationClient.addNotificationBatch(Collections.singletonList(notification));
                XpOperationsUser operationsUser = xpOperationUserClient.queryById(userId).getData();
                if (operationsUser != null
                        && operationsUser.getDeviceType() != null
                        && StringUtils.isNotBlank(operationsUser.getPhoneNumber())) {
                    xpAliPushMessageClient.pushMessage(AliPushMessageDTO.builder()
                            .deviceType(operationsUser.getDeviceType())
                            .account(operationsUser.getPhoneNumber())
                            .title("任务状态变更")
                            .body(bodyJson)
                            .pushType(AliPushTypeConstant.NOTICE)
                            .build());
                }
            } catch (Exception e) {
                log.error("confirmArrive消息通知失败,taskId={}, userId={}", id, userId, e);
            }
            return R.ok();
        } catch (InterruptedException e) {
@@ -870,6 +922,18 @@
        }
    }
    /**
     * 构建任务状态变更推送 body JSON
     */
    private String buildTaskStatusChangedPushBodyJson(String content, Long taskId) {
        Map<String, Object> param = new HashMap<>();
        param.put("content", content);
        param.put("strategy", NotificationTypeEnum.ACCIDENT_STATUS_CHANGED.getCode());
        param.put("type", TaskTypeNotificationEnum.ACCIDENT_TASK.getCode());
        param.put("id", taskId == null ? "0" : taskId.toString());
        return JSONObject.toJSONString(param);
    }
    @Override
    public PageInfo<XpTaskRecordVO> queryTaskByVehicleId(XpTaskRecordPageQuery query) {
        // 1. 构建查询条件
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpTaskMapper.xml
@@ -86,13 +86,13 @@
        <if test="query.sortType != null">
            <if test="query.sortType == 1">
                order by t1.task_urgency_level
                order by t1.create_time desc, t1.task_urgency_level
                <if test="query.sortRule == 2">
                    desc
                </if>
            </if>
            <if test="query.sortType == 2">
                order by t2.plate_number
                order by t1.create_time desc, t2.plate_number
                <if test="query.sortRule == 2">
                    desc
                </if>