liujie
2026-06-09 c96fbe6b0aeb413a17c429133ee6d81d138144b9
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -1,36 +1,51 @@
package com.xiaopeng.maintenance.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO;
import com.xiaopeng.api.feignClient.account.SysUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.feignClient.operation.SmsClient;
import com.xiaopeng.api.feignClient.pushMessage.WebSocketMessageClient;
import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetOperationsClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.SysUser;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.maintenance.*;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.XpTaskVO;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.operation.SmsVo;
import com.xiaopeng.common.core.constant.AliPushTypeConstant;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.NotificationTypeEnum;
import com.xiaopeng.common.core.enums.TaskTypeEnum;
import com.xiaopeng.common.core.enums.TaskTypeNotificationEnum;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.SensitiveDataCryptoUtil;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.evi.oss.service.AliOssService;
import com.xiaopeng.maintenance.constant.I18nKeyConstant;
import com.xiaopeng.maintenance.dto.HandleVehicleAccidentDto;
import com.xiaopeng.maintenance.dto.SaveRemoteDrivingAccidentDto;
import com.xiaopeng.maintenance.dto.SaveErrorTaskDto;
import com.xiaopeng.maintenance.dto.SaveRoutineDetailDto;
import com.xiaopeng.maintenance.dto.SaveVehicleAccidentDto;
@@ -40,22 +55,30 @@
import com.xiaopeng.maintenance.model.query.WorkBenchQuery;
import com.xiaopeng.maintenance.model.query.XpTaskRecordPageQuery;
import com.xiaopeng.maintenance.model.vo.*;
import com.xiaopeng.maintenance.repo.XpOperationsUserRepository;
import com.xiaopeng.maintenance.service.IXpClockInService;
import com.xiaopeng.maintenance.service.IXpTaskRuleRoleService;
import com.xiaopeng.maintenance.service.IXpTaskRuleService;
import com.xiaopeng.maintenance.service.XpTaskService;
import com.xiaopeng.maintenance.utils.AliOssDownloadUtil;
import com.xiaopeng.maintenance.utils.GDMapGeocodingUtil;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import com.xiaopeng.maintenance.vo.TaskCleanDetailVO;
import com.xiaopeng.maintenance.vo.TaskDetectionDetailVO;
import com.xiaopeng.maintenance.vo.TaskReassignmentVo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
@@ -66,6 +89,7 @@
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@@ -106,22 +130,22 @@
    @Autowired
    private XpVehicleCleanMapper vehicleCleanMapper;
    @Autowired
    @Resource
    private XpOperationUserClient xpOperationUserClient;
    @Autowired
    @Resource
    private SysUserClient sysUserClient;
    @Autowired
    private XpTaskHandleDetailRoutineMapper xpTaskHandleDetailRoutineMapper;
    @Autowired
    @Resource
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpTaskRuleMapper xpTaskRuleMapper;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    @Resource
    private XpAliPushMessageClient xpAliPushMessageClient;
    @Autowired
    @Resource
    private XpOperationUserNotificationClient xpOperationUserNotificationClient;
    @Autowired
    private IXpTaskRuleRoleService xpTaskRuleRoleService;
@@ -139,6 +163,14 @@
    @Resource
    private AliOssService aliOssService;
    @Autowired
    private AliOssDownloadUtil aliOssDownloadUtil;
    @Resource
    private WebSocketMessageClient xpWebSocketMessageClient;
    @Resource
    private IXpClockInService xpClockInService;
    @Override
    public PageInfo<WorkBenchVO> workbench(WorkBenchQuery query) throws UnsupportedEncodingException {
@@ -150,7 +182,13 @@
            if (StringUtils.isNotBlank(vehiclePictures)) {
                JSONObject jsonObject = JSONObject.parseObject(vehiclePictures);
                //1
                String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                String downloadUrl = jsonObject.getString("url");
                ;
                try {
                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                } catch (UnsupportedEncodingException e) {
                    log.error("获取图片下载地址失败", e);
                }
                jsonObject.put("url", downloadUrl);
                workBenchVO.setVehiclePictures(jsonObject.toJSONString());
            }
@@ -204,7 +242,6 @@
    }
    @Override
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
@@ -214,37 +251,34 @@
        String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
        xpTask.setTaskCode(code);
        xpTask.setTaskCategory(2);
        String[] split = dto.getTaskTypeId().split(",");
        List<String> typeIds = new ArrayList<>();
        for (String s : split) {
            typeIds.add(s.trim());
        }
        StringBuilder temp = new StringBuilder(dto.getTaskTypeId());
        List<Long> children = typeIds.stream().map(Long::parseLong).collect(Collectors.toList());
        List<Long> parentIds = errorTypeMapper.selectList(new LambdaQueryWrapper<XpErrorType>()
                .in(XpErrorType::getId, children)).stream().map(XpErrorType::getParentId).distinct().collect(Collectors.toList());
        for (Long parentId : parentIds) {
            temp.append(",").append(parentId);
        }
        xpTask.setTaskTypeId(temp.toString());
        xpTask.setTaskTypeId(normalizeAccidentTaskTypeId(dto.getTaskTypeId()));
        xpTask.setTaskUrgencyLevel(dto.getTaskUrgencyLevel());
        xpTask.setTaskDescription(dto.getTaskDescription());
        xpTask.setTaskImage(dto.getTaskImage());
        xpTask.setVehicleId(dto.getId());
        XpVehicle vehicle = xpVehicleClient.queryById(xpTask.getVehicleId()).getData();
//        if (vehicle!=null){
//            VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vehicle.getVehicleVin())), VehiclePosition.class);
//            if (vehiclePosition != null) {
//                double x = vehiclePosition.getPoint().getX();
//                double y = vehiclePosition.getPoint().getY();
//                xpTask.setVehicleLon(new BigDecimal(x));
//                xpTask.setVehicleLat(new BigDecimal(y));
//            }
//        }
        if (Objects.isNull(vehicle)) {
            throw new ServiceException("上报失败,未获取到车辆信息", I18nKeyConstant.TASK_VEHICLE_NOT_EXIST);
        }
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        if (Objects.isNull(vehiclePosition)) {
            log.error("事故上报未获取到车辆当前定位");
            throw new ServiceException("获取车辆实时定位信息失败,请稍后重试", I18nKeyConstant.TASK_SAVE_ERROR);
        }
        xpTask.setVehicleLon(new BigDecimal(vehiclePosition.getPoint().getX()));
        xpTask.setVehicleLat(new BigDecimal(vehiclePosition.getPoint().getY()));
        Map<String, String> geocode = gdMapGeocodingUtil.geocode(xpTask.getVehicleLon().toString(), xpTask.getVehicleLat().toString());
        String address = geocode == null ? null : geocode.get("address");
        if (StringUtils.isBlank(address)) {
            log.error("事故上报通过车辆定位从高德获取地址信息失败");
            throw new ServiceException("获取车辆实时定位信息失败,请稍后重试", I18nKeyConstant.TASK_SAVE_ERROR);
        }
        xpTask.setVehicleLocation(address);
// 临时处理
        xpTask.setVehicleLon(new BigDecimal("104.055683"));
        xpTask.setVehicleLat(new BigDecimal("30.585991"));
        xpTask.setVehicleLocation("四川省成都市武侯区石羊街道天府新谷");
//        xpTask.setVehicleLon(new BigDecimal("104.055683"));
//        xpTask.setVehicleLat(new BigDecimal("30.585991"));
//        xpTask.setVehicleLocation("四川省成都市武侯区石羊街道天府新谷");
        xpTask.setVehicleId(dto.getId());
        xpTask.setReportUserId(xpOperationsUser.getId());
        xpTask.setReportTime(LocalDateTime.now());
@@ -264,7 +298,61 @@
            List<Long> userIds = xpOperationsRoleResult.getData().stream().filter(x -> ids.contains(x.getOperationsUserId()))
                    .map(XpOperationsRole::getOperationsUserId)
                    .distinct().collect(Collectors.toList());
            xpTask.setHandleUserId(StringUtils.join(userIds, ","));
            if (CollUtil.isEmpty(userIds)) {
                throw new ServiceException("未配置车队运维人员");
            }
            //获取首选人的实时定位,计算每个人到事故车辆位置的导航距离
            List<Operations> operationsList = new ArrayList<>();
            for (Long userId : userIds) {
                Operations operations = new Operations();
                operations.setUserId(userId);
                //获取运维人员实时定位
                XpOperationsUserPositionDTO operationsUserPositionDTO = mongoTemplate.findOne(new Query(Criteria.where("id").is(userId)), XpOperationsUserPositionDTO.class);
                if (null != operationsUserPositionDTO) {
                    LonLatDto start = new LonLatDto(operationsUserPositionDTO.getLongitude(), operationsUserPositionDTO.getLatitude());
                    LonLatDto end = new LonLatDto(xpTask.getVehicleLon().doubleValue(), xpTask.getVehicleLat().doubleValue());
                    //获取导航线路
                    RouteVo routeVo = gdMapGeocodingUtil.getDistanceAndTime(start, end, null, null, null, null);
                    operations.setDistance(routeVo.getDistance());
                    operationsList.add(operations);
                }
            }
            if(operationsList.isEmpty()){
                throw new ServiceException("未找到合适的运维人员");
            }
            //对距离进行排序,去距离最近的人员
            operationsList.sort((o1, o2) -> o1.getDistance().compareTo(o2.getDistance()) * -1);
            Operations operations = operationsList.get(0);
            Long userId = operations.getUserId();
            //如果多人距离相同,可按照上班状态>空闲状态>工号排序
            List<Operations> collect = operationsList.stream().filter(s -> s.getDistance().equals(operations.getDistance())).collect(Collectors.toList());
            if (collect.size() > 1) {
                //查询已经上班打卡了的人员
                List<XpClockIn> xpClockIns = xpClockInService.list(new QueryWrapper<XpClockIn>().in("app_user_id", collect)
                        .isNull("clock_out_result").last(" and DATE(now()) = DATE(clock_in_time) and clock_out_time is null"));
                if (xpClockIns.size() == 1) {
                    userId = xpClockIns.get(0).getAppUserId();
                } else if (xpClockIns.size() > 1) {
                    //查询有任务的人员
                    List<Long> collect1 = xpClockIns.stream().map(XpClockIn::getAppUserId).collect(Collectors.toList());
                    List<XpTask> list1 = this.list(new QueryWrapper<XpTask>().eq("task_category", 2).in("handle_user_id", collect1)
                            .in("status", Arrays.asList(0, 1, 4)));
                    if (list1.size() > 0) {
                        //对工号进行排序
                        List<String> collect2 = list1.stream().map(XpTask::getHandleUserId).collect(Collectors.toList());
                        //找出没有任务的人员
                        List<Long> collect3 = collect1.stream().filter(s -> !collect2.contains(s.toString())).collect(Collectors.toList());
                        //查询人员数据,对其进行工号排序
                        XpOperationsUserQueryDTO dto1 = new XpOperationsUserQueryDTO();
                        dto1.setIds(collect3);
                        List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.findList(dto1).getData();
                        data.sort((o1, o2) -> o1.getUniqueNumber().compareTo(o2.getUniqueNumber()) * -1);
                        userId = data.get(0).getId();
                    }
                }
            }
            xpTask.setHandleUserId(userId.toString());
        }
        // 根据事故处理限时配置设置任务截止时间
        LocalDateTime endTime = LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit());
@@ -272,7 +360,7 @@
        xpTask.setExpiredNoticeSent(0);
        xpTask.setWillTimeoutNoticeSent(0);
        this.save(xpTask);
        if (xpTask.getId() == null && StringUtils.isNotBlank(xpTask.getTaskCode())) {
        if (xpTask.getId() == null && StringUtils.isNotEmpty(xpTask.getTaskCode())) {
            XpTask savedTask = this.getOne(new LambdaQueryWrapper<XpTask>()
                    .eq(XpTask::getTaskCode, xpTask.getTaskCode())
                    .last("limit 1"));
@@ -287,10 +375,109 @@
        return xpTask;
    }
    @Override
    @Transactional
    @ShardingSphereTransactionType(TransactionType.BASE)
    public XpTask saveRemoteDrivingAccident(SaveRemoteDrivingAccidentDto dto) {
        validateRemoteDrivingAccidentDto(dto);
        XpVehicle vehicle = xpVehicleClient.findByVehicleVin(dto.getVin().trim()).getData();
        if (Objects.isNull(vehicle)) {
            throw new ServiceException("上报失败,未获取到车辆信息", I18nKeyConstant.TASK_VEHICLE_NOT_EXIST);
        }
        LocalDateTime reportTime = parseRemoteDrivingTaskTime(dto.getTaskTime());
        Integer taskSource = parseTaskSource(dto.getSource());
        XpTask xpTask = new XpTask();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
        xpTask.setTaskCode(code);
        xpTask.setTaskCategory(2);
        xpTask.setTaskTypeId(normalizeAccidentTaskTypeId(dto.getTaskTypeId()));
        xpTask.setTaskUrgencyLevel(mapRemoteDrivingUrgencyLevel(dto.getTaskUrgencyLevel()));
        xpTask.setTaskDescription(dto.getTaskDescription().trim());
        xpTask.setTaskImage(normalizeTaskImage(dto.getTaskImage()));
        xpTask.setVehicleId(vehicle.getId());
//        xpTask.setVehicleLocation(dto.getVehicleLocation().trim());
//        xpTask.setVehicleLon(dto.getVehicleLon());
//        xpTask.setVehicleLat(dto.getVehicleLat());
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        if (Objects.isNull(vehiclePosition)) {
            log.error("远程代驾事故上报未获取到车辆当前定位");
            throw new ServiceException("获取车辆实时定位信息失败,请稍后重试", I18nKeyConstant.TASK_SAVE_ERROR);
        }
        xpTask.setVehicleLon(new BigDecimal(vehiclePosition.getPoint().getX()));
        xpTask.setVehicleLat(new BigDecimal(vehiclePosition.getPoint().getY()));
        Map<String, String> geocode = gdMapGeocodingUtil.geocode(xpTask.getVehicleLon().toString(), xpTask.getVehicleLat().toString());
        String address = geocode == null ? null : geocode.get("address");
        if (StringUtils.isBlank(address)) {
            log.error("远程代驾事故上报通过车辆定位从高德获取地址信息失败");
            throw new ServiceException("获取车辆实时定位信息失败,请稍后重试", I18nKeyConstant.TASK_SAVE_ERROR);
        }
        xpTask.setVehicleLocation(address);
        xpTask.setReportUserId(0L);
        xpTask.setReportTime(reportTime);
        xpTask.setStatus(0);
        xpTask.setTaskSource(taskSource);
        xpTask.setCreateBy(dto.getSource().trim());
        XpTaskRule taskRule = xpTaskRuleService.lambdaQuery().last("limit 1").one();
        List<XpTaskRuleRole> list = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, 9).list();
        if (CollUtil.isEmpty(list) || Objects.isNull(taskRule)) {
            throw new ServiceException("未配置车辆事故任务分配规则");
        }
        List<Long> roleIds = list.stream().map(XpTaskRuleRole::getRoleId).collect(Collectors.toList());
        R<List<XpOperationsRole>> xpOperationsRoleResult = xpOperationUserClient.getXpOperationsRoleByRoleIds(roleIds);
        if (CollUtil.isNotEmpty(xpOperationsRoleResult.getData())) {
            Long fleetId = xpFleetVehicleClient.getFleetIdByVehicleId(vehicle.getId()).getData();
            List<Long> ids = xpFleetOperationsClient.getOperationsIdsByFleetId(fleetId).getData();
            // 根据配置的事故处理角色和事故车辆所属车队人员,确定最终的任务分配人列表(取交集)
            List<Long> userIds = xpOperationsRoleResult.getData().stream()
                    .map(XpOperationsRole::getOperationsUserId)
                    .filter(ids::contains)
                    .distinct()
                    .collect(Collectors.toList());
            xpTask.setHandleUserId(StringUtils.join(userIds, ","));
        }
        // 根据事故处理限时配置设置任务截止时间
        xpTask.setEndTime(reportTime.plusMinutes(taskRule.getProcessingTimeLimit()));
        xpTask.setExpiredNoticeSent(0);
        xpTask.setWillTimeoutNoticeSent(0);
        this.save(xpTask);
        if (xpTask.getId() == null && StringUtils.isNotBlank(xpTask.getTaskCode())) {
            XpTask savedTask = this.getOne(new LambdaQueryWrapper<XpTask>()
                    .eq(XpTask::getTaskCode, xpTask.getTaskCode())
                    .last("limit 1"));
            if (savedTask != null && savedTask.getId() != null) {
                xpTask.setId(savedTask.getId());
            }
        }
        sendAccidentNotificationAsync(xpTask, vehicle.getPlateNumber(), taskRule.getProcessingTimeLimit());
        return xpTask;
    }
    @Data
    class Operations {
        /**
         * 候选人id
         */
        private Long userId;
        /**
         * 距离
         */
        private Integer distance;
    }
    /**
     * 异步发送事故通知
     * @param xpTask 任务信息
     * @param plateNumber 车牌号
     *
     * @param xpTask              任务信息
     * @param plateNumber         车牌号
     * @param processingTimeLimit 处理时限(分钟)
     */
    private void sendAccidentNotificationAsync(XpTask xpTask, String plateNumber, Integer processingTimeLimit) {
@@ -313,32 +500,8 @@
                }
                // 构建通知内容
                String content = String.format("车辆%s在%s发生了紧急事故,请在%d分钟内到达现场!", plateNumber, xpTask.getVehicleLocation(), processingTimeLimit);
                String bodyJson = buildAccidentPushBodyJson(content, xpTask);
                // 构建通知记录(包含 notificationExt)
                List<XpOperationsUserNotification> notifications = data.stream().map(item -> {
                    XpOperationsUserNotification notification = new XpOperationsUserNotification();
                    notification.setOperationsUserId(item.getId());
                    notification.setNotificationType(NotificationTypeEnum.ACCIDENT_NEW.getCode());
                    notification.setNotificationContent(content);
                    notification.setDataId(xpTask.getId());
                    notification.setReadStatus(0);
                    notification.setNotificationExt(bodyJson);
                    return notification;
                }).collect(Collectors.toList());
                // 批量保存通知记录
                xpOperationUserNotificationClient.addNotificationBatch(notifications);
                log.info("事故通知记录保存成功,共 {} 条", notifications.size());
                // 按设备类型分组发送阿里云推送
                Map<Integer, List<XpOperationsUser>> operationUserMap = data.stream()
                        .filter(u -> u.getDeviceType() != null && u.getPhoneNumber() != null)
                        .collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
                for (Map.Entry<Integer, List<XpOperationsUser>> entry : operationUserMap.entrySet()) {
                    pushAliMessage(entry.getValue(), content, xpTask);
                }
                String content = buildAccidentNotificationContent(xpTask, plateNumber, processingTimeLimit);
                saveAndPushAccidentNotification(data, content, xpTask);
            } catch (Exception e) {
                log.error("发送事故通知失败,任务ID={}", xpTask.getId(), e);
            }
@@ -348,9 +511,54 @@
    }
    /**
     * 异步发送事故任务分配通知
     *
     * @param xpTask              任务信息
     * @param handlerUserId       当前分配的处理人
     * @param plateNumber         车牌号
     * @param processingTimeLimit 处理时限(分钟)
     */
    private void sendAssignedAccidentNotificationAsync(XpTask xpTask, Long handlerUserId, String plateNumber, Integer processingTimeLimit) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1, 1, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()
        );
        executor.execute(() -> {
            try {
                R<List<XpOperationsUser>> xpOperationUserResult = xpOperationUserClient.listByIds(Collections.singletonList(handlerUserId));
                List<XpOperationsUser> data = xpOperationUserResult == null ? Collections.emptyList() : xpOperationUserResult.getData();
                if (CollUtil.isEmpty(data)) {
                    return;
                }
                String content = buildAccidentNotificationContent(xpTask, plateNumber, processingTimeLimit);
                saveAndPushAccidentNotification(data, content, xpTask);
            } catch (Exception e) {
                log.error("发送事故任务分配通知失败,任务ID={}, handlerUserId={}", xpTask.getId(), handlerUserId, e);
            }
        });
        executor.shutdown();
    }
    /**
     * 构建事故通知文案
     *
     * @param xpTask              任务信息
     * @param plateNumber         车牌号
     * @param processingTimeLimit 处理时限(分钟)
     * @return 通知内容
     */
    private String buildAccidentNotificationContent(XpTask xpTask, String plateNumber, Integer processingTimeLimit) {
        return String.format("车辆%s在%s发生了紧急事故,请在%d分钟内到达现场!", plateNumber, xpTask.getVehicleLocation(), processingTimeLimit);
    }
    /**
     * 构建事故推送 body JSON
     *
     * @param content 消息内容
     * @param xpTask 任务信息
     * @param xpTask  任务信息
     * @return JSON 字符串
     */
    private String buildAccidentPushBodyJson(String content, XpTask xpTask) {
@@ -361,6 +569,45 @@
        param.put("id", xpTask.getId().toString());
        return JSONObject.toJSONString(param);
    }
    /**
     * 保存事故通知记录并发送阿里云推送
     *
     * @param data    接收通知的运维人员
     * @param content 通知内容
     * @param xpTask  任务信息
     */
    private void saveAndPushAccidentNotification(List<XpOperationsUser> data, String content, XpTask xpTask) {
        String bodyJson = buildAccidentPushBodyJson(content, xpTask);
        List<XpOperationsUserNotification> notifications = data.stream().map(item -> {
            XpOperationsUserNotification notification = new XpOperationsUserNotification();
            notification.setOperationsUserId(item.getId());
            notification.setNotificationType(NotificationTypeEnum.ACCIDENT_NEW.getCode());
            notification.setNotificationContent(content);
            notification.setDataId(xpTask.getId());
            notification.setReadStatus(0);
            notification.setNotificationExt(bodyJson);
            return notification;
        }).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(notifications)) {
            xpOperationUserNotificationClient.addNotificationBatch(notifications);
            log.info("事故通知记录保存成功,共 {} 条", notifications.size());
        }
        XpTaskVO xpTaskVO = new XpTaskVO();
        BeanUtil.copyProperties(xpTask, xpTaskVO);
        xpWebSocketMessageClient.pushTask(xpTaskVO);
        Map<Integer, List<XpOperationsUser>> operationUserMap = data.stream()
                .filter(u -> u.getDeviceType() != null && StringUtils.isNotBlank(u.getPhoneNumber()))
                .collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
        for (Map.Entry<Integer, List<XpOperationsUser>> entry : operationUserMap.entrySet()) {
            pushAliMessage(entry.getValue(), content, xpTask);
        }
    }
    /**
     * 推送阿里云通知
@@ -408,7 +655,8 @@
        res.setPlateNumber(vehicle.getPlateNumber());
        res.setVehicleColor(vehicle.getVehicleColor());
        res.setVehicleCode(vehicle.getVehicleCode());
        res.setVehicleVin(vehicle.getVehicleVin());
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        res.setVehicleVin(vin);
        res.setVehicleLocation(task.getVehicleLocation());
        res.setCreateBy(task.getCreateBy());
        res.setCreateTime(task.getCreateTime());
@@ -432,21 +680,10 @@
        if (Objects.nonNull(xpTaskRule)) {
            res.setCanArriveRange(xpTaskRule.getCanArriveRange());
        }
        String taskImage = res.getTaskImage();
        if (StringUtils.isNotBlank(taskImage)) {
            JSONArray jsonArray = JSONArray.parseArray(taskImage);
            for (Object o : jsonArray) {
                JSONObject jsonObject = (JSONObject)o;
                // 1
                String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                jsonObject.put("url", downloadUrl);
            }
            res.setTaskImage(JSONArray.toJSONString(jsonArray));
        }
        res.setTaskImage(aliOssDownloadUtil.getPictureJsonArray(res.getTaskImage()));
        return R.ok(res);
    }
    @ShardingSphereTransactionType(TransactionType.BASE)
    @Transactional(rollbackFor = Exception.class)
    @Override
@@ -509,7 +746,8 @@
        res.setPlateNumber(vehicle.getPlateNumber());
        res.setVehicleColor(vehicle.getVehicleColor());
        res.setVehicleCode(vehicle.getVehicleCode());
        res.setVehicleVin(vehicle.getVehicleVin());
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        res.setVehicleVin(vin);
        res.setVehicleLocation(task.getVehicleLocation());
        res.setCreateBy(task.getCreateBy());
        res.setCreateTime(task.getCreateTime());
@@ -533,20 +771,7 @@
                res.setHandleUserName(data.getNickName());
            }
        }
        String taskImage = res.getTaskImage();
        if (StringUtils.isNotBlank(taskImage)) {
            JSONArray jsonArray = JSONArray.parseArray(taskImage);
            for (Object o : jsonArray) {
                JSONObject jsonObject = (JSONObject)o;
                // 1
                String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                jsonObject.put("url", downloadUrl);
            }
            res.setTaskImage(JSONArray.toJSONString(jsonArray));
        }
        res.setTaskImage(aliOssDownloadUtil.getPictureJsonArray(res.getTaskImage()));
        return R.ok(res);
    }
@@ -573,6 +798,8 @@
        res.setPlateNumber(vehicle.getPlateNumber());
        res.setVehicleColor(vehicle.getVehicleColor());
        res.setVehicleCode(vehicle.getVehicleCode());
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        res.setVehicleVin(vin);
        res.setVehicleLocation(task.getVehicleLocation());
        res.setCreateBy(task.getCreateBy());
        res.setCreateTime(task.getCreateTime());
@@ -581,50 +808,13 @@
        res.setTaskTypeId(task.getTaskTypeId());
        res.setStatus(task.getStatus());
        XpChargingRecord chargingRecord = chargingRecordMapper.selectOne(new LambdaQueryWrapper<XpChargingRecord>()
                .eq(XpChargingRecord::getTaskId, id).last("limit 1"));
                .eq(XpChargingRecord::getTaskId, id).last("limit 1").orderByDesc(XpChargingRecord::getCreateTime));
        if (chargingRecord != null) {
            res.setStartRemainingBattery(chargingRecord.getStartRemainingBattery());
            String startChargingPicture = res.getStartChargingPicture();
            if (StringUtils.isNotBlank(startChargingPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(startChargingPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    String downloadUrl = null;
                    try {
                        //1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setStartChargingPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setStartChargingPicture(aliOssDownloadUtil.getPictureJsonArray(chargingRecord.getStartChargingPicture()));
            res.setStartTime(chargingRecord.getStartTime());
            res.setEndRemainingBattery(chargingRecord.getEndRemainingBattery());
            String endChargingPicture = chargingRecord.getEndChargingPicture();
            if (StringUtils.isNotBlank(endChargingPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(endChargingPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    String downloadUrl = null;
                    try {
                        //1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setEndChargingPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setEndChargingPicture(aliOssDownloadUtil.getPictureJsonArray(chargingRecord.getEndChargingPicture()));
            res.setEndTime(chargingRecord.getEndTime());
            res.setStartOperationsUserId(chargingRecord.getStartOperationsUserId());
@@ -686,24 +876,7 @@
            res.setRepairReason(vehicleRepair.getRepairReason());
            String startRepairPicture = vehicleRepair.getStartRepairPicture();
            if (StringUtils.isNotBlank(startRepairPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(startRepairPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setStartRepairPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setStartRepairPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleRepair.getStartRepairPicture()));
            res.setRepairMoney(vehicleRepair.getRepairMoney());
            res.setRepairMoneyDetail(vehicleRepair.getRepairMoneyDetail());
            res.setSendUserId(vehicleRepair.getSendUserId());
@@ -810,7 +983,8 @@
        res.setPlateNumber(vehicle.getPlateNumber());
        res.setVehicleColor(vehicle.getVehicleColor());
        res.setVehicleCode(vehicle.getVehicleCode());
        res.setVehicleVin(vehicle.getVehicleVin());
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        res.setVehicleVin(vin);
        res.setVehicleLocation(task.getVehicleLocation());
        res.setCreateBy(task.getCreateBy());
        res.setCreateTime(task.getCreateTime());
@@ -828,111 +1002,16 @@
            res.setCollisionPrevention(vehicleDetection.getCollisionPrevention());
            res.setSensorSelfCheck(vehicleDetection.getSensorSelfCheck());
            res.setSensorCleaning(vehicleDetection.getSensorCleaning());
            String SensorCleaningPicture = vehicleDetection.getSensorCleaningPicture();
            if (StringUtils.isNotBlank(SensorCleaningPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(SensorCleaningPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject =(JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setSensorCleaningPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setSensorCleaningPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getSensorCleaningPicture()));
            res.setInteractiveScreen(vehicleDetection.getInteractiveScreen());
            String InteractiveScreenPicture =vehicleDetection.getInteractiveScreenPicture();
            if (StringUtils.isNotBlank(InteractiveScreenPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(InteractiveScreenPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setInteractiveScreenPicture(JSONArray.toJSONString(jsonArray));
            }
            String InteractiveScreenPicture = vehicleDetection.getInteractiveScreenPicture();
            res.setInteractiveScreenPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getInteractiveScreenPicture()));
            res.setHelpButton(vehicleDetection.getHelpButton());
            String HelpButtonPicture =vehicleDetection.getHelpButtonPicture();
            if (StringUtils.isNotBlank(HelpButtonPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(HelpButtonPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject =(JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setHelpButtonPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setHelpButtonPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getHelpButtonPicture()));
            res.setPaymentFunction(vehicleDetection.getPaymentFunction());
            String PaymentFunctionPicture =vehicleDetection.getPaymentFunctionPicture();
            if (StringUtils.isNotBlank(PaymentFunctionPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(PaymentFunctionPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setPaymentFunctionPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setPaymentFunctionPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getPaymentFunctionPicture()));
            res.setSystemCheck(vehicleDetection.getSystemCheck());
            String SystemCheckPicture =vehicleDetection.getSystemCheckPicture();
            if (StringUtils.isNotBlank(SystemCheckPicture)) {
                JSONArray jsonArray = JSONArray.parseArray(SystemCheckPicture);
                for (Object o : jsonArray) {
                    JSONObject jsonObject =(JSONObject)o;
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setSystemCheckPicture(JSONArray.toJSONString(jsonArray));
            }
            res.setSystemCheckPicture(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getSystemCheckPicture()));
            res.setCheckTime(vehicleDetection.getCheckTime());
            res.setOperationsUserId(vehicleDetection.getOperationsUserId());
            if (vehicleDetection.getOperationsUserId() != null) {
@@ -1021,21 +1100,8 @@
            res.setHandleTime(vehicleDetection.getCreateTime());
            res.setTaskImage(vehicleDetection.getTaskImage());
            String taskImage = res.getTaskImage();
            if (StringUtils.isNotBlank(taskImage)) {
                JSONArray jsonArray = JSONArray.parseArray(taskImage);
                for (Object o : jsonArray) {
                    JSONObject jsonObject = (JSONObject)o;
                    // 1
                    String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    jsonObject.put("url", downloadUrl);
                }
                res.setTaskImage(JSONArray.toJSONString(jsonArray));
            }
            res.setTaskImage(aliOssDownloadUtil.getPictureJsonArray(vehicleDetection.getTaskImage()));
            res.setOperationUserId(vehicleDetection.getOperationsUserId());
            if (vehicleDetection.getOperationsUserId() != null) {
                XpOperationsUser operationsUser = xpOperationUserClient.queryById(vehicleDetection.getOperationsUserId()).getData();
@@ -1078,14 +1144,14 @@
                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())) {
                    if (vehicle != null && 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())
            String taskCode = StringUtils.isNotBlank(task.getTaskCode())
                    ? task.getTaskCode()
                    : String.valueOf(task.getId());
@@ -1119,7 +1185,7 @@
                            .account(operationsUser.getPhoneNumber())
                            .title("任务状态变更")
                            .body(bodyJson)
                            .pushType(AliPushTypeConstant.NOTICE)
                            .pushType(AliPushTypeConstant.MESSAGE)
                            .build());
                }
            } catch (Exception e) {
@@ -1246,29 +1312,9 @@
        PageInfo<XpTaskRecordVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        for (XpTaskRecordVO record : records) {
            String taskImage =record.getTaskImage();
            if (StringUtils.isNotBlank(taskImage)) {
                JSONArray jsonArray = JSONArray.parseArray(taskImage);
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject jsonObject = jsonArray.getJSONObject(i);
                    if (Objects.isNull(jsonObject)) {
                        continue;
                    }
                    String downloadUrl = null;
                    try {
                        // 1
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                record.setTaskImage(JSONArray.toJSONString(jsonArray));
            }
            String taskImage = record.getTaskImage();
            record.setTaskImage(aliOssDownloadUtil.getPictureJsonArray(record.getTaskCode()));
        }
        pageInfo.setRecords(records);
@@ -1299,10 +1345,10 @@
        PageInfo<TaskManagementPageListVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TaskManagementPageListVO> list = this.baseMapper.pageListManagement(pageInfo, query);
        for (TaskManagementPageListVO taskManagementPageListVO : list) {
            if (org.springframework.util.StringUtils.hasLength(taskManagementPageListVO.getHandleUserId())){
            if (org.springframework.util.StringUtils.hasLength(taskManagementPageListVO.getHandleUserId())) {
                String[] split = taskManagementPageListVO.getHandleUserId().split(",");
                List<XpOperationsUser> users = operationUserClient.listByIds(Arrays.stream(split).map(Long::parseLong).collect(Collectors.toList())).getData();
                if (users!=null){
                if (users != null) {
                    StringBuilder handleUserName = new StringBuilder();
                    for (XpOperationsUser user : users) {
                        handleUserName.append(user.getNickName()).append(";");
@@ -1310,60 +1356,7 @@
                    taskManagementPageListVO.setHandleUserName(handleUserName.toString());
                }
            }
            if (taskManagementPageListVO.getTaskCategory() == 1) {
                switch (taskManagementPageListVO.getTaskTypeId()) {
                    case "1":
                        taskManagementPageListVO.setTaskTypeName("车辆检查");
                        break;
                    case "2":
                        taskManagementPageListVO.setTaskTypeName("车辆充电");
                        break;
                    case "3":
                        taskManagementPageListVO.setTaskTypeName("车辆整备");
                        break;
                    case "4":
                        taskManagementPageListVO.setTaskTypeName("整备洗车");
                        break;
                    case "5":
                        taskManagementPageListVO.setTaskTypeName("车辆维修");
                        break;
                    case "6":
                        taskManagementPageListVO.setTaskTypeName("车辆保养");
                        break;
                    case "7":
                        taskManagementPageListVO.setTaskTypeName("车辆保险到期");
                        break;
                    case "8":
                        taskManagementPageListVO.setTaskTypeName("车辆年检到期");
                        break;
                    case "9":
                        taskManagementPageListVO.setTaskTypeName("车辆运营资质到期");
                        break;
                    default:
                        taskManagementPageListVO.setTaskTypeName("");
                        break;
                }
            } else {
                StringBuilder taskTypeName = new StringBuilder();
                String[] typeIds = taskManagementPageListVO.getTaskTypeId().split(",");
                List<XpErrorType> xpErrorTypes = errorTypeMapper.selectList(new LambdaQueryWrapper<XpErrorType>()
                        .in(XpErrorType::getId, Arrays.asList(typeIds)).orderByAsc(XpErrorType::getParentId));
                List<XpErrorType> parents = new ArrayList<>();
                for (XpErrorType xpErrorType : xpErrorTypes) {
                    if (xpErrorType.getParentId() == 0) {
                        parents.add(xpErrorType);
                    }
                }
                for (XpErrorType parent : parents) {
                    List<XpErrorType> children = xpErrorTypes.stream().filter(e -> e.getParentId().equals(parent.getId())).collect(Collectors.toList());
                    taskTypeName.append(parent.getErrorName()).append(">");
                    for (XpErrorType child : children) {
                        taskTypeName.append(child.getErrorName()).append(";");
                    }
                }
                taskManagementPageListVO.setTaskTypeName(taskTypeName.substring(0, taskTypeName.length() - 1));
            }
            taskManagementPageListVO.setTaskTypeName(resolveManagementTaskTypeName(taskManagementPageListVO));
        }
        pageInfo.setRecords(list);
        return pageInfo;
@@ -1373,10 +1366,10 @@
    public List<TaskManagementPageListVO> exportList(TaskManagementPageListQuery query) {
        List<TaskManagementPageListVO> list = this.baseMapper.exportList(query);
        for (TaskManagementPageListVO taskManagementPageListVO : list) {
            if (org.springframework.util.StringUtils.hasLength(taskManagementPageListVO.getHandleUserId())){
            if (org.springframework.util.StringUtils.hasLength(taskManagementPageListVO.getHandleUserId())) {
                String[] split = taskManagementPageListVO.getHandleUserId().split(",");
                List<XpOperationsUser> users = operationUserClient.listByIds(Arrays.stream(split).map(Long::parseLong).collect(Collectors.toList())).getData();
                if (users!=null){
                if (users != null) {
                    StringBuilder handleUserName = new StringBuilder();
                    for (XpOperationsUser user : users) {
                        handleUserName.append(user.getNickName()).append(";");
@@ -1384,68 +1377,329 @@
                    taskManagementPageListVO.setHandleUserName(handleUserName.toString());
                }
            }
            if (taskManagementPageListVO.getTaskCategory() == 1) {
                switch (taskManagementPageListVO.getTaskTypeId()) {
                    case "1":
                        taskManagementPageListVO.setTaskTypeName("车辆检查");
                        break;
                    case "2":
                        taskManagementPageListVO.setTaskTypeName("车辆充电");
                        break;
                    case "3":
                        taskManagementPageListVO.setTaskTypeName("车辆整备");
                        break;
                    case "4":
                        taskManagementPageListVO.setTaskTypeName("整备洗车");
                        break;
                    case "5":
                        taskManagementPageListVO.setTaskTypeName("车辆维修");
                        break;
                    case "6":
                        taskManagementPageListVO.setTaskTypeName("车辆保养");
                        break;
                    case "7":
                        taskManagementPageListVO.setTaskTypeName("车辆保险到期");
                        break;
                    case "8":
                        taskManagementPageListVO.setTaskTypeName("车辆年检到期");
                        break;
                    case "9":
                        taskManagementPageListVO.setTaskTypeName("车辆运营资质到期");
                        break;
                    default:
                        taskManagementPageListVO.setTaskTypeName("");
                        break;
                }
            } else {
                StringBuilder taskTypeName = new StringBuilder();
                String[] typeIds = taskManagementPageListVO.getTaskTypeId().split(",");
                List<XpErrorType> xpErrorTypes = errorTypeMapper.selectList(new LambdaQueryWrapper<XpErrorType>()
                        .in(XpErrorType::getId, Arrays.asList(typeIds)).orderByAsc(XpErrorType::getParentId));
                List<XpErrorType> parents = new ArrayList<>();
                for (XpErrorType xpErrorType : xpErrorTypes) {
                    if (xpErrorType.getParentId() == 0) {
                        parents.add(xpErrorType);
                    }
                }
                for (XpErrorType parent : parents) {
                    List<XpErrorType> children = xpErrorTypes.stream().filter(e -> e.getParentId().equals(parent.getId())).collect(Collectors.toList());
                    taskTypeName.append(parent.getErrorName()).append(">");
                    for (XpErrorType child : children) {
                        taskTypeName.append(child.getErrorName()).append(";");
                    }
                }
                taskManagementPageListVO.setTaskTypeName(taskTypeName.substring(0, taskTypeName.length() - 1));
            }
            taskManagementPageListVO.setTaskTypeName(resolveManagementTaskTypeName(taskManagementPageListVO));
        }
        return list;
    }
    private String resolveManagementTaskTypeName(TaskManagementPageListVO taskManagementPageListVO) {
        if (Integer.valueOf(1).equals(taskManagementPageListVO.getTaskCategory())) {
            return resolveRoutineTaskTypeName(taskManagementPageListVO.getTaskTypeId());
        }
        if (Integer.valueOf(2).equals(taskManagementPageListVO.getTaskCategory())) {
            return resolveAccidentTaskTypeName(taskManagementPageListVO.getTaskTypeId());
        }
        return "";
    }
    private String resolveRoutineTaskTypeName(String taskTypeId) {
        if (StringUtils.isBlank(taskTypeId)) {
            return "";
        }
        switch (taskTypeId) {
            case "1":
                return "车辆检查";
            case "2":
                return "车辆充电";
            case "3":
                return "车辆整备";
            case "4":
                return "整备洗车";
            case "5":
                return "车辆维修";
            case "6":
                return "车辆保养";
            case "7":
                return "车辆保险到期";
            case "8":
                return "车辆年检到期";
            case "9":
                return "车辆运营资质到期";
            default:
                return "";
        }
    }
    private String resolveAccidentTaskTypeName(String taskTypeId) {
        List<Long> orderedTypeIds = parseAccidentTaskTypeIds(taskTypeId);
        if (orderedTypeIds.isEmpty()) {
            return "";
        }
        Map<Long, XpErrorType> errorTypeMap = getAccidentErrorTypeMap(orderedTypeIds);
        if (errorTypeMap.isEmpty()) {
            return "";
        }
        LinkedHashSet<Long> orderedParentIds = new LinkedHashSet<>();
        LinkedHashMap<Long, List<XpErrorType>> childrenByParent = new LinkedHashMap<>();
        for (Long typeId : orderedTypeIds) {
            XpErrorType errorType = errorTypeMap.get(typeId);
            if (errorType == null || StringUtils.isBlank(errorType.getErrorName())) {
                continue;
            }
            if (isTopLevelErrorType(errorType)) {
                orderedParentIds.add(errorType.getId());
                continue;
            }
            Long parentId = errorType.getParentId();
            if (parentId == null) {
                continue;
            }
            orderedParentIds.add(parentId);
            childrenByParent.computeIfAbsent(parentId, key -> new ArrayList<>()).add(errorType);
        }
        List<String> displayNames = new ArrayList<>();
        for (Long parentId : orderedParentIds) {
            XpErrorType parent = errorTypeMap.get(parentId);
            List<XpErrorType> children = childrenByParent.get(parentId);
            if (CollectionUtils.isEmpty(children)) {
                if (parent != null && StringUtils.isNotBlank(parent.getErrorName())) {
                    displayNames.add(parent.getErrorName());
                }
                continue;
            }
            String parentName = parent == null ? null : parent.getErrorName();
            for (XpErrorType child : children) {
                if (StringUtils.isBlank(parentName)) {
                    displayNames.add(child.getErrorName());
                } else {
                    displayNames.add(parentName + ">" + child.getErrorName());
                }
            }
        }
        return String.join(";", displayNames);
    }
    private Map<Long, XpErrorType> getAccidentErrorTypeMap(List<Long> orderedTypeIds) {
        Map<Long, XpErrorType> errorTypeMap = getErrorTypeMap(orderedTypeIds);
        if (errorTypeMap.isEmpty()) {
            return errorTypeMap;
        }
        Set<Long> missingParentIds = new LinkedHashSet<>();
        for (Long typeId : orderedTypeIds) {
            XpErrorType errorType = errorTypeMap.get(typeId);
            if (errorType == null || isTopLevelErrorType(errorType)) {
                continue;
            }
            if (errorType.getParentId() != null && !errorTypeMap.containsKey(errorType.getParentId())) {
                missingParentIds.add(errorType.getParentId());
            }
        }
        if (!missingParentIds.isEmpty()) {
            errorTypeMap.putAll(getErrorTypeMap(missingParentIds));
        }
        return errorTypeMap;
    }
    private boolean isTopLevelErrorType(XpErrorType errorType) {
        return errorType.getParentId() == null || errorType.getParentId() == 0L;
    }
    private String normalizeAccidentTaskTypeId(String taskTypeId) {
        List<Long> orderedTypeIds = parseAccidentTaskTypeIds(taskTypeId);
        if (orderedTypeIds.isEmpty()) {
            return taskTypeId;
        }
        LinkedHashSet<Long> normalizedTypeIds = new LinkedHashSet<>(orderedTypeIds);
        Map<Long, XpErrorType> errorTypeMap = getErrorTypeMap(orderedTypeIds);
        for (Long typeId : orderedTypeIds) {
            XpErrorType errorType = errorTypeMap.get(typeId);
            if (errorType == null || errorType.getParentId() == null || errorType.getParentId() == 0L) {
                continue;
            }
            normalizedTypeIds.add(errorType.getParentId());
        }
        return normalizedTypeIds.stream().map(String::valueOf).collect(Collectors.joining(","));
    }
    private void validateRemoteDrivingAccidentDto(SaveRemoteDrivingAccidentDto dto) {
        if (dto == null) {
            throw new ServiceException("请求参数不能为空");
        }
        if (StringUtils.isBlank(dto.getVin())) {
            throw new ServiceException("vin不能为空");
        }
        if (StringUtils.isBlank(dto.getTaskTypeId())) {
            throw new ServiceException("事故类型不能为空");
        }
        if (StringUtils.isBlank(dto.getTaskUrgencyLevel())) {
            throw new ServiceException("事故紧急程度不能为空");
        }
        if (StringUtils.isBlank(dto.getTaskDescription())) {
            throw new ServiceException("事故描述不能为空");
        }
        if (dto.getTaskDescription().trim().length() > 600) {
            throw new ServiceException("事故描述最多600字");
        }
        if (StringUtils.isBlank(dto.getTaskTime())) {
            throw new ServiceException("工单上报时间不能为空");
        }
        if (StringUtils.isBlank(dto.getVehicleLocation())) {
            throw new ServiceException("车辆位置不能为空");
        }
        if (dto.getVehicleLon() == null) {
            throw new ServiceException("车辆经度不能为空");
        }
        if (dto.getVehicleLat() == null) {
            throw new ServiceException("车辆纬度不能为空");
        }
        if (StringUtils.isBlank(dto.getSource())) {
            throw new ServiceException("工单来源不能为空");
        }
    }
    private Integer mapRemoteDrivingUrgencyLevel(String taskUrgencyLevel) {
        String normalized = StringUtils.trimToEmpty(taskUrgencyLevel);
        if (!StringUtils.isNumeric(normalized)) {
            throw new ServiceException("事故紧急程度不支持: " + taskUrgencyLevel);
        }
        Integer urgencyLevel = Integer.valueOf(normalized);
        if (urgencyLevel < 0 || urgencyLevel > 3) {
            throw new ServiceException("事故紧急程度不支持: " + taskUrgencyLevel);
        }
        return urgencyLevel;
    }
    private Integer parseTaskSource(String source) {
        String normalized = StringUtils.trimToEmpty(source);
        if (!StringUtils.isNumeric(normalized)) {
            throw new ServiceException("工单来源不支持: " + source);
        }
        Integer taskSource = Integer.valueOf(normalized);
        if (taskSource < 1 || taskSource > 3) {
            throw new ServiceException("工单来源不支持: " + source);
        }
        return taskSource;
    }
    private LocalDateTime parseRemoteDrivingTaskTime(String taskTime) {
        String normalized = StringUtils.trimToEmpty(taskTime);
        List<DateTimeFormatter> formatters = Arrays.asList(
                DateTimeFormatter.ISO_LOCAL_DATE_TIME,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        );
        for (DateTimeFormatter formatter : formatters) {
            try {
                return LocalDateTime.parse(normalized, formatter);
            } catch (Exception ignored) {
            }
        }
        throw new ServiceException("工单上报时间格式不正确: " + taskTime);
    }
    private String normalizeTaskImage(String taskImage) {
        if (StringUtils.isBlank(taskImage)) {
            return "[]";
        }
        String trimmed = taskImage.trim();
        if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
            return trimmed;
        }
        return "[]";
    }
    private List<Long> parseAccidentTaskTypeIds(String taskTypeId) {
        if (StringUtils.isBlank(taskTypeId)) {
            return Collections.emptyList();
        }
        LinkedHashSet<Long> typeIds = new LinkedHashSet<>();
        for (String typeId : taskTypeId.split(",")) {
            if (StringUtils.isBlank(typeId)) {
                continue;
            }
            try {
                typeIds.add(Long.parseLong(typeId.trim()));
            } catch (NumberFormatException ignored) {
            }
        }
        return new ArrayList<>(typeIds);
    }
    private Map<Long, XpErrorType> getErrorTypeMap(Collection<Long> typeIds) {
        if (CollectionUtils.isEmpty(typeIds)) {
            return Collections.emptyMap();
        }
        return errorTypeMapper.selectBatchIds(typeIds).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(XpErrorType::getId, errorType -> errorType, (left, right) -> left));
    }
    @Override
    public XpTask queryInfoByVehicleId(Long vehicleId) {
        return this.baseMapper.queryInfoByVehicleId(vehicleId);
    }
    @Autowired
    private XpOperationsUserRepository repository;
    @Override
    @Transactional
    public void reassign(Long taskId, String msg) {
        XpTask task = getById(taskId);
        XpVehicle vehicle = xpVehicleClient.queryById(task.getVehicleId()).getData();
        XpTaskRule taskRule = xpTaskRuleService.lambdaQuery().last("limit 1").one();
        int type = 9;
        if (task.getTaskCategory() != 2) {
            type = Integer.parseInt(task.getTaskTypeId());
        }
        List<XpTaskRuleRole> taskRuleRole = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, type).list();
        if (Objects.isNull(taskRuleRole) || Objects.isNull(taskRule)) {
            throw new ServiceException("未配置车辆事故任务分配规则");
        }
        List<XpTask.Reject> rejects = Optional.ofNullable(task.getExcluded()).orElse(new ArrayList<>());
        if (msg != null && Strings.isNotBlank(task.getHandleUserId())) {
            XpTask.Reject reject = task.reject(msg);
            rejects.add(reject);
            List<Long> id = Collections.singletonList(Long.parseLong(task.getHandleUserId()));
            String roleName = xpOperationUserClient.listByIdsWithRole(id).getData().get(0).getRoleName();
            reject.setRejectUserRoleName(roleName);
        }
        task.setExcluded(rejects);
        task.setReassignmentStatus(1);
        task.setStatus(4);
        Set<String> excludedUserIds = rejects.stream().map(XpTask.Reject::getRejectUserId).collect(Collectors.toSet());
        Long fleetId = xpFleetVehicleClient.getFleetIdByVehicleId(vehicle.getId()).getData();
        List<Long> roleIds = taskRuleRole.stream().map(XpTaskRuleRole::getRoleId).collect(Collectors.toList());
        List<Long> ids = xpFleetOperationsClient.getOperationsIdsByFleetId(fleetId).getData();
        String belongUserIds = xpOperationUserClient.getXpOperationsRoleByRoleIds(roleIds).getData().stream()
                .map(XpOperationsRole::getOperationsUserId)
                .filter(e -> ids == null || ids.isEmpty() || ids.contains(e))
                .filter(id -> !excludedUserIds.contains(id.toString()))
                .map(String::valueOf).collect(Collectors.joining(","));
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
//        VehiclePosition vehiclePosition = new VehiclePosition();
//        vehiclePosition.setPoint(new GeoJsonPoint(103.12615621, 30.264561));
        String handlerUser = chooseNearestAvailableOperationsUser(belongUserIds, vehiclePosition);
        task.setHandleUserId(handlerUser);
        if (handlerUser == null) {
            task.setStatus(4);
            task.setReassignmentStatus(1);
            noticeAdministrator(task);//TODO 需要通知管理员手动处理,考虑再AliPushUtil 中给管理员发送消息
        }
        updateById(task);
        if (StringUtils.isNotBlank(task.getHandleUserId())) {
            sendAccidentNotificationAsync(task, vehicle.getPlateNumber(), taskRule.getProcessingTimeLimit());
        }
    }
    @Override
    public void saveVehicleAccidentError(Long vehicleId) {
@@ -1455,21 +1709,21 @@
        if (Objects.isNull(taskRuleRole) || Objects.isNull(taskRule)) {
            throw new ServiceException("未配置车辆事故任务分配规则");
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        //查找事故处理人,处理角色和车辆所属的车队
        Long fleetId = xpFleetVehicleClient.getFleetIdByVehicleId(vehicle.getId()).getData();
        List<Long> roleIds = taskRuleRole.stream().map(XpTaskRuleRole::getRoleId).collect(Collectors.toList());
        List<XpOperationsRole> data = xpOperationUserClient.getXpOperationsRoleByRoleIds(roleIds).getData();
        List<Long> ids = xpFleetOperationsClient.getOperationsIdsByFleetId(fleetId).getData();
        List<XpOperationsRole> collect = data.stream().filter(x -> ids.contains(x.getOperationsUserId())).collect(Collectors.toList());
        String belongUserIds = collect.stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toSet()).stream().map(String::valueOf).collect(Collectors.joining(","));
        String belongUserIds = xpOperationUserClient.getXpOperationsRoleByRoleIds(roleIds).getData().stream()
                .map(XpOperationsRole::getOperationsUserId)
                .filter(ids::contains)
                .map(String::valueOf).collect(Collectors.joining(","));
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vehicle.getVehicleVin())), VehiclePosition.class);
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        Map<String, String> geocode = gdMapGeocodingUtil.geocode(vehiclePosition.getPoint().getX() + "", vehiclePosition.getPoint().getY() + "");
        String address = geocode.get("address");
        XpTask task = new XpTask();
        String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
        String taskCode = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + UUIDUtil.getNumberRandom(5);
        task.setTaskCode(taskCode);
        task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ACCIDENT);
        task.setTaskTypeId("8,10");
@@ -1480,7 +1734,13 @@
        task.setVehicleLocation(address);
        task.setVehicleLat(new BigDecimal(vehiclePosition.getPoint().getY()));
        task.setVehicleLon(new BigDecimal(vehiclePosition.getPoint().getX()));
        task.setHandleUserId(belongUserIds);
        String handlerUser = chooseNearestAvailableOperationsUser(belongUserIds, vehiclePosition);
        task.setHandleUserId(handlerUser);
        if (handlerUser == null) {
            task.setStatus(4);
            task.setReassignmentStatus(1);
            noticeAdministrator(task);
        }
        task.setAssigneeTime(LocalDateTime.now());
        task.setReportUserId(0L);
        task.setReportTime(LocalDateTime.now());
@@ -1496,25 +1756,161 @@
    }
    @Override
    @Transactional
    public void accept(String taskId) {
        XpTask task = getById(Long.valueOf(taskId));
        XpVehicle vehicle = xpVehicleClient.queryById(task.getVehicleId()).getData();
        updateTaskStatus(task.getId(), TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE, Long.valueOf(task.getHandleUserId()));
        sendAccidentNotificationAsync(getById(task.getId()), vehicle.getPlateNumber(), (int) Duration.between(LocalDateTime.now(), task.getEndTime()).toMinutes());
    }
    @Override
    public PageInfo<XpTaskVO> reassignTaskPageList(TaskPageListQuery query) {
        PageInfo<XpTaskVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<XpTaskVO> list = baseMapper.reassignTaskPageList(pageInfo, query);
        pageInfo.setRecords(list);
        return pageInfo;
    }
    @Override
    public XpTaskVO detail(Long taskId) {
        XpTask task = getById(taskId);
        if (task == null) {
            return null;
        }
        XpTaskVO xpTaskVO = new XpTaskVO();
        BeanUtil.copyProperties(task, xpTaskVO);
        String taskImage = xpTaskVO.getTaskImage();
        if (StringUtils.isNotBlank(taskImage)) {
            try {
                xpTaskVO.setTaskImage(aliOssDownloadUtil.getPictureJsonArray(taskImage));
            } catch (Exception e) {
                log.error("获取任务图片下载地址失败,taskId={}", taskId, e);
            }
        }
        // 2. 如果有车辆信息,可以补充车辆相关详情(如果需要)
        if (task.getVehicleId() != null) {
            R<XpVehicle> vehicleResult = xpVehicleClient.queryById(task.getVehicleId());
            if (vehicleResult != null && vehicleResult.getData() != null) {
                XpVehicle vehicle = vehicleResult.getData();
                xpTaskVO.setPlateNumber(vehicle.getPlateNumber());
//                xpTaskVO.setVehicleColor(vehicle.getVehicleColor());
//                xpTaskVO.setVehicleCode(vehicle.getVehicleCode());
//                xpTaskVO.setVehicleVin(vehicle.getVehicleVin());
            }
        }
        return xpTaskVO;
    }
    @Override
    public List<XpTaskVO> exportList(TaskPageListQuery query) {
        return baseMapper.reassignTaskPageList(query);
    }
    @Value("${robotaxi.sms.app-id:1476153389173952512}")
    private String smsAppId;
    @Value("${robotaxi.sms.app-push-config-code:ROBOTAXI_VERIFY_CODE_SMS}")
    private String ROBOTAXI_VERIFY_CODE_SMS_TPL;
    @Resource
    private SmsClient smsClient;
    private void noticeAdministrator(XpTask task) {
        SmsVo smsVo = new SmsVo();
        smsVo.setAppId(smsAppId);
        smsVo.setParams(Collections.EMPTY_MAP);
        smsVo.setTemplateCode(ROBOTAXI_VERIFY_CODE_SMS_TPL);
        String adminPhone = "139..";
        smsVo.setReceivers(adminPhone);
        smsClient.pushSms(smsVo);
    }
    private String chooseNearestAvailableOperationsUser(String belongUserIds, VehiclePosition vehiclePosition) {
        if (StringUtils.isBlank(belongUserIds)) return null;
        List<Long> ids = Arrays.stream(belongUserIds.split(",")).filter(StringUtils::isNotBlank).map(Long::valueOf).collect(Collectors.toList());
        // 让mongo根据[id in ids]返回距离排序的users
//        List<XpOperationsUserPositionDTO> users = mongoTemplate.find(new Query(Criteria.where("_id").in(ids)), XpOperationsUserPositionDTO.class);
        List<XpOperationsUserPositionDTO> XpOperationsUserPositionDTOs = mongoTemplate.find(
                new Query(Criteria.where("_id").in(ids).and("location").nearSphere(new GeoJsonPoint(vehiclePosition.getPoint().getX(), vehiclePosition.getPoint().getY())).maxDistance(1000 / 6378137.0)),
                XpOperationsUserPositionDTO.class
        );
        boolean skipBusy = false;//TODO 获取系统配置,是否开启跳过忙碌
        List<XpOperationsUserPositionDTO> candidates = XpOperationsUserPositionDTOs.stream()
//                .filter(positionDTO -> skipBusy && positionDTO.getStatus() == 1)
                .sorted(Comparator.comparing(XpOperationsUserPositionDTO::getDistance))
                .collect(Collectors.toList());
        if (candidates.isEmpty()) return null;
        return String.valueOf(candidates.get(0).getId());
    }
    @Override
    public R assigneeTask(Long id, Long handlerUserId, Long assigneeUserId) {
        XpTask task = this.getById(id);
        // 查询规则 任务结束时间从任务分配时开始计算
        XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
        if (taskRule != null) {
            task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
        if (task == null) {
            throw new ServiceException(
                    "任务不存在",
                    I18nKeyConstant.TASK_NOT_EXIST
            );
        }
        boolean isAccidentTask = Integer.valueOf(2).equals(task.getTaskCategory());
        Long vehicleId = task.getVehicleId();
        Integer processingTimeLimit = null;
        String plateNumber = "";
        if (isAccidentTask) {
            // 查询规则 任务结束时间从任务分配时开始计算
            XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
            if (taskRule != null) {
                processingTimeLimit = taskRule.getProcessingTimeLimit();
                task.setEndTime(LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()));
            }
            if (vehicleId != null) {
                XpVehicle vehicle = xpVehicleClient.queryById(vehicleId).getData();
                if (vehicle != null && StringUtils.isNotBlank(vehicle.getPlateNumber())) {
                    plateNumber = vehicle.getPlateNumber();
                }
            }
        } else {
            task.setEndTime(LocalDateTime.now().plusDays(3));
        }
        task.setReassignmentStatus(2);
        task.setStatus(1);
        task.setAssigneeTime(LocalDateTime.now());
        task.setHandleUserId(handlerUserId.toString());
        task.setVehicleId(null);
        task.setAssigneeUserId(assigneeUserId);
        this.updateById(task);
        if (isAccidentTask && processingTimeLimit != null) {
            sendAssignedAccidentNotificationAsync(task, handlerUserId, plateNumber, processingTimeLimit);
        }
        return R.ok();
    }
    @Override
    @Transactional
    public void saveTask(SaveErrorTaskDto dto, Long reportUserId) {
        List<XpTask> saveTask = new ArrayList<>();
        String normalizedTaskTypeId = normalizeAccidentTaskTypeId(dto.getTaskTypeId());
        for (Long vehicleId : dto.getVehicleIds()) {
            XpTask task = new XpTask();
            BeanUtils.copyProperties(dto, task);
@@ -1524,7 +1920,7 @@
            String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            task.setTaskCode(code);
            task.setTaskCategory(2);
            task.setTaskTypeId(dto.getTaskTypeId());
            task.setTaskTypeId(normalizedTaskTypeId);
            task.setTaskUrgencyLevel(dto.getTaskUrgencyLevel());
            task.setTaskDescription(dto.getTaskDescription());
            task.setTaskImage(dto.getTaskImage());
@@ -1536,10 +1932,15 @@
            task.setReportTime(LocalDateTime.now());
            task.setExpiredNoticeSent(0);
            task.setWillTimeoutNoticeSent(0);
            task.setTaskSource(2);
            saveTask.add(task);
        }
        this.saveBatch(saveTask);
        for (XpTask task : saveTask) {
            reassign(task.getId(), null);
        }
    }
    @Override
@@ -1559,7 +1960,8 @@
                    I18nKeyConstant.VEHICLE_NOT_EXIST
            );
        }
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(vehicle.getVehicleVin());
        String vehiclePicture = aliOssDownloadUtil.getPictureJSONObject(vehicle.getVehiclePictures());
        if (task.getTaskCategory() == 1) {
            // 常规任务
@@ -1573,16 +1975,9 @@
                    taskManagementDetectionDetailVO.setPlateNumber(vehicle.getPlateNumber());
                    taskManagementDetectionDetailVO.setVehicleColor(vehicle.getVehicleColor());
                    taskManagementDetectionDetailVO.setVehicleCode(vehicle.getVehicleCode());
                    taskManagementDetectionDetailVO.setVehicleVin(vehicle.getVehicleVin());
                    taskManagementDetectionDetailVO.setVehicleVin(vin);
                    taskManagementDetectionDetailVO.setVehicleLocation(task.getVehicleLocation());
                    if(vehicle.getVehiclePictures()!=null){
                        JSONObject jsonObject = JSONObject.parseObject(vehicle.getVehiclePictures());
                        // 1
                        String downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                        jsonObject.put("url", downloadUrl);
                        taskManagementDetectionDetailVO.setVehiclePictures(jsonObject.toJSONString());
                    }
                    taskManagementDetectionDetailVO.setVehiclePictures(vehiclePicture);
                    taskManagementDetectionDetailVO.setTaskCode(task.getTaskCode());
                    XpVehicleDetection vehicleDetection = vehicleDetectionMapper.selectOne(new LambdaQueryWrapper<XpVehicleDetection>()
@@ -1600,14 +1995,16 @@
                        String SensorCleaningPicture = vehicleDetection.getSensorCleaningPicture();
                        if (StringUtils.isNotBlank(SensorCleaningPicture)) {
                            JSONArray jsonArray = JSONArray.parseArray(SensorCleaningPicture);
                            for (Object o : jsonArray) {
                                JSONObject jsonObject = (JSONObject)o;
                                String downloadUrl = null;
                            for (int i = 0; i < jsonArray.size(); i++) {
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                if (Objects.isNull(jsonObject)) {
                                    continue;
                                }
                                String downloadUrl = jsonObject.getString("url");
                                try {
                                    // 1
                                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                    log.error("获取图片下载地址失败", e);
                                }
                                jsonObject.put("url", downloadUrl);
                            }
@@ -1615,21 +2012,22 @@
                        }
                        taskManagementDetectionDetailVO.setInteractiveScreen(vehicleDetection.getInteractiveScreen());
                        String InteractiveScreenPicture =vehicleDetection.getInteractiveScreenPicture();
                        String InteractiveScreenPicture = vehicleDetection.getInteractiveScreenPicture();
                        if (StringUtils.isNotBlank(InteractiveScreenPicture)) {
                            JSONArray jsonArray = JSONArray.parseArray(InteractiveScreenPicture);
                            for (Object o : jsonArray) {
                                JSONObject jsonObject = (JSONObject)o;
                                String downloadUrl = null;
                            for (int i = 0; i < jsonArray.size(); i++) {
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                if (Objects.isNull(jsonObject)) {
                                    continue;
                                }
                                String downloadUrl = jsonObject.getString("url");
                                try {
                                    // 1
                                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                    log.error("获取图片下载地址失败", e);
                                }
                                jsonObject.put("url", downloadUrl);
                            }
@@ -1639,17 +2037,19 @@
                        taskManagementDetectionDetailVO.setHelpButton(vehicleDetection.getHelpButton());
                        String HelpButtonPicture =vehicleDetection.getHelpButtonPicture();
                        String HelpButtonPicture = vehicleDetection.getHelpButtonPicture();
                        if (StringUtils.isNotBlank(HelpButtonPicture)) {
                            JSONArray jsonArray = JSONArray.parseArray(HelpButtonPicture);
                            for (Object o : jsonArray) {
                                JSONObject jsonObject = (JSONObject)o;
                                String downloadUrl = null;
                            for (int i = 0; i < jsonArray.size(); i++) {
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                if (Objects.isNull(jsonObject)) {
                                    continue;
                                }
                                String downloadUrl = jsonObject.getString("url");
                                try {
                                    // 1
                                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                    log.error("获取图片下载地址失败", e);
                                }
                                jsonObject.put("url", downloadUrl);
                            }
@@ -1659,17 +2059,19 @@
                        taskManagementDetectionDetailVO.setPaymentFunction(vehicleDetection.getPaymentFunction());
                        String PaymentFunctionPicture =vehicleDetection.getPaymentFunctionPicture();
                        String PaymentFunctionPicture = vehicleDetection.getPaymentFunctionPicture();
                        if (StringUtils.isNotBlank(PaymentFunctionPicture)) {
                            JSONArray jsonArray = JSONArray.parseArray(PaymentFunctionPicture);
                            for (Object o : jsonArray) {
                                JSONObject jsonObject = (JSONObject)o;
                                String downloadUrl = null;
                            for (int i = 0; i < jsonArray.size(); i++) {
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                if (Objects.isNull(jsonObject)) {
                                    continue;
                                }
                                String downloadUrl = jsonObject.getString("url");
                                try {
                                    // 1
                                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                    log.error("获取图片下载地址失败", e);
                                }
                                jsonObject.put("url", downloadUrl);
                            }
@@ -1680,17 +2082,20 @@
                        taskManagementDetectionDetailVO.setSystemCheck(vehicleDetection.getSystemCheck());
                        String SystemCheckPicture =vehicleDetection.getSystemCheckPicture();
                        String SystemCheckPicture = vehicleDetection.getSystemCheckPicture();
                        if (StringUtils.isNotBlank(SystemCheckPicture)) {
                            JSONArray jsonArray = JSONArray.parseArray(SystemCheckPicture);
                            for (Object o : jsonArray) {
                                JSONObject jsonObject = (JSONObject)o;
                                String downloadUrl = null;
                            for (int i = 0; i < jsonArray.size(); i++) {
                                JSONObject jsonObject = jsonArray.getJSONObject(i);
                                if (Objects.isNull(jsonObject)) {
                                    continue;
                                }
                                String downloadUrl = jsonObject.getString("url");
                                ;
                                try {
                                    // 1
                                    downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                    log.error("获取图片下载地址失败", e);
                                }
                                jsonObject.put("url", downloadUrl);
                            }
@@ -1717,10 +2122,10 @@
                    taskManagementChargingDetailVO.setPlateNumber(vehicle.getPlateNumber());
                    taskManagementChargingDetailVO.setVehicleColor(vehicle.getVehicleColor());
                    taskManagementChargingDetailVO.setVehicleCode(vehicle.getVehicleCode());
                    taskManagementChargingDetailVO.setVehicleVin(vehicle.getVehicleVin());
                    taskManagementChargingDetailVO.setVehicleVin(vin);
                    taskManagementChargingDetailVO.setVehicleLocation(task.getVehicleLocation());
                    taskManagementChargingDetailVO.setTaskCode(task.getTaskCode());
                    taskManagementChargingDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
                    taskManagementChargingDetailVO.setVehiclePictures(vehiclePicture);
                    XpChargingRecord chargingRecord = chargingRecordMapper.selectOne(new LambdaQueryWrapper<XpChargingRecord>()
                            .eq(XpChargingRecord::getTaskId, id).last("limit 1"));
@@ -1758,10 +2163,10 @@
                    taskManagementPreparationDetailVO.setPlateNumber(vehicle.getPlateNumber());
                    taskManagementPreparationDetailVO.setVehicleColor(vehicle.getVehicleColor());
                    taskManagementPreparationDetailVO.setVehicleCode(vehicle.getVehicleCode());
                    taskManagementPreparationDetailVO.setVehicleVin(vehicle.getVehicleVin());
                    taskManagementPreparationDetailVO.setVehicleVin(vin);
                    taskManagementPreparationDetailVO.setVehicleLocation(task.getVehicleLocation());
                    taskManagementPreparationDetailVO.setTaskCode(task.getTaskCode());
                    taskManagementPreparationDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
                    taskManagementPreparationDetailVO.setVehiclePictures(vehiclePicture);
                    XpVehiclePreparation vehiclePreparation = vehiclePreparationMapper.selectOne(new LambdaQueryWrapper<XpVehiclePreparation>()
                            .eq(XpVehiclePreparation::getTaskId, id).last("limit 1"));
@@ -1799,10 +2204,10 @@
                    taskManagementCleanDetailVO.setPlateNumber(vehicle.getPlateNumber());
                    taskManagementCleanDetailVO.setVehicleColor(vehicle.getVehicleColor());
                    taskManagementCleanDetailVO.setVehicleCode(vehicle.getVehicleCode());
                    taskManagementCleanDetailVO.setVehicleVin(vehicle.getVehicleVin());
                    taskManagementCleanDetailVO.setVehicleVin(vin);
                    taskManagementCleanDetailVO.setVehicleLocation(task.getVehicleLocation());
                    taskManagementCleanDetailVO.setTaskCode(task.getTaskCode());
                    taskManagementCleanDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
                    taskManagementCleanDetailVO.setVehiclePictures(vehiclePicture);
                    XpVehicleClean vehicleClean = vehicleCleanMapper.selectOne(new LambdaQueryWrapper<XpVehicleClean>()
                            .eq(XpVehicleClean::getTaskId, id).last("limit 1"));
@@ -1828,10 +2233,10 @@
                    taskManagementRepairDetailVO.setPlateNumber(vehicle.getPlateNumber());
                    taskManagementRepairDetailVO.setVehicleColor(vehicle.getVehicleColor());
                    taskManagementRepairDetailVO.setVehicleCode(vehicle.getVehicleCode());
                    taskManagementRepairDetailVO.setVehicleVin(vehicle.getVehicleVin());
                    taskManagementRepairDetailVO.setVehicleVin(vin);
                    taskManagementRepairDetailVO.setVehicleLocation(task.getVehicleLocation());
                    taskManagementRepairDetailVO.setTaskCode(task.getTaskCode());
                    taskManagementRepairDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
                    taskManagementRepairDetailVO.setVehiclePictures(vehiclePicture);
                    XpVehicleRepair vehicleRepair = vehicleRepairMapper.selectOne(new LambdaQueryWrapper<XpVehicleRepair>()
                            .eq(XpVehicleRepair::getTaskId, id).last("limit 1"));
@@ -1871,9 +2276,9 @@
                    res.setVehicleColor(vehicle.getVehicleColor());
                    res.setVehicleCode(vehicle.getVehicleCode());
                    res.setVehicleLocation(task.getVehicleLocation());
                    res.setVehicleVin(vehicle.getVehicleVin());
                    res.setVehicleVin(vin);
                    res.setTaskCode(task.getTaskCode());
                    res.setVehiclePictures(vehicle.getVehiclePictures());
                    res.setVehiclePictures(vehiclePicture);
                    XpTaskHandleDetailRoutine xpTaskHandleDetailRoutine = xpTaskHandleDetailRoutineMapper.selectOne(new LambdaQueryWrapper<XpTaskHandleDetailRoutine>()
                            .eq(XpTaskHandleDetailRoutine::getTaskId, id));
                    if (xpTaskHandleDetailRoutine != null) {
@@ -1894,7 +2299,7 @@
                    res.setVehicleColor(vehicle.getVehicleColor());
                    res.setVehicleCode(vehicle.getVehicleCode());
                    res.setVehicleLocation(task.getVehicleLocation());
                    res.setVehicleVin(vehicle.getVehicleVin());
                    res.setVehicleVin(vin);
                    managementDetailVO.setRoutineDetailVO(res);
                    break;
@@ -1908,6 +2313,12 @@
            Map<Long, List<XpErrorType>> errorType = errorTypeMapper.selectList(new LambdaQueryWrapper<XpErrorType>()
                            .in(XpErrorType::getId, Arrays.asList(task.getTaskTypeId().split(","))))
                    .stream().collect(Collectors.groupingBy(XpErrorType::getParentId));
            if (CollUtil.isEmpty(errorType)) {
                throw new ServiceException(
                        "事故类型不存在",
                        I18nKeyConstant.ACCIDENT_TYPE_NOT_EXIST
                );
            }
            List<XpErrorType> xpErrorTypes = errorType.get(0L);
            for (XpErrorType xpErrorType : xpErrorTypes) {
                xpErrorType.setChild(errorType.get(xpErrorType.getId()));
@@ -1917,7 +2328,26 @@
            res.setErrorTypeList(xpErrorTypes);
            res.setStatus(task.getStatus());
            res.setTaskDescription(task.getTaskDescription());
            res.setTaskImage(task.getTaskImage());
            String taskImage = task.getTaskImage();
            if (StringUtils.isNotBlank(taskImage)) {
                JSONArray jsonArray = JSONArray.parseArray(taskImage);
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject jsonObject = jsonArray.getJSONObject(i);
                    if (Objects.isNull(jsonObject)) {
                        continue;
                    }
                    String downloadUrl = jsonObject.getString("url");
                    ;
                    try {
                        downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
                    } catch (UnsupportedEncodingException e) {
                        log.error("获取图片下载地址失败", e);
                    }
                    jsonObject.put("url", downloadUrl);
                }
                res.setTaskImage(JSONArray.toJSONString(jsonArray));
            }
            res.setId(id);
            res.setVehicleId(vehicle.getId());
            res.setTaskUrgencyLevel(task.getTaskUrgencyLevel());
@@ -1925,25 +2355,35 @@
            res.setVehicleColor(vehicle.getVehicleColor());
            res.setVehicleCode(vehicle.getVehicleCode());
            res.setVehicleLocation(task.getVehicleLocation());
            res.setVehicleVin(vehicle.getVehicleVin());
            if (task.getReportUserId().equals(0L)){
            res.setVehicleVin(vin);
            if (task.getReportUserId().equals(0L)) {
                // 系统补偿上传车辆异常任务 为系统创建
                res.setReportUserId(0L);
                res.setReportUserName("系统");
                // todo 临时设置 待客户确认默认头像和电话号码
                res.setReportUserPhone("111111");
                res.setReportUserAvatar("[]");
            }else{
                XpOperationsUser data = xpOperationUserClient.queryById(task.getReportUserId()).getData();
                if (data != null ) {
                    res.setReportUserId(data.getId());
                    res.setReportUserName(data.getNickName());
                    res.setReportUserPhone(data.getPhoneNumber());
                    res.setReportUserAvatar(data.getAvatar());
            } else {
                if (Objects.nonNull(task.getTaskSource()) && task.getTaskSource().equals(2)) {
                    List<SysUser> list = sysUserClient.listByIds(Lists.newArrayList(task.getReportUserId())).getData();
                    if (CollUtil.isNotEmpty(list)) {
                        SysUser sysUser = list.get(0);
                        res.setReportUserId(sysUser.getId());
                        res.setReportUserName(sysUser.getNickName());
                        res.setReportUserPhone(sysUser.getPhoneNumber());
                    }
                } else {
                    XpOperationsUser data = xpOperationUserClient.queryById(task.getReportUserId()).getData();
                    if (data != null) {
                        res.setReportUserId(data.getId());
                        res.setReportUserName(data.getNickName());
                        res.setReportUserPhone(data.getPhoneNumber());
                        res.setReportUserAvatar(data.getAvatar());
                    }
                }
            }
            if (task.getHandleUserId()!=null && task.getHandleUserId().split(",").length == 1) {
            if (task.getHandleUserId() != null && task.getHandleUserId().split(",").length == 1) {
                XpOperationsUser operationsUser = xpOperationUserClient.queryById(Long.valueOf(task.getHandleUserId())).getData();
                if (operationsUser != null) {
                    res.setHandleUserId(operationsUser.getId());
@@ -1971,9 +2411,51 @@
        }
        return R.ok(managementDetailVO);
    }
    /**
     * 任务改派操作
     *
     * @param vo
     */
    @Override
    public void taskReassignment(TaskReassignmentVo vo) {
        XpTask xpTask = this.getById(vo.getTaskId());
        if (null == xpTask) {
            throw new ServiceException("无效的任务", I18nKeyConstant.TASK_NOT_EXIST);
        }
        if (2 == xpTask.getReassignmentStatus()) {
            throw new ServiceException("任务已改派,不能重复操作", I18nKeyConstant.TASK_HAS_BEEN_REASSIGNED);
        }
        xpTask.setReassignmentStatus(2);
        xpTask.setHandleUserId(vo.getNewOperationsUserId() + "");
        xpTask.setStatus(1);
        this.updateById(xpTask);
        //推送websocket
        XpTaskVO xpTaskVO = new XpTaskVO();
        BeanUtil.copyProperties(xpTask, xpTaskVO);
        xpWebSocketMessageClient.pushTask(xpTaskVO);
        boolean isAccidentTask = Integer.valueOf(2).equals(xpTask.getTaskCategory());
        Long vehicleId = xpTask.getVehicleId();
        Integer processingTimeLimit = null;
        String plateNumber = "";
        if (isAccidentTask) {
            // 查询规则 任务结束时间从任务分配时开始计算
            XpTaskRule taskRule = xpTaskRuleMapper.selectOne(null);
            if (taskRule != null) {
                processingTimeLimit = taskRule.getProcessingTimeLimit();
            }
            if (vehicleId != null) {
                XpVehicle vehicle = xpVehicleClient.queryById(vehicleId).getData();
                if (vehicle != null && StringUtils.isNotBlank(vehicle.getPlateNumber())) {
                    plateNumber = vehicle.getPlateNumber();
                }
            }
        }
        if (isAccidentTask && processingTimeLimit != null) {
            sendAssignedAccidentNotificationAsync(xpTask, Long.valueOf(xpTask.getHandleUserId()), plateNumber, processingTimeLimit);
        }
    }
}