“mitao”
2026-03-10 39873465a7e0e280a5c98fcee039681c1da7c9cb
bugfix
1个文件已添加
19个文件已修改
417 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/constant/I18nKeyConstant.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysRoleServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/i18n/messages.properties 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/i18n/messages_en_US.properties 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/i18n/messages_zh_CN.properties 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/PushOrderDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/maintenance/XpCheckLocationClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/maintenance/XpCheckLocationClient.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/utils/StringUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpCheckLocationController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleRepairController.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/exportExcel/XpTaskExportExcel.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleChargingScheduleTask.java 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetPageVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetServiceImpl.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/constant/I18nKeyConstant.java
@@ -18,6 +18,8 @@
    String LOGIN_USERNAME_NOT_VALID = "login.username.not.valid";
    String LOGIN_PASSWORD_NOT_VALID = "login.password.not.valid";
    String LOGIN_BLACK_LIST = "login.black.list";
    String ACCOUNT_NOT_EXISTS = "account.not.exists";
    String LOGIN_ACCOUNT_DISABLED = "login.account.disabled";
    //========== 帐号相关 ==========
    String ACCOUNT_PROFILE_UPDATE_ERROR = "account.profile.update.error";
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java
@@ -61,7 +61,7 @@
        // 查询用户信息
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user)) {
            throw new ServiceException("登录失败,请稍后重试", I18nKeyConstant.LOGIN_FAIL);
            throw new ServiceException("帐号不存在或已被删除", I18nKeyConstant.ACCOUNT_NOT_EXISTS);
        }
        // 角色集合
        Set<String> roles = permissionService.getRolePermission(user);
@@ -105,9 +105,9 @@
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "对不起,您的账号已被删除");
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        if (Integer.valueOf("1").equals(user.getStatus())) {
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "用户已停用,请联系管理员");
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
            throw new ServiceException("您的账号:" + username + " 已禁用", I18nKeyConstant.LOGIN_ACCOUNT_DISABLED);
        }
        passwordService.validate(user, password, request);
        // recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_SUCCESS_STATUS, "登录成功");
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysRoleServiceImpl.java
@@ -209,7 +209,9 @@
        // 构建查询条件
        LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(query.getRoleName())) {
            wrapper.like(SysRole::getRoleName, query.getRoleName());
            // 转义 LIKE 特殊字符:% _ \
            String roleName = StringUtils.escapeLike(query.getRoleName());
            wrapper.like(SysRole::getRoleName, roleName);
        }
        wrapper.orderByDesc(SysRole::getCreateTime);
xp-robotaxi-account-boot/src/main/resources/i18n/messages.properties
@@ -7,6 +7,8 @@
login.username.not.valid=用户名不在指定范围
login.password.not.valid=用户密码不在指定范围
login.black.list=很遗憾,访问IP已被列入系统黑名单
account.not.exists=帐号不存在或已被删除
login.account.disabled=您的账号:{0}已禁用
account.profile.update.error=修改个人信息异常,请联系管理员
account.password.update.error=修改密码异常,请联系管理员
account.phone.duplicate=手机号已开通账号
@@ -24,7 +26,7 @@
validation.group.name.notBlank=组名称不能为空
validation.group.remarks.notBlank=备注不能为空
validation.group.type.notNull=分组类型不能为空
validation.group.menuIds.notEmpty=菜单id集合不能为空
validation.group.menuIds.notEmpty=请选择操作权限
validation.group.id.notNull=权限组ID不能为空
group.name.exists=权限组名称已存在
group.not.exists=权限组不存在
xp-robotaxi-account-boot/src/main/resources/i18n/messages_en_US.properties
@@ -7,6 +7,8 @@
login.username.not.valid=The username is not within the specified range
login.password.not.valid=The user password is not within the specified range
login.black.list=Regrettably, the access IP has been blacklisted by the system
account.not.exists=The account does not exist or has been deleted
login.account.disabled=Your account: {0} has been disabled
account.profile.update.error=If there is an abnormality in modifying personal information, please contact the administrator
account.password.update.error=Password modification is abnormal. Please contact the administrator
account.phone.duplicate=The mobile phone number has been activated for an account
@@ -24,7 +26,7 @@
validation.group.name.notBlank=Group name cannot be empty
validation.group.remarks.notBlank=Remarks cannot be empty
validation.group.type.notNull=Group type cannot be null
validation.group.menuIds.notEmpty=Menu IDs cannot be empty
validation.group.menuIds.notEmpty=Please select the operation permission
validation.group.id.notNull=Group permission ID cannot be null
group.name.exists=The group name already exists
group.not.exists=Group is not exist
xp-robotaxi-account-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -7,6 +7,8 @@
login.username.not.valid=用户名不在指定范围
login.password.not.valid=用户密码不在指定范围
login.black.list=很遗憾,访问IP已被列入系统黑名单
account.not.exists=帐号不存在或已被删除
login.account.disabled=您的账号:{0}已禁用
account.profile.update.error=修改个人信息异常,请联系管理员
account.password.update.error=修改密码异常,请联系管理员
account.phone.duplicate=手机号已开通账号
@@ -24,7 +26,7 @@
validation.group.name.notBlank=组名称不能为空
validation.group.remarks.notBlank=备注不能为空
validation.group.type.notNull=分组类型不能为空
validation.group.menuIds.notEmpty=菜单id集合不能为空
validation.group.menuIds.notEmpty=请选择操作权限
validation.group.id.notNull=权限组ID不能为空
group.name.exists=权限组名称已存在
group.not.exists=权限组不存在
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/PushOrderDto.java
@@ -1,6 +1,6 @@
package com.xiaopeng.api.dto.automaticPilot;
import com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType;
import com.xiaopeng.api.vo.automaticPilot.proto.down.cloudOrderOperation.OrderOperationType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/maintenance/XpCheckLocationClientFallbackFactory.java
@@ -7,6 +7,8 @@
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * 打卡地址Feign客户端降级工厂
 *
@@ -26,6 +28,12 @@
                log.error("远程调用获取检查点位信息异常:{}", cause.getMessage());
                return R.fail("远程调用获取检查点位信息异常");
            }
            @Override
            public R<List<XpCheckLocation>> listByIds(List<Long> ids) {
                log.error("远程调用批量获取检查点位信息异常:{}", cause.getMessage());
                return R.fail("远程调用批量获取检查点位信息异常");
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/maintenance/XpCheckLocationClient.java
@@ -6,7 +6,11 @@
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
 * 打卡地址管理 Feign Client
@@ -21,4 +25,10 @@
     */
    @GetMapping("/inner/check-location/queryById")
    R<XpCheckLocation> queryById(@RequestParam Long id);
    /**
     * 根据ID列表批量查询打卡地址
     */
    @PostMapping("/inner/check-location/listByIds")
    R<List<XpCheckLocation>> listByIds(@RequestBody List<Long> ids);
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/utils/StringUtils.java
@@ -478,4 +478,16 @@
        }
        return sb.toString();
    }
    /**
     * 对字符串进行转义,转义字符为\,被转义的字符包括\、%、_。例如:输入abc%def_ghi\jkl,输出abc\%def\_ghi\\jkl
     * @param value
     * @return
     */
    public static String escapeLike(String value) {
        if (value == null) return null;
        return value.replace("\\", "\\\\")
                .replace("%", "\\%")
                .replace("_", "\\_");
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpCheckLocationController.java
@@ -1,5 +1,6 @@
package com.xiaopeng.maintenance.controller;
import cn.hutool.core.collection.CollUtil;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.maintenance.XpCheckLocation;
@@ -16,6 +17,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
@@ -43,6 +45,15 @@
        return R.ok(xpCheckLocationService.getById(id));
    }
    @PostMapping("/inner/check-location/listByIds")
    @ApiOperation(value = "根据ID列表批量查询打卡地址", tags = {"内部接口"})
    public R<List<XpCheckLocation>> listByIds(@RequestBody List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(xpCheckLocationService.listByIds(ids));
    }
    @ApiOperation(value = "根据ID查询打卡地址", tags = {"运维APP-排班与考勤"})
    @GetMapping("/api/v1/check-location/queryByUserId")
    public R<XpCheckLocation> queryByUserId() {
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java
@@ -116,17 +116,17 @@
            xpCruiseOrder.setLastOrderId(r.getData());
            // 找到收车站点
            R<XpCruiseOrder> returnCruisePoint = xpCruisePlanClient.findReturnCruisePoint(xpCruiseOrder);
            XpCruiseOrder returnCruisePoint = xpCruisePlanClient.findReturnCruisePoint(xpCruiseOrder).getData();
            if(returnCruisePoint!=null){
                xpCruiseOrderClient.addCruiseOrder(returnCruisePoint.getData());
                xpCruiseOrderClient.addCruiseOrder(returnCruisePoint);
                // 下发命令
                PushOrderDto pushOrderDto = new PushOrderDto();
                pushOrderDto.setVin(returnCruisePoint.getData().getVehicleVin());
                pushOrderDto.setOrderId(returnCruisePoint.getData().getId());
                pushOrderDto.setStartLatitude(returnCruisePoint.getData().getStartLat());
                pushOrderDto.setStartLongitude(returnCruisePoint.getData().getStartLon());
                pushOrderDto.setEndLatitude(returnCruisePoint.getData().getEndLat());
                pushOrderDto.setEndLongitude(returnCruisePoint.getData().getEndLon());
                pushOrderDto.setVin(returnCruisePoint.getVehicleVin());
                pushOrderDto.setOrderId(returnCruisePoint.getId());
                pushOrderDto.setStartLatitude(returnCruisePoint.getStartLat());
                pushOrderDto.setStartLongitude(returnCruisePoint.getStartLon());
                pushOrderDto.setEndLatitude(returnCruisePoint.getEndLat());
                pushOrderDto.setEndLongitude(returnCruisePoint.getEndLon());
                pushOrderDto.setOrderType(OrderType.CRUISE_ORDER);
                pushOrderDto.setStage(2);
                orderMissionClient.pushOrder(pushOrderDto);
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleRepairController.java
@@ -9,6 +9,7 @@
import com.xiaopeng.maintenance.dto.XpVehicleRepairStartDTO;
import com.xiaopeng.maintenance.model.query.XpVehicleRepairPageQuery;
import com.xiaopeng.maintenance.model.vo.XpVehicleRepairVO;
import com.xiaopeng.maintenance.schedule.InsuranceInspectionScheduleTask;
import com.xiaopeng.maintenance.service.IXpVehicleRepairService;
import com.xiaopeng.maintenance.vo.RepairStatusVO;
import io.swagger.annotations.Api;
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/exportExcel/XpTaskExportExcel.java
@@ -22,7 +22,7 @@
    @Excel(name = "序号",width = 20)
    private Integer number;
    @Excel(name = "车牌号",width = 20)
    @Excel(name = "车牌",width = 20)
    private String plateNumber;
    @ApiModelProperty(value = "任务类别1常规 2事故")
    @Excel(name = "任务类别",width = 20)
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/schedule/VehicleChargingScheduleTask.java
New file
@@ -0,0 +1,269 @@
package com.xiaopeng.maintenance.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.maintenance.XpTask;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.maintenance.XpTaskRuleRole;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.maintenance.constant.TaskScheduleConstants;
import com.xiaopeng.maintenance.mapper.XpTaskMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import com.xiaopeng.maintenance.mapper.XpTaskRuleRoleMapper;
import com.xiaopeng.maintenance.utils.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 运营车辆电量检测定时任务
 * <p>
 * 根据任务规则配置的充电阈值,从MongoDB读取低电量车辆位置数据,
 * 创建充电类型任务并下发给配置角色对应的运维人员。
 * </p>
 *
 * @author mitao
 * @since 2026-03-10
 */
@Component
public class VehicleChargingScheduleTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleChargingScheduleTask.class);
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "运营车辆电量检测定时任务";
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
    @Autowired
    private XpTaskRuleRoleMapper taskRuleRoleMapper;
    @Autowired
    private XpTaskMapper taskMapper;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private XpOperationUserClient xpOperationUserClient;
    @Autowired
    private MongoTemplate mongoTemplate;
    /**
     * 每分钟执行一次电量检测
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeChargingDetectionTask() {
        log.info("========== 开始执行{}检查 ==========", TASK_NAME);
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
        // 2. 判断充电策略功能是否启用
        if (taskRule.getChargingStrategy() == null || taskRule.getChargingStrategy() != 1) {
            log.info("充电策略未启用,跳过执行");
            return;
        }
        // 3. 获取电量阈值
        BigDecimal thresholdDecimal = taskRule.getChargingStrategyBatteryl();
        if (thresholdDecimal == null) {
            log.warn("充电策略电量阈值未配置,跳过执行");
            return;
        }
        int threshold = thresholdDecimal.intValue();
        if (threshold <= 0) {
            log.warn("充电策略电量阈值不合法:{},跳过执行", thresholdDecimal);
            return;
        }
        // 4. 获取任务接收角色的运维人员
        List<Long> operationUserIds = getOperationUserIdsByRoleType(TaskScheduleConstants.RULE_ROLE_TYPE_CHARGING);
        if (CollectionUtils.isEmpty(operationUserIds)) {
            log.warn("未找到充电策略任务接收角色的运维人员");
            return;
        }
        String belongUserIds = operationUserIds.stream().map(String::valueOf).collect(Collectors.joining(","));
        // 5. 获取所有运营中的车辆,并构建 VIN -> Vehicle 映射
        List<XpVehicle> operatingVehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(operatingVehicles)) {
            log.info("没有运营中的车辆,跳过执行");
            return;
        }
        Map<String, XpVehicle> operatingVehicleByVin = operatingVehicles.stream()
                .filter(v -> StringUtils.hasLength(v.getVehicleVin()))
                .collect(Collectors.toMap(XpVehicle::getVehicleVin, v -> v, (a, b) -> a));
        if (operatingVehicleByVin.isEmpty()) {
            log.info("运营车辆VIN为空,跳过执行");
            return;
        }
        // 6. 从MongoDB查询电量低于阈值的车辆位置数据(只查运营车辆VIN)
        List<String> vins = new ArrayList<>(operatingVehicleByVin.keySet());
        Query query = new Query(Criteria.where("vin").in(vins).and("currentElectricity").lt(threshold));
        List<VehiclePosition> lowBatteryPositions = mongoTemplate.find(query, VehiclePosition.class);
        if (CollectionUtils.isEmpty(lowBatteryPositions)) {
            log.info("未找到低电量车辆(阈值={}),跳过执行", threshold);
            return;
        }
        int createdCount = 0;
        int skippedCount = 0;
        for (VehiclePosition position : lowBatteryPositions) {
            if (position == null || !StringUtils.hasLength(position.getVin())) {
                continue;
            }
            XpVehicle vehicle = operatingVehicleByVin.get(position.getVin());
            if (vehicle == null || vehicle.getId() == null) {
                continue;
            }
            // 避免重复创建:若存在未完成的充电任务则跳过
            if (hasUnfinishedTask(vehicle.getId(), TaskScheduleConstants.TASK_TYPE_CHARGING)) {
                skippedCount++;
                continue;
            }
            XpTask task = buildChargingTask(vehicle, position, belongUserIds, taskRule, threshold);
            taskMapper.insert(task);
            createdCount++;
        }
        log.info("========== {}执行完成,阈值={},命中={},创建任务数={},跳过(已存在未完成任务)={} ==========",
                TASK_NAME, threshold, lowBatteryPositions.size(), createdCount, skippedCount);
    }
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
    /**
     * 获取所有运营中的车辆
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
    /**
     * 根据角色类型获取运维人员ID列表
     */
    private List<Long> getOperationUserIdsByRoleType(Integer roleType) {
        List<XpTaskRuleRole> ruleRoles = taskRuleRoleMapper.selectList(
                new LambdaQueryWrapper<XpTaskRuleRole>()
                        .eq(XpTaskRuleRole::getRuleType, roleType)
        );
        if (CollectionUtils.isEmpty(ruleRoles)) {
            return Collections.emptyList();
        }
        Set<Long> userIds = new HashSet<>();
        for (XpTaskRuleRole ruleRole : ruleRoles) {
            try {
                R<List<XpOperationsRole>> result = xpOperationUserClient.getXpOperationsRoleByRoleId(ruleRole.getRoleId());
                if (result != null && result.getData() != null) {
                    userIds.addAll(
                            result.getData().stream()
                                    .map(XpOperationsRole::getOperationsUserId)
                                    .filter(Objects::nonNull)
                                    .collect(Collectors.toList())
                    );
                }
            } catch (Exception e) {
                log.error("获取角色[{}]对应的运维人员失败", ruleRole.getRoleId(), e);
            }
        }
        return new ArrayList<>(userIds);
    }
    /**
     * 检查是否已存在未完成的同类型任务
     */
    private boolean hasUnfinishedTask(Long vehicleId, String taskTypeId) {
        XpTask existingTask = taskMapper.selectOne(
                new LambdaQueryWrapper<XpTask>()
                        .eq(XpTask::getVehicleId, vehicleId)
                        .eq(XpTask::getTaskTypeId, taskTypeId)
                        .in(XpTask::getStatus,
                                TaskScheduleConstants.TASK_STATUS_PENDING_ARRIVE,
                                TaskScheduleConstants.TASK_STATUS_PENDING)
                        .last("limit 1")
        );
        return existingTask != null;
    }
    private XpTask buildChargingTask(XpVehicle vehicle,
                                    VehiclePosition position,
                                    String belongUserIds,
                                    XpTaskRule taskRule,
                                    int threshold) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String taskCode = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
        Integer currentElectricity = position.getCurrentElectricity();
        String desc = String.format("系统自动下发的车辆充电任务,当前电量%s%%,阈值%d%%",
                currentElectricity == null ? "-" : String.valueOf(currentElectricity),
                threshold);
        XpTask task = new XpTask();
        task.setTaskCode(taskCode);
        task.setTaskCategory(TaskScheduleConstants.TASK_CATEGORY_ROUTINE);
        task.setTaskTypeId(TaskScheduleConstants.TASK_TYPE_CHARGING);
        task.setTaskUrgencyLevel(taskRule.getChargingStrategyLevel() != null
                ? taskRule.getChargingStrategyLevel()
                : TaskScheduleConstants.URGENCY_LEVEL_P2);
        task.setTaskDescription(desc);
        task.setVehicleId(vehicle.getId());
        if (position.getPoint() != null) {
            task.setVehicleLon(BigDecimal.valueOf(position.getPoint().getX()));
            task.setVehicleLat(BigDecimal.valueOf(position.getPoint().getY()));
        }
        // 常规任务默认限时3天
        task.setEndTime(LocalDateTime.now().plusDays(3));
        task.setStatus(TaskScheduleConstants.TASK_STATUS_PENDING);
        task.setHandleUserId(belongUserIds);
        task.setReportUserId(0L);
        task.setReportTime(LocalDateTime.now());
        task.setCreateBy(TaskScheduleConstants.SCHEDULE_CREATOR);
        return task;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -1471,15 +1471,12 @@
                res.setReportUserPhone("111111");
                res.setReportUserAvatar("[]");
            }else{
                List<Long> reportUserIds = new ArrayList<>();
                reportUserIds.add(task.getReportUserId());
                List<SysUser> sysUsers = sysUserClient.listByIds(reportUserIds).getData();
                if (sysUsers != null && !sysUsers.isEmpty()) {
                    SysUser sysUser = sysUsers.get(0);
                    res.setReportUserId(sysUser.getId());
                    res.setReportUserName(sysUser.getNickName());
                    res.setReportUserPhone(sysUser.getPhoneNumber());
                    res.setReportUserAvatar(sysUser.getAvatar());
                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());
                }
            }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -46,7 +46,8 @@
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType;
// import com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType;
import com.xiaopeng.api.vo.automaticPilot.proto.down.cloudOrderOperation.OrderOperationType;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.*;
import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo;
@@ -589,7 +590,7 @@
                PushOrderDto pushOrderDto = new PushOrderDto();
                pushOrderDto.setOrderId(order.getId().toString());
                pushOrderDto.setVin(xpVehicleR.getData().getVehicleVin());
                pushOrderDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.CLOUD_CANCEL_ORDER);
                // pushOrderDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.CLOUD_CANCEL_ORDER);
                orderMissionClient.cancelOrder(pushOrderDto);
            }
        } else {
@@ -675,7 +676,7 @@
                    PushOrderDto cancelDto = new PushOrderDto();
                    cancelDto.setOrderId(order.getId().toString());
                    cancelDto.setVin(oldVehicle.getVehicleVin());
                    cancelDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.CLOUD_CANCEL_ORDER);
                    // cancelDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.CLOUD_CANCEL_ORDER);
                    orderMissionClient.cancelOrder(cancelDto);
                    log.info("已取消原车辆订单指令,订单ID: {}, VIN: {}", orderId, oldVehicle.getVehicleVin());
@@ -1139,7 +1140,7 @@
            PushOrderDto pushOrderDto = new PushOrderDto();
            pushOrderDto.setOrderId(order.getId().toString());
            pushOrderDto.setVin(xpVehicle.getVehicleVin());
            pushOrderDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.VEHICLE_CANCEL_ORDER);
            // pushOrderDto.setOperation_type(com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType.VEHICLE_CANCEL_ORDER);
            orderMissionClient.cancelOrder(pushOrderDto);
        }
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java
@@ -341,7 +341,7 @@
            res.put("name", "测试.png");
            return res.toJSONString();
        } catch (Exception e) {
            log.error("广告物料整体项目结算表导入图片上传oss异常", e);
            log.error("在库车辆导入图片上传oss异常", e);
        }
        return null;
    }
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetPageVO.java
@@ -31,6 +31,9 @@
    @ApiModelProperty(value = "关联运营区域")
    private String electronicName;
    @ApiModelProperty(value = "打卡地名称")
    private String locationName;
    @ApiModelProperty(value = "负责人电话")
    private String phoneNumber;
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetServiceImpl.java
@@ -328,8 +328,8 @@
                .distinct()
                .collect(Collectors.toList());
        List<Long> electronicIds = resultPage.getRecords().stream()
                .map(XpFleet::getElectronicId)
        List<Long> locationIds = resultPage.getRecords().stream()
                .map(XpFleet::getLocationId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
@@ -344,13 +344,19 @@
            }
        }
        // 5. 批量查询围栏信息并转换为Map
        Map<Long, XpElectronicFence> fenceMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(electronicIds)) {
            R<List<XpElectronicFence>> fencesResult = xpElectronicClient.listByIds(electronicIds);
            if (fencesResult.getData() != null) {
                fenceMap = fencesResult.getData().stream()
                        .collect(Collectors.toMap(XpElectronicFence::getId, fence -> fence));
        // 5. 批量查询打卡地信息并转换为Map
        Map<Long, String> locationNameMap = Collections.emptyMap();
        if (!CollectionUtils.isEmpty(locationIds)) {
            R<List<XpCheckLocation>> locationsResult = xpCheckLocationClient.listByIds(locationIds);
            if (locationsResult != null && locationsResult.getData() != null) {
                locationNameMap = locationsResult.getData().stream()
                        .filter(Objects::nonNull)
                        .filter(location -> location.getId() != null && location.getLocationAddress() != null)
                        .collect(Collectors.toMap(
                                XpCheckLocation::getId,
                                XpCheckLocation::getLocationAddress,
                                (a, b) -> a
                        ));
            }
        }
@@ -379,12 +385,9 @@
                }
            }
            // 从Map中获取围栏名称
            if (fleet.getElectronicId() != null) {
                XpElectronicFence fence = fenceMap.get(fleet.getElectronicId());
                if (fence != null) {
                    vo.setElectronicName(fence.getElectronicName());
                }
            // 从Map中获取打卡地名称
            if (fleet.getLocationId() != null) {
                vo.setLocationName(locationNameMap.get(fleet.getLocationId()));
            }
            records.add(vo);