xuhy
2026-04-14 3cc3d0a10593c109a015d01ee05de8da73443778
[feat]事故任务分派逻辑完善、转派工单
2个文件已添加
3个文件已修改
209 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/XpOperationsUserPositionDTO.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/repo/XpOperationsUserRepository.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/PushMessageWebSocketHandler.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/XpOperationsUserPositionDTO.java
New file
@@ -0,0 +1,41 @@
package com.xiaopeng.api.dto.account;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 运维人员查询条件DTO
 *
 * @author mitao
 */
@Data
@ApiModel("运维人员状态")
public class XpOperationsUserPositionDTO {
    @ApiModelProperty(value = "运维人员id")
    private long id;
    @ApiModelProperty(value = "经度")
    private double latitude;
    @ApiModelProperty(value = "纬度")
    private double longitude;
    @ApiModelProperty(value = "纬度")
    private double distance;
    @ApiModelProperty(value = "状态;在线 , 上班中 , 空闲")
    private Long status;
    enum Status {
        online("在线"),working("上班中"),idle("空闲");
        String label;
        Status(String label) {
            this.label = label;
        }
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java
@@ -91,7 +91,7 @@
    @ApiModelProperty(value = "事故即将超时通知是否已发送 0=未发送 1=已发送")
    private Integer willTimeoutNoticeSent;
    @ApiModelProperty(value = "状态 0=待到达 1=待处理 2=已完成 3=已取消")
    @ApiModelProperty(value = "状态 0=待到达 1=待处理 2=已完成 3=已取消 4=改派中")
    private Integer status;
    @ApiModelProperty(value = "任务来源 1-运维APP 2-管理后台")
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/repo/XpOperationsUserRepository.java
New file
@@ -0,0 +1,29 @@
package com.xiaopeng.maintenance.repo;
import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;
/**
 * MongoDB Repository 接口
 * 用于支持地理空间查询和其他操作
 */
public interface XpOperationsUserRepository extends MongoRepository<XpOperationsUserPositionDTO, String> {
    /**
     * 通过经纬度查询范围500米内、一周内、指定时段、状态>1的数据
     * @param point 中心点坐标
     * @param distance 距离范围
     * @return 符合条件的实体列表
     */
    @Query("{ 'location' : { '$nearSphere' : ?0, '$maxDistance' : ?1 ,distanceFiled:  'distance'}, 'id' : { $in : ?2 } }")
    List<XpOperationsUserPositionDTO> findWithDistance(
            Point point,
            Integer distance,
            List<Long> ids
    );
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -7,10 +7,12 @@
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.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.XpAliPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetOperationsClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient;
@@ -23,6 +25,7 @@
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants;
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;
@@ -42,6 +45,7 @@
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.IXpTaskRuleRoleService;
import com.xiaopeng.maintenance.service.IXpTaskRuleService;
import com.xiaopeng.maintenance.service.XpTaskService;
@@ -58,6 +62,8 @@
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.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
@@ -1361,6 +1367,43 @@
        return this.baseMapper.queryInfoByVehicleId(vehicleId);
    }
    @Autowired
    private XpOperationsUserRepository repository;
    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();
        List<XpTaskRuleRole> taskRuleRole = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, 9).list();
        if (Objects.isNull(taskRuleRole) || Objects.isNull(taskRule)) {
            throw new ServiceException("未配置车辆事故任务分配规则");
        }
        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 idsWithoutMe = belongUserIds.replace(task.getHandleUserId(), "");
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vehicle.getVehicleVin())), VehiclePosition.class);
        String handlerUser = chooseNearestAvailableOperationsUser(idsWithoutMe, vehiclePosition);
        task.setHandleUserId(handlerUser);
        if(handlerUser == null) {
            task.setStatus(4);
            noticeAdministrator(task);
        }
        updateById(task);
        // 存储事故处理日志
        XpTaskHandleDetail xpTaskHandleDetail = new XpTaskHandleDetail();
        xpTaskHandleDetail.setTaskId(task.getId());
        xpTaskHandleDetail.setResponsibleDescription(msg);
        xpTaskHandleDetailMapper.insert(xpTaskHandleDetail);
        sendAccidentNotificationAsync(task, vehicle.getPlateNumber(), taskRule.getProcessingTimeLimit());
    }
    @Override
    public void saveVehicleAccidentError(Long vehicleId) {
        XpVehicle vehicle = xpVehicleClient.queryById(vehicleId).getData();
@@ -1369,8 +1412,6 @@
        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();
@@ -1383,7 +1424,7 @@
        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");
@@ -1394,7 +1435,12 @@
        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);
            noticeAdministrator(task);
        }
        task.setAssigneeTime(LocalDateTime.now());
        task.setReportUserId(0L);
        task.setReportTime(LocalDateTime.now());
@@ -1409,6 +1455,47 @@
        sendAccidentNotificationAsync(task, vehicle.getPlateNumber(), taskRule.getProcessingTimeLimit());
    }
    @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) {
        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 = repository.findWithDistance(new Point(vehiclePosition.getPoint().getX(), vehiclePosition.getPoint().getY()), 1000, ids);
        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);
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/PushMessageWebSocketHandler.java
@@ -2,11 +2,17 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO;
import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
@@ -14,6 +20,7 @@
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.UUID;
@@ -31,6 +38,16 @@
    @Autowired
    private WebSocketAuthHandler authHandler;
    private ObjectMapper om;
    @PostConstruct
    void setupObjectMapper() {
        // 允许对象忽略json中不存在的属性
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        om.registerModule(new JavaTimeModule());
        om = new ObjectMapper();
    }
    // ==================== 连接生命周期 ====================
@@ -61,6 +78,12 @@
        log.info("[服务器] 收到消息 -> {}", payload);
        try {
            if (payload.startsWith("pos:")) {
                if (log.isDebugEnabled())
                    log.debug("收到运维人员位置上报消息:{}",payload);
                XpOperationsUserPositionDTO xpOperationsUserPositionDTO = om.readValue(payload.substring(4), XpOperationsUserPositionDTO.class);
                saveToMongo(xpOperationsUserPositionDTO);
            }
            JSONObject messageObj = JSON.parseObject(payload);
            String messageType = messageObj.getString("type");
@@ -77,6 +100,21 @@
        }
    }
    @Autowired
    private MongoTemplate mongoTemplate;
    private void saveToMongo(XpOperationsUserPositionDTO dto) {
        XpOperationsUserPositionDTO vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("id").is(dto.getId())), XpOperationsUserPositionDTO.class);
        if (null == vehiclePosition) {
            mongoTemplate.save(dto);
        }else{
            Update update = new Update()
                    .set("id",dto.getId())
                    .set("latitude", dto.getLatitude())
                    .set("latitude", dto.getLongitude());
            mongoTemplate.updateFirst(new Query(Criteria.where("id").is(dto.getId())), update, XpOperationsUserPositionDTO.class);
        }
    }
    // ==================== 业务处理 ====================
    /**