无关风月
2026-02-13 a6d543bcef73e2a78d8da9b065e241643c166aa8
Merge remote-tracking branch 'origin/master'

# Conflicts:
# xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
10个文件已删除
1 文件已重命名
22个文件已添加
62个文件已修改
3918 ■■■■■ 已修改文件
.gitignore 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpOperationsRoleMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpOperationsRoleMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/XpOperationsUserQueryDTO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/VehiclePositionByVinDto.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpOperationUserClientFallbackFactory.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/VehicleControlClientFallbackFactory.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpVehicleClientFallbackFactory.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserClient.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/UserVehicleClient.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/VehicleControlClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpVehicleClient.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpAgreementAppUser.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/account/XpOperationsUserWithRoleVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/VehiclePosition.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/ActionType.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperation.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationState.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationStatus.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationType.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderType.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/PointType.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/TargetPoint.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/Waypoint.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/up/systemStatusXpu/VehicleDynamicStatus.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/VehiclePositionVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/KafkaTestController.java 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 673 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaProducerService.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/util/XPUUtil.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/TaskTypeEnum.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-redis/src/main/java/com/xiaopeng/common/redis/service/RedisService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpChargingRecordController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpCheckLocationController.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpChargingRecordService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/XpTaskService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpOpenCityController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointManagementController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/OpenCityAddDTO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/OpenCityEditDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/query/OpenCityPageQuery.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/XpOpenCityService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpOpenCityServiceImpl.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/mango/VehiclePositionService.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/pom.xml 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/config/DataUpdateHandlerConfig.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/constant/I18nKeyConstant.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetController.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetVehicleController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/mapper/XpVehicleMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/FleetAddDTO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/FleetEditDTO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetPageQuery.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetUserPageQuery.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetVehiclePageQuery.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetDetailVO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetPageVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetUserVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetVehicleVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/XpVehicleVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpFleetService.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpVehicleService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetServiceImpl.java 479 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/VehicleControlUtil.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages.properties 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages_en_US.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages_zh_CN.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicleConvoy-boot/src/main/resources/mapper/vehicleConvoy/XpVehicleMapper.xml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -15,7 +15,7 @@
target
#*.yaml
#*.yml
*.yaml
*.yml
*.log.DS_Store
**/.DS_Store
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserController.java
@@ -1,12 +1,18 @@
package com.xiaopeng.account.controller;
import com.xiaopeng.account.mapper.XpOperationsRoleMapper;
import com.xiaopeng.account.service.IXpOperationsUserService;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
@@ -23,9 +29,12 @@
    private final IXpOperationsUserService xpOperationsUserService;
    private final XpOperationsRoleMapper xpOperationsRoleMapper;
    @Autowired
    public XpOperationsUserController(IXpOperationsUserService xpOperationsUserService) {
    public XpOperationsUserController(IXpOperationsUserService xpOperationsUserService, XpOperationsRoleMapper xpOperationsRoleMapper) {
        this.xpOperationsUserService = xpOperationsUserService;
        this.xpOperationsRoleMapper = xpOperationsRoleMapper;
    }
    /**
@@ -55,4 +64,51 @@
    public R<List<XpOperationsUser>> listByName(@RequestBody String nickName) {
        return R.ok(xpOperationsUserService.listByName(nickName));
    }
    /**
     * 根据用户ID列表批量查询用户及角色信息(内部接口)
     * @param ids 用户ID列表
     * @return 用户及角色信息列表
     */
    @ApiOperation(value = "批量查询用户及角色信息", tags = {"内部接口"})
    @PostMapping("/inner/xpOperationsUser/listByIdsWithRole")
    public R<List<XpOperationsUserWithRoleVO>> listByIdsWithRole(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return R.ok(new ArrayList<>());
        }
        List<XpOperationsUserWithRoleVO> result = xpOperationsRoleMapper.selectUsersWithRoleByIds(ids);
        return R.ok(result);
    }
    /**
     * 根据条件查询用户及角色信息(内部接口)
     * @param dto 查询条件
     * @return 用户及角色信息列表
     */
    @ApiOperation(value = "根据条件查询用户及角色信息", tags = {"内部接口"})
    @PostMapping("/inner/xpOperationsUser/queryByConditions")
    public R<List<XpOperationsUserWithRoleVO>> queryByConditions(@RequestBody XpOperationsUserQueryDTO dto) {
        List<XpOperationsUserWithRoleVO> result = xpOperationsRoleMapper.selectUsersByConditions(
                dto.getNickName(),
                dto.getPhoneNumber(),
                dto.getRoleId()
        );
        return R.ok(result);
    }
    /**
     * 根据条件查询用户及角色信息
     * @param dto 查询条件
     * @return 用户及角色信息列表
     */
    @ApiOperation(value = "根据条件查询用户不分页列表", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/xpOperationsUser/queryByConditions")
    public R<List<XpOperationsUserWithRoleVO>> getList(@RequestBody XpOperationsUserQueryDTO dto) {
        List<XpOperationsUserWithRoleVO> result = xpOperationsRoleMapper.selectUsersByConditions(
                dto.getNickName(),
                dto.getPhoneNumber(),
                dto.getRoleId()
        );
        return R.ok(result);
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpOperationsRoleMapper.java
@@ -2,6 +2,10 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -13,4 +17,25 @@
 */
public interface XpOperationsRoleMapper extends BaseMapper<XpOperationsRole> {
    /**
     * 根据用户ID列表查询用户及角色信息
     *
     * @param ids 用户ID列表
     * @return 用户及角色信息列表
     */
    List<XpOperationsUserWithRoleVO> selectUsersWithRoleByIds(@Param("ids") List<Long> ids);
    /**
     * 根据条件查询用户及角色信息
     *
     * @param nickName 姓名(模糊查询)
     * @param phoneNumber 手机号(模糊查询)
     * @param roleId 角色ID(精确查询)
     * @return 用户及角色信息列表
     */
    List<XpOperationsUserWithRoleVO> selectUsersByConditions(
            @Param("nickName") String nickName,
            @Param("phoneNumber") String phoneNumber,
            @Param("roleId") Long roleId
    );
}
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpOperationsRoleMapper.xml
@@ -7,4 +7,40 @@
        operations_user_id, role_id
    </sql>
    <!-- 根据用户ID列表查询用户及角色信息 -->
    <select id="selectUsersWithRoleByIds" resultType="com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO">
        SELECT
            u.*,
            r.id AS roleId,
            r.role_name AS roleName
        FROM xp_operations_user u
        LEFT JOIN xp_operations_role ur ON u.id = ur.operations_user_id
        LEFT JOIN sys_role r ON ur.role_id = r.id AND r.is_deleted = 0
        WHERE u.id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    <!-- 根据条件查询用户及角色信息 -->
    <select id="selectUsersByConditions" resultType="com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO">
        SELECT
            u.*,
            r.id AS roleId,
            r.role_name AS roleName
        FROM xp_operations_user u
        LEFT JOIN xp_operations_role ur ON u.id = ur.operations_user_id
        LEFT JOIN sys_role r ON ur.role_id = r.id AND r.is_deleted = 0
        WHERE 1=1
        <if test="nickName != null and nickName != ''">
            AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
        </if>
        <if test="phoneNumber != null and phoneNumber != ''">
            AND u.phone_number LIKE CONCAT('%', #{phoneNumber}, '%')
        </if>
        <if test="roleId != null">
            AND r.id = #{roleId}
        </if>
    </select>
</mapper>
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/account/XpOperationsUserQueryDTO.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.dto.account;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 运维人员查询条件DTO
 *
 * @author mitao
 */
@Data
@ApiModel("运维人员查询条件DTO")
public class XpOperationsUserQueryDTO {
    @ApiModelProperty(value = "姓名(模糊查询)")
    private String nickName;
    @ApiModelProperty(value = "手机号(模糊查询)")
    private String phoneNumber;
    @ApiModelProperty(value = "角色ID(精确查询)")
    private Long roleId;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/VehiclePositionByVinDto.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.api.dto.operation;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@ApiModel(value = "筛选车辆信息Dto")
@AllArgsConstructor
@NoArgsConstructor
public class VehiclePositionByVinDto {
    // 围栏id
    private Long fenceId;
    // 车辆vin
    private List<String> vinList;
    // 数量
    private Integer num;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
New file
@@ -0,0 +1,38 @@
package com.xiaopeng.api.dto.pushMessage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
@ApiModel(value = "推送订单轨迹信息")
public class PushOrderRouteDTO implements Serializable {
    @NotNull(message = "订单id不能为空")
    @ApiModelProperty(value = "订单id")
    private Long orderId;
    @NotNull(message = "经度不能为空")
    @ApiModelProperty(value = "经度")
    private Double longitude;
    @NotNull(message = "纬度不能为空")
    @ApiModelProperty(value = "纬度")
    private Double latitude;
    @NotNull(message = "剩余距离不能为空")
    @ApiModelProperty(value = "剩余距离")
    private Integer distance;
    @NotNull(message = "剩余时间不能为空")
    @ApiModelProperty(value = "剩余时间")
    private Integer duration;
    @NotNull(message = "订单类型不能为空")
    @ApiModelProperty(value = "订单类型 1=实时订单 2=巡游订单")
    private Integer routeType;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpOperationUserClientFallbackFactory.java
@@ -1,7 +1,9 @@
package com.xiaopeng.api.factory.account;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
@@ -34,8 +36,21 @@
            @Override
            public R<List<XpOperationsUser>> listByName(String nickName) {
                log.error("根据昵称查询运维用户信息失败,原因:{}", cause.getMessage());
                return R.fail("根据昵称查询运维用户信息失败:" + cause.getMessage());
            }
            @Override
            public R<List<XpOperationsUserWithRoleVO>> listByIdsWithRole(List<Long> ids) {
                log.error("根据id列表获取运维人员列表失败,原因:{}", cause.getMessage());
                return R.fail("根据id列表获取运维人员列表失败");
            }
            @Override
            public R<List<XpOperationsUserWithRoleVO>> queryByConditions(XpOperationsUserQueryDTO dto) {
                log.error("根据条件查询运维用户信息失败,原因:{}", cause.getMessage());
                return R.fail("根据条件查询运维用户信息失败:" + cause.getMessage());
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java
@@ -64,7 +64,7 @@
            }
            @Override
            public R queryById(Long id) {
            public R<XpPoint> queryById(Long id) {
                log.error("获取点位详情失败:{}", cause.getMessage());
                return R.fail("获取点位详情失败");
            }
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/XpPushMessageClientFallbackFactory.java
@@ -1,5 +1,6 @@
package com.xiaopeng.api.factory.pushMessage;
import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
@@ -41,6 +42,11 @@
            public R<String> sendVehicleLocation(SendMessageDTO dto) {
                return R.fail("推送车辆实时定位失败,"+cause.getMessage());
            }
            @Override
            public R<String> pushOrderRoute(PushOrderRouteDTO dto) {
                return R.fail("通过车辆实时定位推送订单轨迹失败,"+cause.getMessage());
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/VehicleControlClientFallbackFactory.java
@@ -13,7 +13,7 @@
    public VehicleControlClient create(Throwable cause) {
        return new VehicleControlClient() {
            @Override
            public BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, Long uid) {
            public BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, String uid) {
                log.error("查询车辆内温度失败:{}", cause.getMessage());
                return null;
            }
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpVehicleClientFallbackFactory.java
@@ -1,7 +1,10 @@
package com.xiaopeng.api.factory.vehicleConvoy;
import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.operation.VehiclePositionVo;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
@@ -52,6 +55,18 @@
                log.error("根据围栏id查询车辆信息失败,原因: {}", cause.getMessage());
                return R.ok(new ArrayList<>());
            }
            @Override
            public R<List<String>> queryVehiclePositionByVin(VehiclePositionByVinDto dto) {
                log.error("查询车辆信息失败,原因: {}", cause.getMessage());
                return R.fail();
            }
            @Override
            public R<XpVehicle> findByVehicleVin(String vehicleId) {
                log.error("查询车辆信息失败,原因: {}", cause.getMessage());
                return R.fail();
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserClient.java
@@ -1,7 +1,9 @@
package com.xiaopeng.api.feignClient.account;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.factory.account.XpAppUserClientFallbackFactory;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
@@ -43,4 +45,20 @@
     */
    @PostMapping("/inner/xpOperationsUser/listByName")
    R<List<XpOperationsUser>> listByName(@RequestParam("nickName") String nickName);
    /**
     * 根据用户ID列表批量查询用户及角色信息
     * @param ids 用户ID列表
     * @return 用户及角色信息列表
     */
    @PostMapping("/inner/xpOperationsUser/listByIdsWithRole")
    R<List<XpOperationsUserWithRoleVO>> listByIdsWithRole(@RequestBody List<Long> ids);
    /**
     * 根据条件查询用户及角色信息
     * @param dto 查询条件
     * @return 用户及角色信息列表
     */
    @PostMapping("/inner/xpOperationsUser/queryByConditions")
    R<List<XpOperationsUserWithRoleVO>> queryByConditions(@RequestBody XpOperationsUserQueryDTO dto);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java
@@ -15,7 +15,6 @@
@FeignClient(contextId = "XpMapClient", value = ServiceNameConstantsEnum.XP_MAP_SERVICE, fallbackFactory = XpMapClientFallbackFactory.class)
public interface XpMapClient {
    /**
     * 获取距离和时长
     * @param dto
@@ -32,6 +31,7 @@
    @PostMapping("/base/v1/mini-program/map/getOrderDistanceAndDuration")
    R<RouteVo> getOrderDistanceAndDuration(@RequestBody OrderRouteAndFareDto dto);
    /**
     * 判断是否同一经营区域
     * @param dto
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java
@@ -52,7 +52,7 @@
     * 点位详情
     */
    @GetMapping("/api/v1/point/queryPointById")
    public R queryById(@RequestParam Long id);
    public R<XpPoint> queryById(@RequestParam Long id);
    /**
     * 启用/禁用
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/XpPushMessageClient.java
@@ -1,10 +1,12 @@
package com.xiaopeng.api.feignClient.pushMessage;
import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.factory.pushMessage.XpPushMessageClientFallbackFactory;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@@ -45,5 +47,13 @@
     */
    @PostMapping("/inner/websocket/order/sendVehicleLocation")
    R<String> sendVehicleLocation(@RequestBody SendMessageDTO dto);
    /**
     * 通过车辆实时定位推送订单轨迹
     *
     * @param dto 消息实体
     * @return 通过车辆实时定位推送订单轨迹
     */
    @PostMapping("/inner/v1/order/pushOrderRoute")
    R<String> pushOrderRoute(@RequestBody PushOrderRouteDTO dto);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/UserVehicleClient.java
@@ -1,15 +1,20 @@
package com.xiaopeng.api.feignClient.vehicleConvoy;
import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto;
import com.xiaopeng.api.factory.vehicleConvoy.UserVehicleClientFallbackFactory;
import com.xiaopeng.api.vo.operation.VehiclePositionVo;
import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo;
import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo;
import com.xiaopeng.api.vo.vehicleConvoy.GetTaxiPassengersVo;
import com.xiaopeng.common.core.domain.R;
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 java.util.List;
/**
 * 小鹏内部用户中心服务
@@ -43,4 +48,6 @@
     */
    @PostMapping("/logan/vehicleTaxi/update/cancelTaxiPassenger")
    BaseVehicleUserDto cancelTaxiPassenger(@RequestBody CancelTaxiPassengerVo vo);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/VehicleControlClient.java
@@ -24,7 +24,7 @@
     * @return
     */
    @GetMapping("/logan/api/v1/vehicles/state")
    BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, @RequestHeader("uid") Long uid);
    BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, @RequestHeader("uid") String uid);
    /**
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpVehicleClient.java
@@ -1,7 +1,10 @@
package com.xiaopeng.api.feignClient.vehicleConvoy;
import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.factory.vehicleConvoy.XpVehicleClientFallbackFactory;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.operation.VehiclePositionVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
@@ -62,4 +65,20 @@
     */
    @GetMapping("/inner/vehicle/listByFenceId")
    R<List<XpVehicle>> listByFenceId(@RequestParam("fenceId") Long fenceId);
    /**
     * 查询车辆信息 进行筛选
     */
    @PostMapping("/inner/vehicle/queryVehiclePositionByVin")
    R<List<String>> queryVehiclePositionByVin(@RequestBody VehiclePositionByVinDto dto);
    /**
     * 根据车辆vin查询车辆信息
     *
     * @param vehicleVin
     * @return
     */
    @GetMapping("/inner/vehicle/findByVehicleVin")
    R<XpVehicle> findByVehicleVin(String vehicleVin);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpTask.java
@@ -1,18 +1,17 @@
package com.xiaopeng.api.model.maintenance;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
@@ -40,7 +39,7 @@
    @ApiModelProperty(value = "任务类别1常规 2事故")
    private Integer taskCategory;
//1:车辆检查,2:车辆充电,3:车辆整备,4:整备洗车,5:车辆维修,6:车辆保养,7:车辆保险到期,8:车辆年检到期9:车辆运营资质到期
    @ApiModelProperty(value = "任务类型id 任务类别为2时对应数据字典,多个逗号分割,为1的时候 枚举1车辆检查 2车辆充电 3车辆整备 4整备洗车 5车辆维修 6车辆保养 7车辆保险到期 8车辆年检到期 9车辆运营资质到期")
    private String taskTypeId;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpAgreementAppUser.java
@@ -30,7 +30,7 @@
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    @ApiModelProperty(value = "用户id")
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/account/XpOperationsUserWithRoleVO.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.vo.account;
import com.xiaopeng.api.model.account.XpOperationsUser;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 运维人员及角色信息视图对象
 *
 * @author mitao
 */
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("运维人员及角色信息视图对象")
public class XpOperationsUserWithRoleVO extends XpOperationsUser {
    @ApiModelProperty(value = "角色id")
    private Long roleId;
    @ApiModelProperty(value = "角色名称")
    private String roleName;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/VehiclePosition.java
@@ -20,15 +20,11 @@
        @CompoundIndex(name = "location_index", def = "{'point': '2dsphere'}")
}
)
public class VehiclePosition implements Serializable {
    /**
     * 车辆id
     */
    @Id
    private Long vehicle_id;
public class  VehiclePosition implements Serializable {
    /**
     * 车辆VIN
     */
    @Id
    private String vin;
    /**
     * 车辆所属围栏id
@@ -39,19 +35,21 @@
     */
    private GeoJsonPoint point;
    /**
     * 运营状态 1运营中 2维修中 3已收车 4车辆异常
     * 航向,单位:度,北零顺时针。范围:[0-360)。0 => 北, 90 => 东, 180 => 南, 270 => 西
     */
    private Integer status;
    private Double course;
    /**
     * 车辆状态 1接单中 0空闲  巡游单不做处理 还是空闲
     * 海拔 单位米
     */
    private Integer state;
    private Float alt;
    /**
     * 当前速度
     */
    private Float speed;
    /**
     * 当前电量
     */
    private Integer currentElectricity;
    private Float currentElectricity;
    /**
     * 是否有巡游单子 1有 0否
@@ -62,9 +60,4 @@
     * 订单id
     */
    private Long orderId;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/ActionType.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperation.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationState.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationStatus.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderOperationType.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/OrderType.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/PointType.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/TargetPoint.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/down/cloudPushAddress/Waypoint.java
File was deleted
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/proto/up/systemStatusXpu/VehicleDynamicStatus.java
@@ -23,4 +23,20 @@
     * 时间戳(毫秒)
     */
    private Long timestamp;
    /**
     * 油门开度(0-100%)
     */
    private Float throttle;
    /**
     * 刹车压力(0-100%)
     */
    private Float brake;
    /**
     * 手刹状态
     */
    private Boolean parking_brake;
    /**
     * 电池电量(0-100%)
     */
    private Float battery_level;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/VehiclePositionVo.java
File was renamed from xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/VehiclePositionVo.java
@@ -1,12 +1,9 @@
package com.xiaopeng.operation.vo;
package com.xiaopeng.api.vo.operation;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
@@ -25,9 +22,9 @@
     */
    private String vin;
    /**
     * 车辆所属围栏id
     * 车辆所属区域id
     */
    private Long electronic_id;
    private Long area_electronic_id;
    /**
     * 车辆实时位置信息
     */
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/KafkaTestController.java
@@ -1,11 +1,12 @@
package com.xiaopeng.automaticPilot.controller;
import com.xiaopeng.automaticPilot.service.KafkaProducerService;
import com.alibaba.fastjson2.JSON;
import com.xiaopeng.api.vo.automaticPilot.proto.up.MsgContent;
import com.xiaopeng.automaticPilot.service.KafkaConsumerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.web.bind.annotation.*;
/**
@@ -18,138 +19,40 @@
public class KafkaTestController {
    @Autowired
    private KafkaProducerService kafkaProducerService;
    private KafkaConsumerService kafkaConsumerService;
    /**
     * 发送消息(异步)
     * 消费消息 - XPU上报Robotaxi云端的消息topic(高优先级)
     *
     * @param message 消息内容
     */
    @PostMapping("/send")
    @ApiOperation("发送消息(异步)")
    public String sendMessage(
            @ApiParam(value = "主题", required = true) @RequestParam String topic,
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
    @KafkaListener(topics = "topic-prod-robotaxi-xpu-high", groupId = "group-robotaxi-xpu")
    public void topicProdRobotaxiXpuHigh(String message) {
        try {
            kafkaProducerService.sendMessage(topic, message);
            return "消息发送成功";
            log.info("收到XPU上报高优先级主题消息: {}", message);
            kafkaConsumerService.topicProdRobotaxiXpuHigh(message);
        } catch (Exception e) {
            log.error("发送消息失败", e);
            return "消息发送失败: " + e.getMessage();
            log.error("处理XPU上报高优先级主题消息失败: {}", message, e);
            throw e;
        }
    }
    /**
     * 发送消息(带 Key)
     * 消费消息 - XPU上报Robotaxi云端的消息topic(低优先级)
     *
     * @param message 消息内容
     */
    @PostMapping("/sendWithKey")
    @ApiOperation("发送消息(带 Key)")
    public String sendMessageWithKey(
            @ApiParam(value = "主题", required = true) @RequestParam String topic,
            @ApiParam(value = "消息 Key", required = true) @RequestParam String key,
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
    @KafkaListener(topics = "topic-prod-robotaxi-xpu-normal", groupId = "group-robotaxi-xpu")
    public void topicProdRobotaxiXpuNormal(String message) {
        try {
            kafkaProducerService.sendMessage(topic, key, message);
            return "消息发送成功";
            log.info("收到XPU上报低优先级主题消息: {}", message);
            kafkaConsumerService.topicProdRobotaxiXpuNormal(message);
        } catch (Exception e) {
            log.error("发送消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送消息(带回调)
     */
    @PostMapping("/sendWithCallback")
    @ApiOperation("发送消息(带回调)")
    public String sendMessageWithCallback(
            @ApiParam(value = "主题", required = true) @RequestParam String topic,
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessageWithCallback(topic, message);
            return "消息发送成功(带回调)";
        } catch (Exception e) {
            log.error("发送消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送消息(同步)
     */
    @PostMapping("/sendSync")
    @ApiOperation("发送消息(同步)")
    public String sendMessageSync(
            @ApiParam(value = "主题", required = true) @RequestParam String topic,
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessageSync(topic, message);
            return "消息发送成功(同步)";
        } catch (Exception e) {
            log.error("发送消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送自动驾驶测试消息
     */
    @PostMapping("/sendAutoPilot")
    @ApiOperation("发送自动驾驶测试消息")
    public String sendAutoPilotMessage(
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessage("automatic-pilot-topic", message);
            return "自动驾驶消息发送成功";
        } catch (Exception e) {
            log.error("发送自动驾驶消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送车辆状态测试消息
     */
    @PostMapping("/sendVehicleStatus")
    @ApiOperation("发送车辆状态测试消息")
    public String sendVehicleStatusMessage(
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessage("vehicle-status-topic", message);
            return "车辆状态消息发送成功";
        } catch (Exception e) {
            log.error("发送车辆状态消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送导航测试消息
     */
    @PostMapping("/sendNavigation")
    @ApiOperation("发送导航测试消息")
    public String sendNavigationMessage(
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessage("navigation-topic", message);
            return "导航消息发送成功";
        } catch (Exception e) {
            log.error("发送导航消息失败", e);
            return "消息发送失败: " + e.getMessage();
        }
    }
    /**
     * 发送订单测试消息
     */
    @PostMapping("/sendOrder")
    @ApiOperation("发送订单测试消息")
    public String sendOrderMessage(
            @ApiParam(value = "消息内容", required = true) @RequestParam String message) {
        try {
            kafkaProducerService.sendMessage("order-topic", message);
            return "订单消息发送成功";
        } catch (Exception e) {
            log.error("发送订单消息失败", e);
            return "消息发送失败: " + e.getMessage();
            log.error("处理XPU上报低优先级主题消息失败: {}", message, e);
            throw e;
        }
    }
}
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -1,8 +1,54 @@
package com.xiaopeng.automaticPilot.service;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.api.dto.map.OrderRouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.automaticPilot.proto.down.GeoPoint;
import com.xiaopeng.api.vo.automaticPilot.proto.down.ResponseMessage;
import com.xiaopeng.api.vo.automaticPilot.proto.down.StatusCode;
import com.xiaopeng.api.vo.automaticPilot.proto.down.cloudOrderOperation.*;
import com.xiaopeng.api.vo.automaticPilot.proto.up.MessageType;
import com.xiaopeng.api.vo.automaticPilot.proto.up.MsgContent;
import com.xiaopeng.api.vo.automaticPilot.proto.up.RTCmdType;
import com.xiaopeng.api.vo.automaticPilot.proto.up.RtMsgHeader;
import com.xiaopeng.api.vo.automaticPilot.proto.up.navigationDeviationEvent.NavigationDeviationEvent;
import com.xiaopeng.api.vo.automaticPilot.proto.up.navigationDeviationEvent.NavigationDeviationMessage;
import com.xiaopeng.api.vo.automaticPilot.proto.up.navigationDeviationEvent.RtEventType;
import com.xiaopeng.api.vo.automaticPilot.proto.up.orderStateUpdateEvent.OrderStatusInfoEvent;
import com.xiaopeng.api.vo.automaticPilot.proto.up.systemStatusXpu.*;
import com.xiaopeng.api.vo.automaticPilot.proto.up.xpuNavigationRequest.NavigationRequest;
import com.xiaopeng.api.vo.automaticPilot.proto.up.xpuNavigationRequest.OrderPhase;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.automaticPilot.util.XPUUtil;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.OrderStatusEnum;
import com.xiaopeng.common.core.enums.PushMessageTypeEnum;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.utils.uuid.UUID;
import com.xiaopeng.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
 * Kafka 消费者服务
@@ -11,111 +57,590 @@
@Service
public class KafkaConsumerService {
    @Resource
    private XpMapClient xpMapClient;
    @Resource
    private XpOrderClient xpOrderClient;
    @Resource
    private MongoTemplate mongoTemplate;
    @Resource
    private XpVehicleClient xpVehicleClient;
    @Resource
    private XPUUtil xpuUtil;
    @Autowired
    public RedisTemplate redisTemplate;
    @Resource
    private XpPushMessageClient xpPushMessageClient;
    /**
     * 消费消息 - 自动驾驶主题
     * 消费消息 - XPU上报Robotaxi云端的消息topic(高优先级)
     *
     * @param message 消息内容
     */
    @KafkaListener(topics = "automatic-pilot-topic", groupId = "xp-automaticPilot-group")
    public void consumeAutomaticPilotMessage(String message) {
        try {
            log.info("收到自动驾驶主题消息: {}", message);
            // 处理消息逻辑
            processMessage(message);
        } catch (Exception e) {
            log.error("处理消息失败: {}", message, e);
            throw e;
    public void topicProdRobotaxiXpuHigh(String message) {
        MsgContent msgContent = JSON.parseObject(message, MsgContent.class);
        RtMsgHeader header = msgContent.getHeader();
        MessageType type = header.getType();
        //处理请求消息
        if (MessageType.MESSAGE_TYPE_REQUEST == type) {
            //区分业务类型
            RTCmdType cmd = header.getCmd();
            switch (cmd) {
                case SYSTEM_STATUS_XPU:
                    log.info("XPU发布系统运行状态 (XPU→云端):{}", message);
                    systemStatusXpu(msgContent);
                    break;
                case XPU_NAVIGATION_REQUEST:
                    log.info("XPU向云端请求导航 (XPU→云端):{}", message);
                    xpuNavigationRequest(msgContent);
                    break;
                case TRIP_STATUS_UPDATE_EVENT:
                    log.info("行程状态更新(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case PULLOVER_STATUS_EVENT:
                    log.info("靠边停车状态(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case DRIVING_MODE_UPDATE_EVENT:
                    log.info("驾驶模式变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case SYSTEM_STATUS_UPDATE_EVENT:
                    log.info("系统状态变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case TASK_STATE_UPDATE_EVENT:
                    log.info("任务状态变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case ORDER_STATE_UPDATE_EVENT:
                    log.info("订单状态变化(XPU→云端)(XPU→座舱):{}", message);
                    orderStateUpdateEvent(msgContent);
                    break;
                case POINT_ARRIVAL_STATUS_EVENT:
                    log.info("到达目标点状态(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case PICKUP_CONFIRMATION_EVENT:
                    log.info("用户上车确认(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case DROPOFF_INFO_EVENT:
                    log.info("用户下车信息(XPU→云端):{}", message);
                    break;
                case NAVIGATION_DEVIATION_EVENT:
                    log.info("导航偏航(XPU→云端):{}", message);
                    navigationDeviationEvent(msgContent);
                    break;
                case EMERGENCY_STOP_EVENT:
                    log.info("急停(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case NAVIGATION_UPDATE_EVENT:
                    log.info("导航信息变化(XPU→云端):{}", message);
                    break;
                case TASK_QUEUE_UPDATE_EVENT:
                    log.info("XPU待执行任务队列(XPU→云端):{}", message);
                    break;
                case SYSTEM_STATE_ANAPSHOT_SYNC_EVENT:
                    log.info("每次MQTT连接之后触发 (XPU→云端):{}", message);
                    break;
                case ORDER_STATE_UPDATE_TO_CDU_EVENT:
                    log.info("更新订单状态到CDU (XPU→云端):{}", message);
                    break;
                default:
                    log.info("消息不在处理范围内,忽略:{}", message);
            }
        }
    }
    /**
     * 消费消息 - XPU上报Robotaxi云端的消息topic(低优先级)
     */
    public void topicProdRobotaxiXpuNormal(String message) {
        MsgContent msgContent = JSON.parseObject(message, MsgContent.class);
        RtMsgHeader header = msgContent.getHeader();
        MessageType type = header.getType();
        //处理请求消息
        if (MessageType.MESSAGE_TYPE_REQUEST == type) {
            //区分业务类型
            RTCmdType cmd = header.getCmd();
            switch (cmd) {
                case SYSTEM_STATUS_XPU:
                    log.info("XPU发布系统运行状态 (XPU→云端):{}", message);
                    systemStatusXpu(msgContent);
                    break;
                case XPU_NAVIGATION_REQUEST:
                    log.info("XPU向云端请求导航 (XPU→云端):{}", message);
                    xpuNavigationRequest(msgContent);
                    break;
                case TRIP_STATUS_UPDATE_EVENT:
                    log.info("行程状态更新(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case PULLOVER_STATUS_EVENT:
                    log.info("靠边停车状态(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case DRIVING_MODE_UPDATE_EVENT:
                    log.info("驾驶模式变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case SYSTEM_STATUS_UPDATE_EVENT:
                    log.info("系统状态变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case TASK_STATE_UPDATE_EVENT:
                    log.info("任务状态变化(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case ORDER_STATE_UPDATE_EVENT:
                    log.info("订单状态变化(XPU→云端)(XPU→座舱):{}", message);
                    orderStateUpdateEvent(msgContent);
                    break;
                case POINT_ARRIVAL_STATUS_EVENT:
                    log.info("到达目标点状态(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case PICKUP_CONFIRMATION_EVENT:
                    log.info("用户上车确认(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case DROPOFF_INFO_EVENT:
                    log.info("用户下车信息(XPU→云端):{}", message);
                    break;
                case NAVIGATION_DEVIATION_EVENT:
                    log.info("导航偏航(XPU→云端):{}", message);
                    navigationDeviationEvent(msgContent);
                    break;
                case EMERGENCY_STOP_EVENT:
                    log.info("急停(XPU→云端)(XPU→座舱):{}", message);
                    break;
                case NAVIGATION_UPDATE_EVENT:
                    log.info("导航信息变化(XPU→云端):{}", message);
                    break;
                case TASK_QUEUE_UPDATE_EVENT:
                    log.info("XPU待执行任务队列(XPU→云端):{}", message);
                    break;
                case SYSTEM_STATE_ANAPSHOT_SYNC_EVENT:
                    log.info("每次MQTT连接之后触发 (XPU→云端):{}", message);
                    break;
                case ORDER_STATE_UPDATE_TO_CDU_EVENT:
                    log.info("更新订单状态到CDU (XPU→云端):{}", message);
                    break;
                default:
                    log.info("消息不在处理范围内,忽略:{}", message);
            }
        }
    }
    /**
     * XPU发布系统运行状态---车辆实时位置
     *
     * @param msgContent
     */
    public void systemStatusXpu(MsgContent msgContent) {
        //先响应消息
        Long sequence = msgContent.getHeader().getSequence();
        responseMessage(sequence);
        //开始处理消息对应的业务逻辑
        SystemRuntimeStatus systemRuntimeStatus = msgContent.getContent_detail().getObject("systemRuntimeStatus", SystemRuntimeStatus.class);
        //获取实时位姿
        RealtimePose pose = systemRuntimeStatus.getPose();
        Wgs naviPose = pose.getNavi_pose();
        Double latitude = naviPose.getLatitude();
        Double longitude = naviPose.getLongitude();
        Double course = naviPose.getCourse();
        Float alt = naviPose.getAlt();
        //获取车辆静态信息
        VehicleStaticInfo staticInfo = systemRuntimeStatus.getVehicle_status().getStatic_info();
        String vin = staticInfo.getVin();
        redisTemplate.opsForGeo().add("vehiclePosition:" + vin, new GeoJsonPoint(longitude, latitude), vin);
        //保存车辆实时信息到mongodb
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        if (null == vehiclePosition) {
            vehiclePosition = new VehiclePosition();
        }
        vehiclePosition.setVin(vin);
        vehiclePosition.setPoint(new GeoJsonPoint(longitude, latitude));
        vehiclePosition.setCourse(course);
        vehiclePosition.setAlt(alt);
        VehicleStatus vehicleStatus = systemRuntimeStatus.getVehicle_status();
        //车辆动态信息
        VehicleDynamicStatus dynamicStatus = vehicleStatus.getDynamic_status();
        dynamicStatus.setSpeed(dynamicStatus.getSpeed());
        vehiclePosition.setCurrentElectricity(dynamicStatus.getBattery_level());
        //订单信息
        OrderStatusInfo runningOrderStatusInfo = systemRuntimeStatus.getRunning_order_status_info();
        Long orderId = null;
        String order_id = runningOrderStatusInfo.getOrder_id();
        vehiclePosition.setHasOrder(0);
        if (StringUtils.isNotEmpty(order_id)) {
            if (order_id.contains("XU")) {
                vehiclePosition.setHasOrder(1);
                orderId = Long.parseLong(order_id.substring(2));
            } else {
                orderId = Long.parseLong(order_id);
            }
        }
        vehiclePosition.setOrderId(orderId);
        mongoTemplate.save(vehiclePosition);
        //调用推送服务接口
        if (null != orderId) {
            RtTripInfo rtTripInfo = systemRuntimeStatus.getRt_trip_info();
            PushOrderRouteDTO dto = new PushOrderRouteDTO();
            dto.setOrderId(orderId);
            dto.setRouteType(order_id.contains("XU") ? 2 : 1);
            dto.setLongitude(longitude);
            dto.setLatitude(latitude);
            dto.setDuration(rtTripInfo.getRemaining_time());
            dto.setDistance(rtTripInfo.getRemaining_distance().intValue());
            xpPushMessageClient.pushOrderRoute(dto);
        }
    }
    /**
     * 消费消息 - 车辆状态主题
     * XPU向云端请求导航
     *
     * @param message 消息内容
     * @param msgContent
     */
    @KafkaListener(topics = "vehicle-status-topic", groupId = "xp-automaticPilot-group")
    public void consumeVehicleStatusMessage(String message) {
        try {
            log.info("收到车辆状态主题消息: {}", message);
            // 处理车辆状态消息逻辑
            processVehicleStatus(message);
        } catch (Exception e) {
            log.error("处理车辆状态消息失败: {}", message, e);
            throw e;
    public void xpuNavigationRequest(MsgContent msgContent) {
        //先响应消息
        Long sequence = msgContent.getHeader().getSequence();
        responseMessage(sequence);
        //开始处理消息对应的业务逻辑
        NavigationRequest naviRequest = msgContent.getContent_detail().getObject("naviRequest", NavigationRequest.class);
        String orderId = naviRequest.getOrder_id();
        String taskId = naviRequest.getTask_id();
        OrderPhase orderPhase = naviRequest.getOrder_phase();
        log.info("订单ID:{}", orderId);
        log.info("任务ID:{}", taskId);
        switch (orderPhase) {
            case IDLE:
                log.info("订单的任务未开始执行");
                break;
            case ROUTE_TO_PICKUP:
                log.info("驶往上车点的行程");
                buildNaviRequest(orderId, taskId, orderPhase);
                break;
            case ROUTE_TO_DESTINATION:
                log.info("驶往下车点的行程");
                buildNaviRequest(orderId, taskId, orderPhase);
                break;
            case ROUTE_TO_CRUISE:
                log.info("巡游阶段路线");
                buildNaviRequest(orderId, taskId, orderPhase);
                break;
        }
    }
    /**
     * 消费消息 - 导航主题
     * 车辆偏航后重新规划线路
     *
     * @param message 消息内容
     * @param msgContent
     */
    @KafkaListener(topics = "navigation-topic", groupId = "xp-automaticPilot-group")
    public void consumeNavigationMessage(String message) {
        try {
            log.info("收到导航主题消息: {}", message);
            // 处理导航消息逻辑
            processNavigation(message);
        } catch (Exception e) {
            log.error("处理导航消息失败: {}", message, e);
            throw e;
    public void navigationDeviationEvent(MsgContent msgContent) {
        //先响应消息
        Long sequence = msgContent.getHeader().getSequence();
        responseMessage(sequence);
        //开始处理消息对应的业务逻辑
        NavigationDeviationEvent deviationEvent = msgContent.getContent_detail().getObject("deviationEvent", NavigationDeviationEvent.class);
        //偏航事件类型 1:请求导航,2:取消导航
        if (deviationEvent.getEvent_type().equals(RtEventType.RT_EVENT_TYPE_REQUEST)) {
            NavigationDeviationMessage navigationDeviationMessage = deviationEvent.getNavigation_deviation_message();
            String orderId = navigationDeviationMessage.getOrder_id();
            String taskId = navigationDeviationMessage.getTask_id();
            Double longitude = navigationDeviationMessage.getLongitude();
            Double latitude = navigationDeviationMessage.getLatitude();
            //订单类型  1:乘客订单,2=巡游订单
            Integer orderType = 1;
            LonLatDto destination = new LonLatDto();
            LonLatDto origin = new LonLatDto();
            if (orderId.contains("XU")) {
                orderType = 2;
                // todo 下发巡游单的导航数据
                //巡游单
                origin.setLongitude(longitude);
                origin.setLatitude(latitude);
                destination.setLongitude(116.40528);
                destination.setLatitude(39.90498);
            } else {
                //正常单
                R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId));
                if (200 != xpOrderR.getCode()) {
                    throw new RuntimeException("订单不存在");
                }
                XpOrder xpOrder = xpOrderR.getData();
                //驶往上车点的行程
                if (Arrays.asList(3).contains(xpOrder.getStatus())) {
                    //查询司机当前位置到起点的线路
                    origin.setLongitude(longitude);
                    origin.setLatitude(latitude);
                    destination.setLongitude(xpOrder.getStartLon());
                    destination.setLatitude(xpOrder.getStartLat());
                }
                //驶往下车点的行程
                if (Arrays.asList(6).contains(xpOrder.getStatus())) {
                    //查询起点到终点的线路
                    origin.setLongitude(longitude);
                    origin.setLatitude(latitude);
                    destination.setLongitude(xpOrder.getEndLon());
                    destination.setLatitude(xpOrder.getEndLat());
                }
                OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
                dto.setOrderId(Long.parseLong(orderId));
                dto.setOrigin(origin);
                dto.setDestination(destination);
                R<RouteVo> routeVoR = xpMapClient.getOrderDistanceAndDuration(dto);
                if (200 != routeVoR.getCode()) {
                    throw new RuntimeException("获取订单线路和运费失败");
                }
                RouteVo routeVo = routeVoR.getData();
                //构建导航下发请求对象
                cloudOrderOperation(orderType, orderId, taskId, routeVo);
            }
        }
    }
    /**
     * 消费消息 - 订单主题
     * 构建导航请求
     *
     * @param message 消息内容
     * @param orderId
     * @param taskId
     * @param orderPhase
     */
    @KafkaListener(topics = "order-topic", groupId = "xp-automaticPilot-group")
    public void consumeOrderMessage(String message) {
        try {
            log.info("收到订单主题消息: {}", message);
            // 处理订单消息逻辑
            processOrder(message);
        } catch (Exception e) {
            log.error("处理订单消息失败: {}", message, e);
            throw e;
    public void buildNaviRequest(String orderId, String taskId, OrderPhase orderPhase) {
        //订单类型  1:乘客订单,2=巡游订单
        Integer orderType = 1;
        LonLatDto destination = new LonLatDto();
        LonLatDto origin = new LonLatDto();
        if (orderId.contains("XU")) {
            orderType = 2;
            // todo 下发巡游单的导航数据
            //巡游单
            if (orderPhase.equals(OrderPhase.ROUTE_TO_CRUISE)) {
                origin.setLongitude(116.40528);
                origin.setLatitude(39.90498);
                destination.setLongitude(116.40528);
                destination.setLatitude(39.90498);
            }
        } else {
            //正常单
            R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId));
            if (200 != xpOrderR.getCode()) {
                throw new RuntimeException("订单不存在");
            }
            XpOrder xpOrder = xpOrderR.getData();
            //驶往上车点的行程
            if (orderPhase.equals(OrderPhase.ROUTE_TO_PICKUP)) {
                XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
                VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
                if (Objects.isNull(vehiclePosition)) {
                    throw new RuntimeException("车辆位置信息不存在");
                }
                //查询司机当前位置到起点的线路
                origin.setLongitude(vehiclePosition.getPoint().getX());
                origin.setLatitude(vehiclePosition.getPoint().getY());
                destination.setLongitude(xpOrder.getStartLon());
                destination.setLatitude(xpOrder.getStartLat());
            }
            //驶往下车点的行程
            if (orderPhase.equals(OrderPhase.ROUTE_TO_DESTINATION)) {
                //查询起点到终点的线路
                origin.setLongitude(xpOrder.getStartLon());
                origin.setLatitude(xpOrder.getStartLat());
                destination.setLongitude(xpOrder.getEndLon());
                destination.setLatitude(xpOrder.getEndLat());
            }
            OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
            dto.setOrderId(Long.parseLong(orderId));
            dto.setOrigin(origin);
            dto.setDestination(destination);
            R<RouteVo> routeVoR = xpMapClient.getOrderDistanceAndDuration(dto);
            if (200 != routeVoR.getCode()) {
                throw new RuntimeException("获取订单线路和运费失败");
            }
            RouteVo routeVo = routeVoR.getData();
            //构建导航下发请求对象
            cloudOrderOperation(orderType, orderId, taskId, routeVo);
        }
    }
    /**
     * 处理消息逻辑(示例)
     *
     * @param message 消息内容
     */
    private void processMessage(String message) {
        // TODO: 实现具体的消息处理逻辑
        log.info("处理自动驾驶消息: {}", message);
    }
    /**
     * 处理车辆状态消息(示例)
     * 云端订单操作----下发导航数据
     *
     * @param message 消息内容
     * @param routeVo
     */
    private void processVehicleStatus(String message) {
        // TODO: 实现具体的车辆状态处理逻辑
        log.info("处理车辆状态消息: {}", message);
    public void cloudOrderOperation(Integer orderType, String orderId, String taskId, RouteVo routeVo) {
        //构建消息体
        OrderOperation orderOperation = new OrderOperation();
        orderOperation.setOrder_id(orderId);
        orderOperation.setTask_id(taskId);
        orderOperation.setOperation_type(OrderOperationType.CLOUD_UPDATE_WAYPOINTS);
        //构建航点
        List<Waypoint> waypoints = new ArrayList<>();
        List<LonLatDto> polyline = routeVo.getPolyline();
        for (int i = 0; i < polyline.size(); i++) {
            LonLatDto lonLatDto = polyline.get(i);
            Waypoint waypoint = new Waypoint();
            waypoint.setWaypoint_id(UUID.randomUUID().toString());
            if (0 == i) {
                waypoint.setPoint_type(PointType.START_POINT);
                //订单类型不同,点位的动作类型不同
                waypoint.setAction_type(1 == orderType ? ActionType.PICK_UP : ActionType.ACTION_UNSPECIFIED);
            } else if (polyline.size() - 1 == i) {
                waypoint.setPoint_type(PointType.END_POINT);
                waypoint.setAction_type(1 == orderType ? ActionType.DROP_OFF : ActionType.ACTION_UNSPECIFIED);
            } else {
                waypoint.setPoint_type(PointType.WAY_POINT);
                //订单类型不同,点位的动作类型不同
                waypoint.setAction_type(ActionType.ACTION_UNSPECIFIED);
            }
            //构建经纬度
            GeoPoint geoPoint = new GeoPoint();
            geoPoint.setLongitude(lonLatDto.getLongitude());
            geoPoint.setLatitude(lonLatDto.getLatitude());
            waypoint.setPoint(geoPoint);
            waypoint.setSequence(i + 1);
            waypoints.add(waypoint);
        }
        orderOperation.setWaypoints(waypoints);
        //开发发送请求到XPU
        xpuUtil.cloudOrderOperation(orderOperation);
    }
    /**
     * 处理导航消息(示例)
     *
     * @param message 消息内容
     */
    private void processNavigation(String message) {
        // TODO: 实现具体的导航处理逻辑
        log.info("处理导航消息: {}", message);
    }
    /**
     * 处理订单消息(示例)
     *
     * @param message 消息内容
     * 构建响应消息
     */
    private void processOrder(String message) {
        // TODO: 实现具体的订单处理逻辑
        log.info("处理订单消息: {}", message);
    public void responseMessage(Long request_seq) {
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setRequest_seq(request_seq);
        responseMessage.setCode(StatusCode.OK);
        responseMessage.setTimestamp(System.currentTimeMillis());
        xpuUtil.responseMessage(responseMessage);
    }
    /**
     * 订单状态更新事件
     * @param msgContent
     */
    public void orderStateUpdateEvent(MsgContent msgContent) {
        //先响应消息
        Long sequence = msgContent.getHeader().getSequence();
        responseMessage(sequence);
        //开始处理消息对应的业务逻辑
        OrderStatusInfoEvent orderStatusInfoEvent = msgContent.getContent_detail().getObject("orderStatusInfoEvent", OrderStatusInfoEvent.class);
        //只处理请求消息
        if (orderStatusInfoEvent.getEvent_type().equals(RtEventType.RT_EVENT_TYPE_REQUEST)) {
            com.xiaopeng.api.vo.automaticPilot.proto.up.orderStateUpdateEvent.OrderStatusInfo orderStatusInfo = orderStatusInfoEvent.getOrder_status_info();
            String orderId = orderStatusInfo.getOrder_id();
            //普通单
            com.xiaopeng.api.vo.automaticPilot.proto.up.orderStateUpdateEvent.OrderStatus status = orderStatusInfo.getStatus();
            switch (status) {
                case ORDER_STATUS_UNSPECIFIED:
                    //订单状态未知
                    log.info("订单状态未知,不做任何处理:{}", orderId);
                    break;
                case ORDER_RECEIVED:
                    //收到订单
                    buildPushOrderStatus(orderId, OrderStatusEnum.READY_DEPART);
                    break;
                case ORDER_STARTED:
                    //开始执行订单
                    log.info("开始执行订单,不做任何处理:{}", orderId);
                    break;
                case ORDER_EN_ROUTE_TO_PICKUP:
                    //前往上车点
                    buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP);
                    break;
                case ORDER_ARRIVED_AT_PICKUP:
                    //到达上车点
                    log.info("到达上车点,不做任何处理:{}", orderId);
                    break;
                case ORDER_WAITING_FOR_PICKUP:
                    //等待乘客上车
                    buildPushOrderStatus(orderId, OrderStatusEnum.PICK_UP_GUESTS);
                    break;
                case ORDER_PASSENGER_ONBOARD:
                    //乘客已上车
                    buildPushOrderStatus(orderId, OrderStatusEnum.PASSENGER_BOARDED);
                    break;
                case ORDER_EN_ROUTE_TO_DESTINATION:
                    //前往目的地
                    buildPushOrderStatus(orderId, OrderStatusEnum.IN_SERVICE);
                    break;
                case ORDER_ARRIVED_AT_DESTINATION:
                    //到达目的地
                    log.info("到达目的地,不做任何处理:{}", orderId);
                    break;
                case ORDER_WAITING_FOR_DROPOFF:
                    //等待乘客下车
                    buildPushOrderStatus(orderId, OrderStatusEnum.SEE_OFF_GUESTS);
                    break;
                case ORDER_DROPOFF_COMPLETED:
                    //下车完成
                    buildPushOrderStatus(orderId, OrderStatusEnum.APPROVAL_REJECTION);
                    break;
                case ORDER_COMPLETED:
                    //已完成
                    log.info("已完成,不做任何处理:{}", orderId);
                    break;
                case ORDER_CANCELLED:
                    //已取消
                    buildPushOrderStatus(orderId, OrderStatusEnum.CANCELED);
                    break;
                case ORDER_ARRIVED_AT_CRUISE_DESTINATION:
                    //到达巡游终点
                    buildPushOrderStatus(orderId, null);
                    break;
                case ORDER_PAUSED:
                    //订单暂停
                    log.info("订单暂停,不做任何处理:{}", orderId);
                    break;
                default:
                    break;
            }
        }
    }
    /**
     * 构建订单状态推送消息
     * @param orderId
     * @param status
     */
    public void buildPushOrderStatus(String orderId, OrderStatusEnum status){
        if(orderId.contains("XU")){
            //巡游单
        }else{
            //普通单
            //构建推送消息
            Map<String, Object> params = new HashMap<>();
            Map<String, Object> content = new HashMap<>();
            content.put("orderId", orderId);
            content.put("status", status.getCode());
            params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc());
            params.put("content", com.alibaba.fastjson2.JSONObject.toJSONString(content));
            params.put("timestamp", System.currentTimeMillis() + "");
            String message = JSONObject.toJSONString(params);
            R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId));
            XpOrder xpOrder = xpOrderR.getData();
            Long appUserId = xpOrder.getAppUserId();
            SendMessageDTO dto = new SendMessageDTO();
            dto.setAppUserId(appUserId + "");
            dto.setMessage(message);
            R<String> stringR = xpPushMessageClient.sendStatus(dto);
            if (R.isError(stringR)) {
                log.error("推送消息失败:{}", stringR.getMsg());
            }
        }
    }
}
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaProducerService.java
File was deleted
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/util/XPUUtil.java
@@ -1,5 +1,7 @@
package com.xiaopeng.automaticPilot.util;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
@@ -81,8 +83,9 @@
        header.setMsg_id(UUID.randomUUID().toString());
        header.setTimestamp(System.currentTimeMillis());
        header.setCmd(RTCmdType.CLOUD_ORDER_OPERATION);
        header.setSequence(1L);
        header.setType(MessageType.MESSAGE_TYPE_RESPONSE);
        Snowflake snowflake = IdUtil.getSnowflake(0, 0);
        header.setSequence(snowflake.nextId());
        header.setType(MessageType.MESSAGE_TYPE_REQUEST);
        msgContent.setHeader(header);
        // 消息内容
        JSONObject jsonObject = new JSONObject();
@@ -108,4 +111,38 @@
    }
    /**
     * 响应消息
     *
     * @param responseMessage
     * @return
     */
    public ResponseMessage responseMessage(ResponseMessage responseMessage) {
        MsgContent msgContent = new MsgContent();
        // 消息头
        RtMsgHeader header = new RtMsgHeader();
        header.setMsg_id(UUID.randomUUID().toString());
        header.setTimestamp(System.currentTimeMillis());
        header.setCmd(RTCmdType.CLOUD_TASK_CONTROL);
        Snowflake snowflake = IdUtil.getSnowflake(0, 0);
        header.setSequence(snowflake.nextId());
        header.setType(MessageType.MESSAGE_TYPE_RESPONSE);
        msgContent.setHeader(header);
        // 消息内容
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("resposne", responseMessage);
        msgContent.setContent_detail(jsonObject);
        log.info("云端-->XPU 响应消息:{}", msgContent);
        HttpRequest post = HttpUtil.createPost("");
        post.body(JSON.toJSONString(msgContent));
        HttpResponse execute = post.execute();
        int status = execute.getStatus();
        if (200 != status) {
            throw new RuntimeException("XPU 接口异常:" + execute.getStatus());
        }
        return null;
    }
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/constant/CacheConstants.java
@@ -120,7 +120,11 @@
     */
    public static final String ORDER_OLD_LOCATION = "order_old_location:";
    /**
     * 订单车辆位置
     * 订单车辆轨迹
     */
    public static final String ORDER_ROUTE = "order_route:";
    /**
     * 订单车辆轨迹,禁止用于其他用处
     */
    public static final String ORDER_ROUTE_CAPTURE = "order_route_capture:";
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/PushMessageTypeEnum.java
@@ -11,6 +11,7 @@
    ORDER_STATUS("ORDER_STATUS"),
    VEHICLE_LOCATION("VEHICLE_LOCATION"),
    ORDER_ROUTE("ORDER_ROUTE");
    @Getter
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/TaskTypeEnum.java
New file
@@ -0,0 +1,38 @@
package com.xiaopeng.common.core.enums;
import lombok.Getter;
import lombok.AllArgsConstructor;
@Getter
@AllArgsConstructor
public enum TaskTypeEnum {
    VEHICLE_INSPECTION(1, "车辆检查"),
    VEHICLE_CHARGING(2, "车辆充电"),
    VEHICLE_PREPARATION(3, "车辆整备"),
    VEHICLE_WASH(4, "整备洗车"),
    VEHICLE_REPAIR(5, "车辆维修"),
    VEHICLE_MAINTENANCE(6, "车辆保养"),
    VEHICLE_INSURANCE_EXPIRE(7, "车辆保险到期"),
    ANNUAL_INSPECTION_EXPIRE(8, "车辆年检到期"),
    LICENSE_EXPIRE(9,"车辆运营资质到期");
    private final Integer code;
    private final String desc;
    public static TaskTypeEnum getEnumByCode(Integer code) {
        for (TaskTypeEnum e : TaskTypeEnum.values()) {
            if (e.code.equals(code)) {
                return e;
            }
        }
        return null;
    }
    public static String getEnumDescByCode(Integer code) {
        for (TaskTypeEnum e : TaskTypeEnum.values()) {
            if (e.code.equals(code)) {
                return e.getDesc();
            }
        }
        return "";
    }
}
xp-robotaxi-common/xp-common-redis/src/main/java/com/xiaopeng/common/redis/service/RedisService.java
@@ -173,8 +173,9 @@
    }
    // 获取第一个元素但不删除
    public Object getFirstElement(String key) {
        return redisTemplate.opsForList().index(key, 0);
    public String getFirstElement(String key) {
        String index = (String) redisTemplate.opsForList().index(key, 0);
        return index;
    }
    // 获取第一个元素并删除(弹出)
@@ -374,7 +375,7 @@
     */
    public Set<String> getQuoteMatchName(String name) {
        // 模糊查询key为name的值
        Set<String> keys = redisTemplate.keys("*" + name );
        Set<String> keys = redisTemplate.keys(name + "*");
        return keys;
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpChargingRecordController.java
@@ -86,5 +86,17 @@
            @Valid @RequestBody XpChargingRecordPageQuery query) {
        return R.ok(xpChargingRecordService.queryChargingByVehicleId(query));
    }
    /**
     * 管理后台-车辆管理-运营车辆管理-删除充电记录
     * @param id
     * @return
     */
    @ApiOperation(value = "删除充电记录", tags = {"管理后台-车辆管理-运营车辆管理"})
    @DeleteMapping("/api/v1/chargingRecord/delete/{id}")
    public R<?> deleteRecordById(@PathVariable Long id){
        xpChargingRecordService.deleteRecordById(id);
        return R.ok();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpCheckLocationController.java
New file
@@ -0,0 +1,32 @@
package com.xiaopeng.maintenance.controller;
import com.xiaopeng.api.model.maintenance.XpCheckLocation;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.DisabledEnum;
import com.xiaopeng.maintenance.service.IXpCheckLocationService;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author MiTao
 * @date 2026-02-12 21:17
 *
 */
@Validated
@RestController
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCheckLocationController {
    private final IXpCheckLocationService xpCheckLocationService;
    @GetMapping("/api/v1/check-location/list")
    @ApiOperation(value = "获取打卡地址列表", tags = {"管理后台-车队管理"})
    public  R<List<XpCheckLocation>> getList(){
        return R.ok(xpCheckLocationService.lambdaQuery().eq(XpCheckLocation::getStatus, DisabledEnum.YES.getCode()).list());
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java
@@ -20,12 +20,10 @@
import com.xiaopeng.maintenance.service.XpTaskService;
import com.xiaopeng.maintenance.vo.VehicleListVO;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
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.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
@@ -171,4 +169,16 @@
    public R<PageInfo<XpTaskRecordVO>> queryTaskByVehicleId(@Valid @RequestBody XpTaskRecordPageQuery query) {
        return R.ok(xpTaskService.queryTaskByVehicleId(query));
    }
    /**
     * 管理后台-车辆管理-运营车辆管理-删除任务/事故
     * @param id
     * @return
     */
    @ApiOperation(value = "删除删除任务/事故", tags = {"管理后台-车辆管理-运营车辆管理"})
    @DeleteMapping("/api/v1/task/delete/{id}")
    public R<?> deleteTaskById(@ApiParam(name = "id", value = "任务ID") @PathVariable Long id){
        xpTaskService.deleteTaskById(id);
        return R.ok();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpChargingRecordService.java
@@ -49,4 +49,10 @@
     * @return 充电记录分页列表
     */
    PageInfo<XpChargingRecordVO> queryChargingByVehicleId(XpChargingRecordPageQuery query);
    /**
     * 根据ID删除充电记录
     * @param id
     */
    void deleteRecordById(Long id);
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/XpTaskService.java
@@ -57,4 +57,10 @@
     * @return
     */
    PageInfo<XpTaskRecordVO> queryTaskByVehicleId(@Valid XpTaskRecordPageQuery query);
    /**
     * 删除任务/事故
     * @param id
     */
    void deleteTaskById(Long id);
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java
@@ -186,4 +186,9 @@
        pageInfo.setTotal(resultPage.getTotal());
        return pageInfo;
    }
    @Override
    public void deleteRecordById(Long id) {
        removeById(id);
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -1,6 +1,7 @@
package com.xiaopeng.maintenance.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
@@ -8,6 +9,7 @@
import com.xiaopeng.api.model.maintenance.*;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.TaskTypeEnum;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.maintenance.constant.I18nKeyConstant;
@@ -20,15 +22,16 @@
import com.xiaopeng.maintenance.model.query.XpTaskRecordPageQuery;
import com.xiaopeng.maintenance.model.vo.*;
import com.xiaopeng.maintenance.service.XpTaskService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -41,6 +44,22 @@
 */
@Service
public class XpTaskServiceImpl extends ServiceImpl<XpTaskMapper, XpTask> implements XpTaskService {
    /**
     * 常规任务类型映射
     */
    private static final Map<String, String> TASK_TYPE_MAP = new java.util.HashMap<>();
    static {
        TASK_TYPE_MAP.put("1", "车辆检查");
        TASK_TYPE_MAP.put("2", "车辆充电");
        TASK_TYPE_MAP.put("3", "车辆整备");
        TASK_TYPE_MAP.put("4", "整备洗车");
        TASK_TYPE_MAP.put("5", "车辆维修");
        TASK_TYPE_MAP.put("6", "车辆保养");
        TASK_TYPE_MAP.put("7", "车辆保险到期");
        TASK_TYPE_MAP.put("8", "车辆年检到期");
        TASK_TYPE_MAP.put("9", "车辆运营资质到期");
    }
    @Autowired
    private XpTaskHandleDetailMapper xpTaskHandleDetailMapper;
@@ -581,6 +600,101 @@
    @Override
    public PageInfo<XpTaskRecordVO> queryTaskByVehicleId(XpTaskRecordPageQuery query) {
        return null;
        // 1. 构建查询条件
        LambdaQueryWrapper<XpTask> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(XpTask::getVehicleId, query.getVehicleId());
        if (query.getTaskCategory() != null) {
            wrapper.eq(XpTask::getTaskCategory, query.getTaskCategory());
        }
        if (StringUtils.isNotBlank(query.getTaskTypeId())) {
            wrapper.eq(XpTask::getTaskTypeId, query.getTaskTypeId());
        }
        if (query.getStartTime() != null) {
            wrapper.ge(XpTask::getCreateTime, query.getStartTime().atStartOfDay());
        }
        if (query.getEndTime() != null) {
            wrapper.le(XpTask::getCreateTime, query.getEndTime().atTime(23, 59, 59));
        }
        wrapper.orderByDesc(XpTask::getCreateTime);
        // 2. 执行分页查询
        Page<XpTask> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpTask> resultPage = this.page(page, wrapper);
        // 3. 转换为 VO 并填充 typeName
        List<XpTaskRecordVO> records = new ArrayList<>();
        if (!CollectionUtils.isEmpty(resultPage.getRecords())) {
            // 3.1 收集所有事故任务的 errorType ID
            Set<Long> errorTypeIds = new HashSet<>();
            for (XpTask task : resultPage.getRecords()) {
                if (task.getTaskCategory() != null && task.getTaskCategory() == 2
                    && StringUtils.isNotBlank(task.getTaskTypeId())) {
                    String[] ids = task.getTaskTypeId().split(",");
                    for (String id : ids) {
                        try {
                            errorTypeIds.add(Long.parseLong(id.trim()));
                        } catch (NumberFormatException e) {
                            // 忽略格式错误的ID
                        }
                    }
                }
            }
            // 3.2 批量查询 errorType
            Map<Long, String> errorTypeMap = new HashMap<>();
            if (!errorTypeIds.isEmpty()) {
                List<XpErrorType> errorTypes = errorTypeMapper.selectBatchIds(errorTypeIds);
                for (XpErrorType errorType : errorTypes) {
                    errorTypeMap.put(errorType.getId(), errorType.getErrorName());
                }
            }
            // 3.3 填充 typeName
            for (XpTask task : resultPage.getRecords()) {
                XpTaskRecordVO vo = new XpTaskRecordVO();
                BeanUtils.copyProperties(task, vo);
                // 填充 typeName
                if (task.getTaskCategory() != null && task.getTaskCategory() == 1) {
                    // 常规任务: 使用枚举映射
                    vo.setTypeName(TaskTypeEnum.getEnumDescByCode(Integer.valueOf(task.getTaskTypeId())));
                } else if (task.getTaskCategory() != null && task.getTaskCategory() == 2) {
                    // 事故任务: 拼接父子级名称
                    if (StringUtils.isNotBlank(task.getTaskTypeId())) {
                        String[] ids = task.getTaskTypeId().split(",");
                        if (ids.length >= 2) {
                            try {
                                String parentName = errorTypeMap.get(Long.parseLong(ids[0].trim()));
                                String childName = errorTypeMap.get(Long.parseLong(ids[1].trim()));
                                if (parentName != null && childName != null) {
                                    vo.setTypeName(parentName + "->" + childName);
                                }
                            } catch (NumberFormatException e) {
                                // 忽略格式错误的ID
                            }
                        }
                    }
                }
                records.add(vo);
            }
        }
        // 4. 构建分页结果
        PageInfo<XpTaskRecordVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        pageInfo.setRecords(records);
        pageInfo.setTotal(resultPage.getTotal());
        return pageInfo;
    }
    @Override
    public void deleteTaskById(Long id) {
        removeById(id);
    }
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -111,6 +111,9 @@
        // 存储redis
        redisService.setCacheObject(CacheConstants.ORDER_ROUTE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo);
        // 存储路线偏离缓存,后续进行轨迹截取
        redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo);
        return R.ok(routeVo);
    }
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java
@@ -5,6 +5,7 @@
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.common.core.utils.HttpUtils;
import com.xiaopeng.map.mangodb.service.XpGeoFencingPolygonService;
import com.xiaopeng.map.utils.mangoUtils.MongoGeoFenceUtil;
import org.springframework.beans.factory.annotation.Autowired;
@@ -54,11 +55,15 @@
        params.put("start", start.getLongitude() + "," + start.getLatitude());
        params.put("end", end.getLongitude() + "," + end.getLatitude());
        params.put("routeUserType",2);
        params.put("voidPolygons", avoidRoads);
        if(avoidRoads!=null && !avoidRoads.isEmpty()){
            params.put("voidPolygons", avoidRoads);
        }
        if(routePlanRule!=null){
            params.put("strategy", routePlanRule);
        }
        String post = HttpUtils.post(url, JSON.toJSONString(params));
        System.out.println("高德地图返回结果:"+ post);
        String forObject = restTemplate.getForObject(url, String.class, params);
        JSONObject jsonObject = JSON.parseObject(forObject);
        String data = jsonObject.getString("data");
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java
@@ -32,4 +32,7 @@
    // 订单创建失败
    String ORDER_CREATE_FAIL = "order.create.fail";
    //开通城市已存在
    String OPEN_CITY_EXISTS = "open.city.exists";
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpOpenCityController.java
@@ -3,17 +3,20 @@
import com.xiaopeng.api.model.operation.XpOpenCity;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.dto.OpenCityAddDTO;
import com.xiaopeng.operation.dto.OpenCityEditDTO;
import com.xiaopeng.operation.query.OpenCityPageQuery;
import com.xiaopeng.operation.service.XpOpenCityService;
import com.xiaopeng.operation.vo.OpenCityListVO;
import com.xiaopeng.operation.vo.XpOpenCityVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -71,6 +74,63 @@
        return R.ok(nameLists);
    }
    /**
     * 管理后台-开通城市管理-开通城市分页列表
     * @param query
     * @return
     */
    @ApiOperation(value = "开通城市分页列表", tags = {"管理后台-开通城市管理"})
    @PostMapping("/api/v1/openCity/pageList")
    public R<PageInfo<XpOpenCity>> getPageList(@RequestBody OpenCityPageQuery query) {
        return R.ok(xpOpenCityService.getPageList(query));
    }
    /**
     * 管理后台-开通城市管理-新增开通城市
     * @param dto
     * @return
     */
    @ApiOperation(value = "新增开通城市", tags = {"管理后台-开通城市管理"})
    @PostMapping("/api/v1/openCity/add")
    public R<?> add(@Valid @RequestBody OpenCityAddDTO dto) {
        xpOpenCityService.add(dto);
        return R.ok();
    }
    /**
     * 管理后台-开通城市管理-编辑开通城市
     * @param dto
     * @return
     */
    @ApiOperation(value = "编辑开通城市", tags = {"管理后台-开通城市管理"})
    @PostMapping("/api/v1/openCity/edit")
    public R<?> add(@Valid @RequestBody OpenCityEditDTO dto) {
        xpOpenCityService.edit(dto);
        return R.ok();
    }
    /**
     * 管理后台-开通城市管理-删除开通城市
     * @param id
     * @return
     */
    @ApiOperation(value = "删除开通城市", tags = {"管理后台-开通城市管理"})
    @DeleteMapping("/api/v1/openCity/delete/{id}")
    public R<?> deleteById(@ApiParam(name = "id", value = "开通城市ID") @PathVariable Long id) {
        xpOpenCityService.deleteById(id);
        return R.ok();
    }
    /**
     * 管理后台-开通城市管理-启用/禁用
     * @param id
     * @param status
     * @return
     */
    @ApiOperation(value = "启用/禁用", tags = {"管理后台-开通城市管理"})
    @PostMapping("/api/v1/openCity/upDown")
    public R<?> upDown(Long id, Integer status) {
        xpOpenCityService.upDown(id, status);
        return R.ok();
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointManagementController.java
@@ -86,7 +86,7 @@
     */
    @GetMapping("/queryPointById")
    @ApiOperation(value = "点位详情")
    public R queryById(@RequestParam Long id) {
    public R<XpPoint> queryById(@RequestParam Long id) {
        return R.ok(xpPointService.getById(id));
    }
    /**
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/OpenCityAddDTO.java
New file
@@ -0,0 +1,45 @@
package com.xiaopeng.operation.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
 * @author MiTao
 * @date 2026-02-12 22:23
 *
 */
@Data
@ApiModel("新增开通城市数据传输对象")
public class OpenCityAddDTO {
    @ApiModelProperty(value = "省份名称")
    @NotBlank(message = "省份名称不能为空")
    private String provinceName;
    @ApiModelProperty(value = "省份code")
    @NotBlank(message = "省份code名称不能为空")
    private String provinceCode;
    @ApiModelProperty(value = "城市名称")
    @NotBlank(message = "城市名称名称不能为空")
    private String cityName;
    @ApiModelProperty(value = "城市code")
    @NotBlank(message = "城市code名称不能为空")
    private String cityCode;
    @ApiModelProperty(value = "区县名称")
    private String districtName;
    @ApiModelProperty(value = "区县code")
    private String districtCode;
    @ApiModelProperty(value = "状态 1=启用 0=禁用")
    @NotNull(message = "状态不能为空")
    private Integer status;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/OpenCityEditDTO.java
New file
@@ -0,0 +1,22 @@
package com.xiaopeng.operation.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
 * @author MiTao
 * @date 2026-02-12 22:25
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("开通城市编辑数据传输对象")
public class OpenCityEditDTO extends OpenCityAddDTO{
    @ApiModelProperty(value = "ID")
    @NotNull(message = "ID不能为空")
    private Long id;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/query/OpenCityPageQuery.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.operation.query;
import com.xiaopeng.common.core.web.page.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * @author MiTao
 * @date 2026-02-12 22:13
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("开通城市分页查询对象")
public class OpenCityPageQuery extends BasePage {
    @ApiModelProperty(value = "城市名称")
    private String cityName;
    @ApiModelProperty(value = "状态 1=启用 0=禁用")
    private Integer status;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/XpOpenCityService.java
@@ -3,9 +3,14 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.operation.XpOpenCity;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.dto.OpenCityAddDTO;
import com.xiaopeng.operation.dto.OpenCityEditDTO;
import com.xiaopeng.operation.query.OpenCityPageQuery;
import com.xiaopeng.operation.vo.OpenCityListVO;
import com.xiaopeng.operation.vo.XpOpenCityVo;
import javax.validation.Valid;
import java.util.List;
/**
@@ -28,4 +33,38 @@
     */
    List<OpenCityListVO> getOpenCityListForAdmin();
    /**
     * 开通城市分页列表
     * @return
     */
    PageInfo<XpOpenCity> getPageList(OpenCityPageQuery query);
    /**
     * 新增开通城市
     *
     * @param dto
     */
    void add(@Valid OpenCityAddDTO dto);
    /**
     * 编辑开通城市
     *
     * @param dto
     */
    void edit(@Valid OpenCityEditDTO dto);
    /**
     * 删除开通城市
     * @param id
     */
    void deleteById(Long id);
    /**
     * 管理后台-开通城市管理-启用/禁用
     *
     * @param id
     * @param status
     * @return
     */
    void upDown(Long id, Integer status);
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpOpenCityServiceImpl.java
@@ -1,11 +1,19 @@
package com.xiaopeng.operation.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.operation.mapper.XpOpenCityMapper;
import com.xiaopeng.api.model.operation.XpOpenCity;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.security.utils.SecurityUtils;
import com.xiaopeng.operation.constant.I18nKeyConstant;
import com.xiaopeng.operation.dto.OpenCityAddDTO;
import com.xiaopeng.operation.dto.OpenCityEditDTO;
import com.xiaopeng.operation.mapper.XpOpenCityMapper;
import com.xiaopeng.operation.query.OpenCityPageQuery;
import com.xiaopeng.operation.service.XpOpenCityService;
import com.xiaopeng.operation.vo.OpenCityListVO;
import com.xiaopeng.operation.vo.XpOpenCityNextVo;
@@ -13,9 +21,9 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -80,4 +88,58 @@
                })
                .collect(Collectors.toList());
    }
    @Override
    public PageInfo<XpOpenCity> getPageList(OpenCityPageQuery query) {
        Page<XpOpenCity> page = lambdaQuery().like(StringUtils.isNotBlank(query.getCityName()), XpOpenCity::getCityName, query.getCityName())
                .eq(Objects.nonNull(query.getStatus()), XpOpenCity::getStatus, query.getStatus())
                .orderByDesc(XpOpenCity::getCreateTime)
                .page(new Page<>(query.getPageNum(), query.getPageSize()));
        PageInfo<XpOpenCity> pageInfo = new PageInfo<>();
        pageInfo.setRecords(page.getRecords());
        page.setTotal(page.getTotal());
        return pageInfo;
    }
    @Override
    public void add(OpenCityAddDTO dto) {
        checkIfExist(dto, null);
        XpOpenCity xpOpenCity = BeanUtil.copyProperties(dto, XpOpenCity.class);
        xpOpenCity.setCreateBy(SecurityUtils.getLoginUser().getSysUser().getNickName());
        save(xpOpenCity);
    }
    /**
     * 校验开通城市是否已添加
     * @param dto
     * @param excludeOpenCityId
     */
    private void checkIfExist(OpenCityAddDTO dto, Long excludeOpenCityId) {
        Long count = lambdaQuery().eq(XpOpenCity::getProvinceCode, dto.getProvinceCode())
                .eq(XpOpenCity::getCityCode, dto.getCityCode())
                .eq(StringUtils.isNotBlank(dto.getDistrictCode()), XpOpenCity::getDistrictCode, dto.getDistrictCode())
                .ne(Objects.nonNull(excludeOpenCityId), XpOpenCity::getId, excludeOpenCityId)
                .count();
        if (count > 0) {
            throw new ServiceException("该城市已存在", I18nKeyConstant.OPEN_CITY_EXISTS);
        }
    }
    @Override
    public void edit(OpenCityEditDTO dto) {
        checkIfExist(dto, dto.getId());
        XpOpenCity xpOpenCity = BeanUtil.copyProperties(dto, XpOpenCity.class);
        xpOpenCity.setUpdateBy(SecurityUtils.getLoginUser().getSysUser().getNickName());
        updateById(xpOpenCity);
    }
    @Override
    public void deleteById(Long id) {
        removeById(id);
    }
    @Override
    public void upDown(Long id, Integer status) {
        lambdaUpdate().eq(XpOpenCity::getId, id).set(XpOpenCity::getStatus, status).update();
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java
@@ -396,11 +396,11 @@
        // 获取起步里程
        XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, dto.getOpenCityId()));
        Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson());
        if (startMileage == null) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
//        XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, dto.getOpenCityId()));
//        Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson());
//        if (startMileage == null) {
//            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
//        }
        // 根据id获取手机号
@@ -417,7 +417,7 @@
        // 生成订单数据
        XpOrder xpOrder = new XpOrder();
        xpOrder.setStartMileage(Double.valueOf(startMileage));
//        xpOrder.setStartMileage(Double.valueOf(startMileage));
        xpOrder.setAppUserId(Long.valueOf(uid));
        xpOrder.setOrderNumber("XP" + System.currentTimeMillis());
@@ -473,10 +473,10 @@
        Integer distance = 0;
        Integer duration = 0;
        if (distanceAndDuration.getCode() != 200) {
            throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
//            throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
        }
        distance = distanceAndDuration.getData().getDistance();
        duration = distanceAndDuration.getData().getDuration();
//        distance = distanceAndDuration.getData().getDistance();
//        duration = distanceAndDuration.getData().getDuration();
        // 产生订单
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java
@@ -3,30 +3,33 @@
import cn.hutool.core.bean.BeanUtil;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.VehicleControlClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpTradingStrategy;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.operation.mapper.XpElectronicFenceMapper;
import com.xiaopeng.operation.service.IXpElectronicFenceAreaService;
import com.xiaopeng.operation.service.IXpTradingStrategyService;
import com.xiaopeng.operation.service.XpPointService;
import com.xiaopeng.operation.utils.mango.VehiclePositionService;
import com.xiaopeng.operation.utils.map.FastDistanceCalculator;
import com.xiaopeng.operation.vo.RouterVo;
import com.xiaopeng.operation.vo.VehiclePositionVo;
import org.redisson.Redisson;
import com.xiaopeng.api.vo.operation.VehiclePositionVo;
import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@@ -62,6 +65,9 @@
    private UserVehicleClient userVehicleClient;
    @Autowired
    private XpVehicleClient xpVehicleClient;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
@@ -80,21 +86,24 @@
        threadPoolTaskExecutor.initialize();
    }
    @Autowired
    private XpElectronicFenceMapper xpElectronicFenceMapper;
    /**
     * 5秒执行一次
     */
//    @Scheduled(cron = "0/5 * * * * ?")
    @Scheduled(cron = "0/5 * * * * ?")
    public void taskOrder() {
        System.out.println("5秒执行一次");
        Set<String> quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID);
        for (String name : quoteMatchName) {
            Object firstElement = redisService.getFirstElement(name);
            String firstElement = redisService.getFirstElement(name);
            if (firstElement != null) {
                String[] split = firstElement.toString().split("_");
                // 如果市未处理 创建新任务
                if (split[2].equals("0")) {
                    // 状态改为处理中
                    redisService.updateFirst(name, split[0] + "_" + split[1] + "_1");
//                    redisService.updateFirst(name, split[0] + "_" + split[1] + "_1"+split[3]+split[4]+split[5]+split[6]);
                    threadPoolTaskExecutor.submit(() -> {
                        orderProcess(name, firstElement.toString());
                    });
@@ -110,6 +119,7 @@
     * @param firstElement 队列第一个元素
     */
    private void orderProcess(String quoteName, String firstElement) {
        System.out.println("进入处理环节");
        XpTradingStrategy xpTradingStrategy = xpTradingStrategyService.getOne(null);
        String orderId = firstElement.split("_")[0];
        String lonLat = firstElement.split("_")[1];
@@ -190,7 +200,7 @@
        processPushOrder(quoteName, count, uid, split, orderNo, xpTradingStrategy, distance, duration, planRule);
        // 最终没找到车
        redisService.popFirstElement(quoteName);
//        redisService.popFirstElement(quoteName);
        // 取消订单
        xpOrderClient.cancelOrder(orderId);
@@ -206,7 +216,12 @@
     * @param orderNo 订单号
     * @param xpTradingStrategy  规则
     */
    @SneakyThrows
    private void processPushOrder(String quoteName, int count, String uid, String[] split, String orderNo, XpTradingStrategy xpTradingStrategy, String distance, String duration, String planRule) {
        System.out.println("进入推单环环节");
        if(count==0){
            count=1;
        }
        // 订单状态
        R<XpOrder> xpOrderR = xpOrderClient.queryOrderByOrderNo(orderNo);
        if (xpOrderR.getCode() != 200) {
@@ -215,12 +230,41 @@
        if (xpOrderR.getData().getStatus() != 1) {
            return;
        }
        XpOrder xpOrder = xpOrderR.getData();
        // 找出围栏id
        Long xpElectronicFenceAreaId = xpOrder.getXpElectronicFenceAreaId();
        Long electronicId = xpElectronicFenceAreaService.getById(xpElectronicFenceAreaId).getElectronicId();
        XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(electronicId);
        for (int i = 0; i < count; i++) {
            System.out.println("进入找车环节");
            // 根据距离找车
            // 车多则根据配置找具体车数量 根据距离最近
            // 更改 暂不限制数量
            List<VehiclePosition> positionList = vehiclePositionService.findAvailableVehiclesNearby(Double.parseDouble(split[0]), Double.parseDouble(split[1]), xpTradingStrategy.getOnePushDistance(), xpTradingStrategy.getSimultaneouslyOrderCount());
            // 获取车辆的区域信息
            if (positionList.isEmpty()) {
                continue;
            }
            List<String> vinList = positionList.stream().map(VehiclePosition::getVin).collect(Collectors.toList());
            // 获取车辆的信息 筛选出可用车辆
            R<List<String>> vehiclePositionVos = xpVehicleClient.queryVehiclePositionByVin(new VehiclePositionByVinDto(xpElectronicFence.getId(), vinList, xpTradingStrategy.getSimultaneouslyOrderCount()));
            if (vehiclePositionVos.getCode() != 200) {
                continue;
            }
            List<VehiclePositionVo> availableVehiclesNearby = BeanUtil.copyToList(positionList, VehiclePositionVo.class);
            List<String> data = vehiclePositionVos.getData();
            // 找出符合条件的
            availableVehiclesNearby = availableVehiclesNearby.stream().filter(e -> data.contains(e.getVin())).collect(Collectors.toList());
//            List<VehiclePositionVo> availableVehiclesNearby =vehiclePositionVos.getData();
            if (!availableVehiclesNearby.isEmpty()) {
                if (availableVehiclesNearby.size() > 1) {
@@ -348,7 +392,7 @@
                        if (max1.equals(weightElectricity)) {
                            // 计算车的分数  分数最大是车的size
                            availableVehiclesNearby= availableVehiclesNearby.stream().sorted(Comparator.comparing(VehiclePositionVo::getCurrentElectricity).reversed()).collect(Collectors.toList());
                            availableVehiclesNearby = availableVehiclesNearby.stream().sorted(Comparator.comparing(VehiclePositionVo::getCurrentElectricity).reversed()).collect(Collectors.toList());
                            int size = tendencyElectricity;
                            // 做比对的分数
                            int temp = availableVehiclesNearby.get(0).getCurrentElectricity();
@@ -363,9 +407,9 @@
                        // 算出总分 再来排序计算
                        for (VehiclePositionVo vehiclePositionVo : availableVehiclesNearby) {
                            vehiclePositionVo.setOrderValueTotal(vehiclePositionVo.getCurrentElectricityScore() + vehiclePositionVo.getDistanceScore() + vehiclePositionVo.getTimeScore() + vehiclePositionVo.getDistanceDifference()+ vehiclePositionVo.getTimeDifference());
                            vehiclePositionVo.setOrderValueTotal(vehiclePositionVo.getCurrentElectricityScore() + vehiclePositionVo.getDistanceScore() + vehiclePositionVo.getTimeScore() + vehiclePositionVo.getDistanceDifference() + vehiclePositionVo.getTimeDifference());
                        }
                        availableVehiclesNearby= availableVehiclesNearby.stream().sorted(Comparator.comparing(VehiclePositionVo::getOrderValueTotal).reversed()).collect(Collectors.toList());
                        availableVehiclesNearby = availableVehiclesNearby.stream().sorted(Comparator.comparing(VehiclePositionVo::getOrderValueTotal).reversed()).collect(Collectors.toList());
                        int size = tendencyOrderValue;
@@ -481,7 +525,7 @@
                            Integer secondMax = values.get(1);
                            // 判断第二大的是哪个
                            if(Objects.equals(secondMax, tendencyDistance)){
                            if (Objects.equals(secondMax, tendencyDistance)) {
                                for (VehiclePositionVo vehiclePosition : availableVehiclesNearby) {
                                    RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
                                    LonLatDto lonLatDto = new LonLatDto();
@@ -515,7 +559,7 @@
                            }
                            if(Objects.equals(secondMax, tendencyTime)){
                            if (Objects.equals(secondMax, tendencyTime)) {
                                for (VehiclePositionVo vehiclePosition : availableVehiclesNearby) {
                                    RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
                                    LonLatDto lonLatDto = new LonLatDto();
@@ -550,7 +594,7 @@
                            // 订单价值
                            if(Objects.equals(secondMax, tendencyOrderValue)){
                            if (Objects.equals(secondMax, tendencyOrderValue)) {
                            }
@@ -565,28 +609,33 @@
                    }
                    VehiclePositionVo vehiclePositionVo = availableVehiclesNearby.stream().sorted(Comparator.comparing(VehiclePositionVo::getTotal).reversed()).collect(Collectors.toList()).get(0);
                    VehiclePositionVo vehiclePositionVo1 = vehiclePositionVo;
                    RLock lock = redissonClient.getLock("FIND_CAR_" + vehiclePositionVo1.getVin());
//
//                    VehiclePositionVo vehiclePositionVo1 = vehiclePositionVo;
                    RLock lock = redissonClient.getLock("FIND_CAR_" + vehiclePositionVo.getVin());
                    lock.lock(10, TimeUnit.SECONDS);
                    try {
                        VehiclePosition vehiclePosition = vehiclePositionService.findByVehicleId(vehiclePositionVo.getVehicle_id());
//                        VehiclePosition vehiclePosition = vehiclePositionService.findByVehicleId(vehiclePositionVo.getVehicle_id());
                        R<XpVehicle> vehiclePositionR = xpVehicleClient.findByVehicleVin(vehiclePositionVo.getVin());
                        if (vehiclePositionR.getCode() != 200) {
                            continue;
                        }
                        XpVehicle vehiclePosition = vehiclePositionR.getData();
                        if (vehiclePosition.getStatus() != 1 || vehiclePosition.getState() != 0) {
                            lock.unlock();
                            continue;
                        }
                        if (vehiclePosition.getHasOrder() == 1) {
                            //  关闭巡游单子 TODO
                        }
                        //  关闭巡游单子 TODO
                        AddTaxiPassengerVo addTaxiPassengerVo = new AddTaxiPassengerVo();
                        addTaxiPassengerVo.setVin(vehiclePosition.getVin());
                        addTaxiPassengerVo.setVin(vehiclePosition.getVehicleVin());
                        addTaxiPassengerVo.setUid(uid);
                        addTaxiPassengerVo.setOrderNo(orderNo);
                        // TODO appid
                        addTaxiPassengerVo.setAppId("");
                        BaseVehicleUserDto baseVehicleUserDto = userVehicleClient.addTaxiPassenger(addTaxiPassengerVo);
                        if (baseVehicleUserDto.getCode() == 200) {
                            // TODO 下单
                            // 绑定成功  弹出这个队列第一个订单
                            redisService.popFirstElement(quoteName);
@@ -601,13 +650,17 @@
                } else {
                    // 直接派单  定义一个锁来锁定车辆
                    VehiclePosition vehiclePosition = positionList.get(0);
                    Long vehicleId = vehiclePosition.getVehicle_id();
                    String vehicleVin = vehiclePosition.getVin();
                    RLock lock = redissonClient.getLock("FIND_CAR_" + vehiclePosition.getVin());
                    lock.lock(10, TimeUnit.SECONDS);
                    try {
                        // 查询车辆状态
                        VehiclePosition vehicleId1 = vehiclePositionService.findByVehicleId(vehicleId);
                       R<XpVehicle> vehicleR = xpVehicleClient.findByVehicleVin(vehicleVin);
                       if(vehicleR.getCode()!=200){
                           continue;
                       }
                       XpVehicle vehicleId1 = vehicleR.getData();
                        if (vehicleId1.getStatus() != 1 || vehicleId1.getState() != 0) {
                            lock.unlock();
                            continue;
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/mango/VehiclePositionService.java
@@ -2,22 +2,26 @@
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class VehiclePositionService {
    @Resource
    private MongoTemplate mongoTemplate;
    /**
     * 查询附近指定距离内且状态正常的车辆
     * @param longitude 经度
@@ -28,27 +32,32 @@
    public List<VehiclePosition> findAvailableVehiclesNearby(double longitude,
                                                             double latitude,
                                                             double distanceInMeters,int num) {
        // 创建查询点
        Point referencePoint = new Point(longitude, latitude);
        // 设置最大距离(MongoDB使用公里或米,这里转换为公里)
        distanceInMeters = distanceInMeters / 1000;
        try {
            // 1. 创建查询点
            Point referencePoint = new Point(longitude, latitude);
        // 构建查询条件
        Query query = new Query();
        query.addCriteria(Criteria.where("point")
                .near(referencePoint)
                .maxDistance(distanceInMeters));
        query.addCriteria(Criteria.where("status").is(1)); // 运营中
        query.addCriteria(Criteria.where("state").is(1));  // 接单中
        // 可以添加排序,按距离排序
         query.with(Sort.by(Sort.Direction.ASC, "point"));
        // 可以限制返回数量
         query.limit(num);
        return mongoTemplate.find(query, VehiclePosition.class);
            // 2. 构建 NearQuery - 完全使用 NearQuery 的 API
            NearQuery nearQuery = NearQuery.near(referencePoint, Metrics.KILOMETERS)
                    .maxDistance(distanceInMeters / 1000)  // 转成公里
                    .spherical(true);
            // 4. 执行查询 - 明确指定集合名
            GeoResults<VehiclePosition> geoResults = mongoTemplate.geoNear(
                    nearQuery,
                    VehiclePosition.class,
                    "vehicle_position"  // 明确指定集合名,避免映射错误
            );
            // 5. 提取结果
            return geoResults.getContent().stream()
                    .map(geoResult -> geoResult.getContent())
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
@@ -63,4 +72,21 @@
        return mongoTemplate.findOne(query, VehiclePosition.class);
    }
    /**
     * 所有的车辆
     */
    public List<VehiclePosition> findAll() {
        // 构建查询条件
        Query query = new Query();
        query.addCriteria(Criteria.where("status").is(1)); // 运营中
        query.addCriteria(Criteria.where("state").is(1));  // 接单中
        // 可以添加排序,按距离排序
        query.with(Sort.by(Sort.Direction.ASC, "point"));
        return mongoTemplate.find(query, VehiclePosition.class);
    }
}
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties
@@ -11,4 +11,5 @@
fence.structure.not.exist=围栏构成不存在
fence.not.exist=围栏不存在
fence.status.cannot.copied=围栏启用状态不可复制;
order.create.fail=订单创建失败
order.create.fail=订单创建失败
open.city.exists=该城市已存在
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -11,4 +11,5 @@
fence.structure.not.exist=The fence composition does not exist\
fence.status.cannot.copied=The enabled state of the fence cannot be copied;
fence.not.exist=The fence does not exist.
order.create.fail=Order creation failed
order.create.fail=Order creation failed
open.city.exists=This city already exists
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -11,4 +11,5 @@
fence.structure.not.exist=围栏构成不存在
fence.not.exist=围栏不存在
fence.status.cannot.copied=围栏启用状态不可复制
order.create.fail=订单创建失败
order.create.fail=订单创建失败
open.city.exists=该城市已存在
xp-robotaxi-order-boot/pom.xml
@@ -15,19 +15,18 @@
    </description>
    <dependencies>
        <!--        <dependency>-->
        <!--            <groupId>com.alibaba.cloud</groupId>-->
        <!--            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>-->
        <!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>com.alibaba.cloud</groupId>-->
        <!--            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
        <!--        </dependency>-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
@@ -120,21 +119,23 @@
        </dependency>
        <dependency>
            <groupId>com.xiaopeng.logan</groupId>
            <artifactId>logan-starter-core</artifactId>
            <version>2.3.5</version>
        </dependency>
        <dependency>
            <groupId>com.xiaopeng.logan</groupId>
            <artifactId>logan-commons-legacy</artifactId>
            <version>2.3.5</version>
        </dependency>
        <dependency>
            <groupId>com.xiaopeng.logan</groupId>
            <artifactId>logan2-namor-client</artifactId>
            <version>1.16.0</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>com.xiaopeng.logan</groupId>-->
<!--            <artifactId>logan-starter-core</artifactId>-->
<!--            <version>2.3.5</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.xiaopeng.logan</groupId>-->
<!--            <artifactId>logan-commons-legacy</artifactId>-->
<!--            <version>2.3.5</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.xiaopeng.logan</groupId>-->
<!--            <artifactId>logan2-namor-client</artifactId>-->
<!--            <version>1.16.0</version>-->
<!--        </dependency>-->
    </dependencies>
    <build>
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -335,20 +335,25 @@
     * 手动结束订单
     */
    @PutMapping("/api/v1/order/endTrip/{order_id}")
    @PutMapping("/api/v1/mini-program/order/endTrip/{orderId}")
    @ApiOperation(value = "结束行程", tags = {"小程序-结束行程"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R endTrip(@PathVariable("orderId") Long orderId,HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        xpOrderService.endTrip(orderId,uid);
        try {
            xpOrderService.endTrip(orderId,uid);
        }catch (Exception e){
            return R.ok();
        }
        return R.ok();
    }
    /**+
     * 获取行程费用明细
     */
    @PutMapping("/api/v1/order/fare-breakdown/{order_id}")
    @PutMapping("/api/v1/mini-program/order/fare-breakdown/{orderId}")
    @ApiOperation(value = "获取行程费用明细", tags = {"小程序-获取行程费用明细"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<FareBreakdownVo> fareBreakdown(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
@@ -359,7 +364,7 @@
    /**
     * 订单评价
     */
    @PostMapping("/api/v1/order/payment")
    @PostMapping("/api/v1/mini-program/order/payment")
    @ApiOperation(value = "发起支付", tags = {"小程序-发起支付"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R payment(@Validated @RequestBody XpOrderPayDto dto, HttpServletRequest request) {
@@ -396,7 +401,7 @@
     */
    @ApiOperation(value = "通过订单id查询车辆信息", tags = {"小程序-业务流程"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    @GetMapping("/api/v1/order/queryVehicleByOrderId")
    @GetMapping("/api/v1/mini-program/order/queryVehicleByOrderId")
    public R<XpVehicle> queryVehicleByOrderId(@RequestParam Long orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.queryVehicleByOrderId(orderId,uid));
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -4,6 +4,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -13,6 +14,7 @@
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.dto.order.HistoryEndPointsDto;
import com.xiaopeng.api.dto.order.HistoryStartPointsDto;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.dto.statistics.DailyCountDTO;
import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
@@ -20,6 +22,7 @@
import com.xiaopeng.api.feignClient.account.XpAppUserClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient;
@@ -27,6 +30,7 @@
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.feignClient.operation.XpPricingClient;
import com.xiaopeng.api.model.account.XpAppUser;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
@@ -39,6 +43,8 @@
import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.OrderStatusEnum;
import com.xiaopeng.common.core.enums.PushMessageTypeEnum;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.DateUtils;
import com.xiaopeng.common.core.utils.GeodesyUtil;
@@ -118,6 +124,7 @@
    private final XpPricingClient xpPricingClient;
    private final XpSystemSettingClient xpSystemSettingClient;
    private final OrderTrajectoryService orderTrajectoryService;
    private final XpPushMessageClient xpPushMessageClient;
    @Autowired
    private MongoTemplate mongoTemplate;
@@ -757,6 +764,23 @@
        xpOrderCancel.setCancelSource(4);
        xpOrderCancel.setCancelReason("系统取消订单");
        xpOrderCancelService.save(xpOrderCancel);
        // 推送消息
        // 消息推送
        Map<String, Object> params = new HashMap<>();
        Map<String, Object> content = new HashMap<>();
        content.put("orderId",order.getId());
        content.put("status", OrderStatusEnum.CANCELED.getCode());
        params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc());
        params.put("content", com.alibaba.fastjson2.JSONObject.toJSONString(content));
        params.put("timestamp", new Date().getTime()+"");
        String message = JSONObject.toJSONString(params);
        SendMessageDTO sendMessageDTO = new SendMessageDTO();
        sendMessageDTO.setMessage(message);
        sendMessageDTO.setAppUserId(String.valueOf(order.getAppUserId()));
        xpPushMessageClient.sendStatus(sendMessageDTO);
        log.info("订单取消消息推送完毕");
    }
    /**
@@ -806,7 +830,10 @@
        xpOrder.setStatus(status);
        switch (status) {
            case 4:
                // 添加取消订单定时任务job
                // 查询起点点位
                Long startPointId = xpOrder.getStartPointId();
                XpPoint xpPoint = xpPointClient.queryById(startPointId).getData();
                // 添加取消订单定时任务job  时间为点位限停时长
                Map<String, ? extends Object> cancelJobMaps =
                        new ImmutableMap.Builder<String, Long>().
                                put("id", xpOrder.getId())
@@ -815,7 +842,7 @@
                        OrderCancelJob.class,
                        (OrderCancelJob.name + xpOrder.getId()).toUpperCase(),
                        TimeJobType.ORDER_CANCEL,
                        DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(5)),
                        DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(xpPoint.getParkingTimeLimit())),
                        cancelJobMaps
                );
                // 车辆到达上车点时间
xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
New file
@@ -0,0 +1,102 @@
package com.xiaopeng.pushMessage.controller;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.pushMessage.PushOrderRouteDTO;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.PushMessageTypeEnum;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.pushMessage.utils.MapMatchingUtil;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@Api(tags = "订单推送轨迹接口")
@RestController
@RequestMapping("")
public class PushOrderRouteController {
    @Autowired
    private RedisService redisService;
    @Autowired
    private XpPushMessageClient xpPushMessageClient;
    @Autowired
    private XpOrderClient xpOrderClient;
    /**
     * 通过定位推送订单轨迹
     * @return
     */
    @PostMapping("/inner/v1/order/pushOrderRoute")
    public R<String> pushOrderRoute(@Validated @RequestBody PushOrderRouteDTO dto) {
        // 获取路线规划
        RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType());
        if(Objects.nonNull(routeVo)){
            // 查询订单信息
            XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData();
            // 计算实时点位到线段的最优截取
            List<LonLatDto> lonLatDtos = MapMatchingUtil.clipBySegmentDistance(routeVo.getPolyline(), new LonLatDto(dto.getLongitude(), dto.getLatitude()));
            routeVo.setDistance(dto.getDistance());
            routeVo.setDuration(dto.getDuration());
            routeVo.setPolyline(lonLatDtos);
            // 存储最新路线偏离缓存,后续进行轨迹截取
            redisService.setCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType(), routeVo);
            // 封装轨迹点位集合
            Map<String, Object> polyline = new HashMap<>();
            for (LonLatDto lonLatDto : lonLatDtos) {
                polyline.put("longitude",lonLatDto.getLongitude());
                polyline.put("latitude",lonLatDto.getLatitude());
            }
            // 封装路由详情
            Map<String, Object> route = new HashMap<>();
            route.put("distance",dto.getDistance());
            route.put("duration",dto.getDuration());
            route.put("polyline",JSONObject.toJSONString(polyline));
            // 封装消息内容
            Integer routeType = dto.getRouteType();
            if(routeType == 2){
                routeType = 3;
            }else {
                routeType = xpOrder.getStatus() == 4 ? 1 : 2;
            }
            Map<String, Object> content = new HashMap<>();
            content.put("orderId",dto.getOrderId());
            content.put("routeType",routeType);
            content.put("route",JSONObject.toJSONString(route));
            // 封装轨迹信息
            Map<String, Object> params = new HashMap<>();
            params.put("type", PushMessageTypeEnum.ORDER_ROUTE.getDesc());
            params.put("content", JSONObject.toJSONString(content));
            params.put("timestamp", new Date().getTime()+"");
            // 进行消息推送
            SendMessageDTO sendMessageDTO = new SendMessageDTO();
            sendMessageDTO.setAppUserId(String.valueOf(xpOrder.getAppUserId()));
            sendMessageDTO.setMessage(JSONObject.toJSONString(params));
            xpPushMessageClient.sendRoute(sendMessageDTO);
        }
        return R.ok();
    }
}
xp-robotaxi-pushMessage-boot/src/main/java/com/xiaopeng/pushMessage/utils/MapMatchingUtil.java
New file
@@ -0,0 +1,123 @@
package com.xiaopeng.pushMessage.utils;
import com.xiaopeng.api.dto.operation.LonLatDto;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Slf4j
public class MapMatchingUtil {
    /**
     * 轨迹裁剪(线段版本)
     * @param polyline
     * @param currentPoint
     * @return
     */
    public static List<LonLatDto> clipBySegmentDistance(List<LonLatDto> polyline,LonLatDto currentPoint) {
        if (polyline == null || polyline.size() < 2) {
            return Collections.emptyList();
        }
        double minDistance = Double.MAX_VALUE;
        int minSegmentIndex = 0;
        int increaseCount = 0;
        final int threshold = 3;
        final double tolerance = 1.0;
        for (int i = 0; i < polyline.size() - 1; i++) {
            double d = pointToSegmentDistance(
                    currentPoint,
                    polyline.get(i),
                    polyline.get(i + 1)
            );
            if (d < minDistance) {
                minDistance = d;
                minSegmentIndex = i;
                increaseCount = 0;
            } else if (d > minDistance + tolerance) {
                increaseCount++;
            }
            if (increaseCount >= threshold) {
                break;
            }
        }
        // 从最优线段的后一个点开始截取
        return new ArrayList<>(
                polyline.subList(minSegmentIndex + 1, polyline.size())
        );
    }
    /**
     * 点到线段距离
     * @param p
     * @param a
     * @param b
     * @return
     */
    public static double pointToSegmentDistance(
            LonLatDto p,
            LonLatDto a,
            LonLatDto b) {
        double refLat = p.getLatitude();
        double[] pXY = toXY(p.getLatitude(), p.getLongitude(), refLat);
        double[] aXY = toXY(a.getLatitude(), a.getLongitude(), refLat);
        double[] bXY = toXY(b.getLatitude(), b.getLongitude(), refLat);
        double x = pXY[0];
        double y = pXY[1];
        double x1 = aXY[0];
        double y1 = aXY[1];
        double x2 = bXY[0];
        double y2 = bXY[1];
        double dx = x2 - x1;
        double dy = y2 - y1;
        if (dx == 0 && dy == 0) {
            return Math.hypot(x - x1, y - y1);
        }
        double t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
        if (t < 0) {
            return Math.hypot(x - x1, y - y1);
        } else if (t > 1) {
            return Math.hypot(x - x2, y - y2);
        }
        double projX = x1 + t * dx;
        double projY = y1 + t * dy;
        return Math.hypot(x - projX, y - projY);
    }
    /**
     * 经纬度转平面坐标(局部近似)
     * @param lat
     * @param lon
     * @param refLat
     * @return
     */
    private static double[] toXY(double lat, double lon, double refLat) {
        double R = 6378137; // 地球半径
        double x = Math.toRadians(lon) * R * Math.cos(Math.toRadians(refLat));
        double y = Math.toRadians(lat) * R;
        return new double[]{x, y};
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/config/DataUpdateHandlerConfig.java
New file
@@ -0,0 +1,72 @@
package com.xiaopeng.vehicleConvoy.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
/**
 * @author xiaochen
 * @ClassName DataUpdateInterceptor
 * @Description 数据更新操作处理
 * @date 2021-12-15
 * <p>
 * 注意,之前在此处注入了 JwtTokenUtils
 * <p>
 * 造成spring循环依赖,项目支棱不起来
 */
@Slf4j
@Configuration
public class DataUpdateHandlerConfig implements MetaObjectHandler {
    /**
     * 新增数据执行
     *
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        String userName = "";
        try{
            //  获取登录信息
            userName = SecurityUtils.getLoginUser().getSysUser().getNickName();
            log.info("DataUpdateInterceptor insertFill");
        } catch (Exception e) {
            log.error("DataUpdateInterceptor insertFill error:{}", e.getMessage());
        }
        if (StringUtils.isNotBlank(userName)) {
            this.setFieldValByName("createBy", userName, metaObject);
            this.setFieldValByName("updateBy", userName, metaObject);
        } else {
            this.setFieldValByName("createBy", userName, metaObject);
            this.setFieldValByName("updateBy", userName, metaObject);
        }
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("createBy", userName, metaObject);
    }
    /**
     * 修改数据执行
     *
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        String userName = "";
        try{
            //  获取登录信息
            userName = SecurityUtils.getLoginUser().getSysUser().getNickName();
            log.info("DataUpdateInterceptor insertFill");
        } catch (Exception e) {
            log.error("DataUpdateInterceptor insertFill error:{}", e.getMessage());
        }
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateBy", userName, metaObject);
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/constant/I18nKeyConstant.java
@@ -19,4 +19,10 @@
    String VEHICLE_OPERATION_INFO_INCOMPLETE = "vehicle.operation.info.incomplete";
    String VEHICLE_BATCH_OPERATION_CONDITION_NOT_MET = "vehicle.batch.operation.condition.not.met";
    String VEHICLE_TIME_EXPIRED = "vehicle.time.expired";
    // ========== 车队相关 ==========
    String FLEET_OPERATION_USER_BOUND_V1 = "fleet.operation.user.bound.v1";
    String FLEET_OPERATION_USER_BOUND_2 = "fleet.operation.user.bound.v2";
    String FLEET_VEHICLE_BOUND_V1 = "fleet.vehicle.bound.v1";
    String FLEET_VEHICLE_BOUND_V2 = "fleet.vehicle.bound.v2";
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetController.java
@@ -2,15 +2,21 @@
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.vehicleConvoy.model.dto.FleetAddDTO;
import com.xiaopeng.vehicleConvoy.model.dto.FleetEditDTO;
import com.xiaopeng.vehicleConvoy.model.query.FleetPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetUserPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetVehiclePageQuery;
import com.xiaopeng.vehicleConvoy.model.vo.*;
import com.xiaopeng.vehicleConvoy.service.IXpFleetService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@@ -22,12 +28,12 @@
 * @author mitao
 * @since 2026-02-03
 */
@RestController
@Api(tags = "车队管理")
@RequiredArgsConstructor(onConstructor_ = @Lazy)
@RestController
public class XpFleetController {
    @Resource
    private IXpFleetService xpFleetService;
    private final IXpFleetService xpFleetService;
    /**
     * 根据ID列表批量查询车队信息(内部接口)
@@ -43,4 +49,104 @@
        }
        return R.ok(xpFleetService.listByIds(ids));
    }
    /**
     * 添加车队
     *
     * @param dto 添加车队DTO
     * @return 操作结果
     */
    @ApiOperation(value = "添加车队", tags = {"管理后台-车队管理"}, notes = "打卡地列表接口在xp-robotaxi-maintenance-boot,人员列表在xp-robotaxi-account-boot")
    @PostMapping("/api/v1/fleet/add")
    public R<Void> add(@Valid @RequestBody FleetAddDTO dto) {
        return xpFleetService.add(dto);
    }
    /**
     * 编辑车队
     *
     * @param dto 编辑车队DTO
     * @return 操作结果
     */
    @ApiOperation(value = "编辑车队", tags = {"管理后台-车队管理"}, notes = "打卡地列表接口在xp-robotaxi-maintenance-boot,人员列表在xp-robotaxi-account-boot")
    @PostMapping("/api/v1/fleet/edit")
    public R<Void> edit(@Valid @RequestBody FleetEditDTO dto) {
        return xpFleetService.edit(dto);
    }
    /**
     * 查询车队详情
     *
     * @param id 车队id
     * @return 车队详情
     */
    @ApiOperation(value = "查询车队详情", tags = {"管理后台-车队管理"})
    @GetMapping("/api/v1/fleet/queryById")
    public R<FleetDetailVO> queryById(@RequestParam Long id) {
        return xpFleetService.queryById(id);
    }
    /**
     * 车队分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    @ApiOperation(value = "车队分页列表", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/fleet/pageList")
    public R<PageInfo<FleetPageVO>> pageList(@Valid @RequestBody FleetPageQuery query) {
        return xpFleetService.pageList(query);
    }
    /**
     * 查询车队车辆分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    @ApiOperation(value = "查询车队车辆分页列表", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/fleet/queryVehiclePageList")
    public R<PageInfo<FleetVehicleVO>> queryVehiclePageList(@Valid @RequestBody FleetVehiclePageQuery query) {
        return xpFleetService.queryVehiclePageList(query);
    }
    /**
     * 查询车队人员分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    @ApiOperation(value = "查询车队人员分页列表", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/fleet/queryUserPageList")
    public R<PageInfo<FleetUserVO>> queryUserPageList(@Valid @RequestBody FleetUserPageQuery query) {
        return xpFleetService.queryUserPageList(query);
    }
    @ApiOperation(value = "删除车队", tags = {"管理后台-车队管理"})
    @DeleteMapping("/api/v1/fleet/delete/{id}")
    public R<?> deleteById(@ApiParam(name = "id", value = "车队ID") @PathVariable Long id) {
        xpFleetService.deleteById(id);
        return R.ok();
    }
    @ApiOperation(value = "车队添加车辆成员", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/fleet/vehicle/add")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fleetId", value = "车队ID", dataTypeClass = Long.class),
            @ApiImplicitParam(name = "vehicleId", value = "车辆ID", dataTypeClass = Long.class)})
    public R<?> addVehicle(Long fleetId, Long vehicleId) {
        xpFleetService.addVehicle(fleetId, vehicleId);
        return R.ok();
    }
    @ApiOperation(value = "车队添加人员成员", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/fleet/operation-user/add")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fleetId", value = "车队ID", dataTypeClass = Long.class),
            @ApiImplicitParam(name = "operationUserId", value = "车辆ID", dataTypeClass = Long.class)})
    public R<?> addOperationUserId(Long fleetId, Long operationUserId) {
        xpFleetService.addOperationUserId(fleetId, operationUserId);
        return R.ok();
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetVehicleController.java
@@ -2,7 +2,6 @@
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
@@ -21,7 +20,6 @@
 * @since 2026-02-03
 */
@RestController
@Api(tags = "车队车辆管理")
public class XpFleetVehicleController {
    @Resource
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java
@@ -7,15 +7,15 @@
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.dto.operation.VehiclePositionByVinDto;
import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.feignClient.maintenance.XpChargingRecordClient;
import com.xiaopeng.api.feignClient.maintenance.XpVehiclePreparationClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicleAnnualRecord;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandCallbackVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
@@ -26,17 +26,16 @@
import com.xiaopeng.vehicleConvoy.model.exportExcel.XpVehicleExportExcel;
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -376,6 +375,38 @@
    }
    /**
     * 查询车辆位置信息 做筛选
     * @param dto
     * @return
     */
    @PostMapping("/inner/vehicle/queryVehiclePositionByVin")
    public R<List<String>> queryVehiclePositionByVin(@RequestBody VehiclePositionByVinDto dto){
        List<XpVehicle> list = xpVehicleService.list(new LambdaQueryWrapper<XpVehicle>().eq(XpVehicle::getState, 0).eq(XpVehicle::getStatus, 1).eq(XpVehicle::getElectronicId, dto.getFenceId()).in(XpVehicle::getVehicleVin,
                dto.getVinList()));
        if(list.isEmpty()){
            return R.ok(new ArrayList<>());
        }else {
            List<String> vehiclePositionVos = new ArrayList<>(list.size());
            for (XpVehicle xpVehicle : list) {
                vehiclePositionVos.add(xpVehicle.getVehicleVin());
            }
            return R.ok(vehiclePositionVos);
        }
    }
    /**
     * 查询车辆信息
     * @param vehicleVin
     * @return
     */
    @GetMapping("/inner/vehicle/findByVehicleVin")
    public R<XpVehicle> findByVehicleVin(String vehicleVin){
        XpVehicle xpVehicle = xpVehicleService.getOne(new LambdaQueryWrapper<XpVehicle>().eq(XpVehicle::getVehicleVin, vehicleVin));
        return R.ok(xpVehicle);
    }
    /**
     * 查询可改派的车辆列表
     *
     * @return 车辆列表
@@ -446,7 +477,7 @@
     * 业务流程-车门解锁/后备箱解锁
     */
    @ApiOperation(value = "打开车门操作", tags = {"小程序-业务流程"})
    @PostMapping("/api/v1/order/vehicle/unlock")
    @PostMapping("/api/v1/mini-program/order/vehicle/unlock")
    public R<Integer> unlock(@RequestBody UnlockAppletDTO dto) {
        return R.ok(xpVehicleService.unlock(dto));
    }
@@ -455,7 +486,7 @@
     * 业务流程-打开设置温度
     */
    @ApiOperation(value = "打开设置温度", tags = {"小程序-业务流程"})
    @PostMapping("/api/v1/order/ac/set-temperature")
    @PostMapping("/api/v1/mini-program/order/ac/set-temperature")
    public R<Integer> setTemperature(@RequestBody SetTemperatureDTO dto) {
        return R.ok(xpVehicleService.setTemperature(dto));
    }
@@ -464,11 +495,26 @@
     * 业务流程-通过订单id查询当前车辆温度
     */
    @ApiOperation(value = "通过订单id查询当前车辆温度", tags = {"小程序-业务流程"})
    @GetMapping("/api/v1/order/queryTemperatureByOrderId")
    @GetMapping("/api/v1/mini-program/order/queryTemperatureByOrderId")
    public R<VehicleTemperatureDto> queryTemperatureByOrderId(@RequestParam Long orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        VehicleTemperatureDto vehicleTemperatureDto = xpVehicleService.queryTemperatureByOrderId(orderId,uid);
        return R.ok(vehicleTemperatureDto);
    }
    /**
     * 获取车辆不分页列表
     *
     * @param plateNumber
     * @param fleetName
     * @return
     */
    @ApiOperation(value = "获取车辆不分页列表", tags = {"管理后台-车队管理"})
    @PostMapping("/api/v1/vehicle/list")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "plateNumber", value = "车牌号", dataTypeClass = String.class),
            @ApiImplicitParam(name = "fleetName", value = "车队名称", dataTypeClass = String.class)})
    public R<List<XpVehicleVO>> getList(String plateNumber, String fleetName) {
        return R.ok(xpVehicleService.getList(plateNumber, fleetName));
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/mapper/XpVehicleMapper.java
@@ -7,6 +7,7 @@
import com.xiaopeng.vehicleConvoy.model.query.VehicleExportQuery;
import com.xiaopeng.vehicleConvoy.model.query.VehicleMapQuery;
import com.xiaopeng.vehicleConvoy.model.query.VehiclePageQuery;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
@@ -79,4 +80,12 @@
     * @return 车辆列表
     */
    List<XpVehicle> vehicleExportList(@Param("query") VehicleExportQuery query);
    /**
     * 车辆不分页列表
     * @param plateNumber
     * @param fleetName
     * @return
     */
    List<XpVehicleVO> getList(@Param("plateNumber") String plateNumber, @Param("fleetName") String fleetName);
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/FleetAddDTO.java
New file
@@ -0,0 +1,40 @@
package com.xiaopeng.vehicleConvoy.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
 * 添加车队DTO
 *
 * @author mitao
 */
@Data
@ApiModel("添加车队数据传输对象")
public class FleetAddDTO {
    @ApiModelProperty(value = "车队名称", required = true)
    @NotBlank(message = "车队名称不能为空")
    private String fleetName;
    @ApiModelProperty(value = "负责人id", required = true)
    @NotNull(message = "负责人id不能为空")
    private Long operationsUserId;
    @ApiModelProperty(value = "打卡地id", required = true)
    @NotNull(message = "打卡地id不能为空")
    private Long locationId;
    @ApiModelProperty(value = "关联车辆ids", required = true)
    @NotEmpty(message = "关联车辆不能为空")
    private List<Long> vehicleIds;
    @ApiModelProperty(value = "关联人员ids", required = true)
    @NotEmpty(message = "关联人员不能为空")
    private List<Long> operationsUserIds;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/FleetEditDTO.java
New file
@@ -0,0 +1,33 @@
package com.xiaopeng.vehicleConvoy.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
 * 编辑车队DTO
 *
 * @author mitao
 */
@Data
@ApiModel("编辑车队数据传输对象")
public class FleetEditDTO {
    @ApiModelProperty(value = "车队id", required = true)
    @NotNull(message = "车队id不能为空")
    private Long id;
    @ApiModelProperty(value = "车队名称", required = true)
    @NotBlank(message = "车队名称不能为空")
    private String fleetName;
    @ApiModelProperty(value = "负责人id", required = true)
    @NotNull(message = "负责人id不能为空")
    private Long operationsUserId;
    @ApiModelProperty(value = "打卡地ID")
    private Long locationId;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetPageQuery.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.vehicleConvoy.model.query;
import com.xiaopeng.common.core.web.page.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 车队分页查询参数
 *
 * @author mitao
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "车队分页查询参数")
public class FleetPageQuery extends BasePage {
    @ApiModelProperty(value = "车队名称")
    private String fleetName;
    @ApiModelProperty(value = "负责人电话")
    private String phoneNumber;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetUserPageQuery.java
New file
@@ -0,0 +1,30 @@
package com.xiaopeng.vehicleConvoy.model.query;
import com.xiaopeng.common.core.web.page.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 车队人员分页查询参数
 *
 * @author mitao
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "车队人员分页查询参数")
public class FleetUserPageQuery extends BasePage {
    @ApiModelProperty(value = "车队id")
    private Long fleetId;
    @ApiModelProperty(value = "人员姓名")
    private String nickName;
    @ApiModelProperty(value = "电话")
    private String phone;
    @ApiModelProperty(value = "角色ID")
    private Long roleId;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/query/FleetVehiclePageQuery.java
New file
@@ -0,0 +1,27 @@
package com.xiaopeng.vehicleConvoy.model.query;
import com.xiaopeng.common.core.web.page.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
 * 车队车辆分页查询参数
 *
 * @author mitao
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "车队车辆分页查询参数")
public class FleetVehiclePageQuery extends BasePage {
    @ApiModelProperty(value = "车队id", required = true)
    @NotNull(message = "车队id不能为空")
    private Long fleetId;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetDetailVO.java
New file
@@ -0,0 +1,53 @@
package com.xiaopeng.vehicleConvoy.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * 车队详情视图对象
 *
 * @author mitao
 */
@Data
@ApiModel("车队详情视图对象")
public class FleetDetailVO {
    @ApiModelProperty(value = "车队id")
    private Long id;
    @ApiModelProperty(value = "车队名称")
    private String fleetName;
    @ApiModelProperty(value = "负责人id")
    private Long operationsUserId;
    @ApiModelProperty(value = "负责人姓名")
    private String operationsUserName;
    @ApiModelProperty(value = "打卡地id")
    private Long locationId;
    @ApiModelProperty(value = "打卡地名称")
    private String locationName;
    @ApiModelProperty(value = "电子围栏id")
    private Long electronicId;
    @ApiModelProperty(value = "人员数量")
    private Integer userCount;
    @ApiModelProperty(value = "车辆数量")
    private Integer vehicleCount;
    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetPageVO.java
New file
@@ -0,0 +1,46 @@
package com.xiaopeng.vehicleConvoy.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * 车队分页列表视图对象
 *
 * @author mitao
 */
@Data
@ApiModel("车队分页列表视图对象")
public class FleetPageVO {
    @ApiModelProperty(value = "车队id")
    private Long id;
    @ApiModelProperty(value = "车队名称")
    private String fleetName;
    @ApiModelProperty(value = "负责人id")
    private Long operationsUserId;
    @ApiModelProperty(value = "负责人姓名")
    private String operationsUserName;
    @ApiModelProperty(value = "关联运营区域")
    private String electronicName;
    @ApiModelProperty(value = "负责人电话")
    private String phoneNumber;
    @ApiModelProperty(value = "人员数量")
    private Integer userCount;
    @ApiModelProperty(value = "车辆数量")
    private Integer vehicleCount;
    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetUserVO.java
New file
@@ -0,0 +1,30 @@
package com.xiaopeng.vehicleConvoy.model.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 车队人员视图对象
 *
 * @author mitao
 */
@Data
@ApiModel("车队人员视图对象")
public class FleetUserVO {
    @ApiModelProperty(value = "人员id")
    private Long userId;
    @ApiModelProperty(value = "人员姓名")
    private String nickName;
    @ApiModelProperty(value = "电话")
    private String phoneNumber;
    @ApiModelProperty(value = "工号")
    private String jobNumber;
    @ApiModelProperty(value = "角色名称")
    private String roleName;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/FleetVehicleVO.java
New file
@@ -0,0 +1,36 @@
package com.xiaopeng.vehicleConvoy.model.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 车队车辆视图对象
 *
 * @author mitao
 */
@Data
@ApiModel("车队车辆视图对象")
public class FleetVehicleVO {
    @ApiModelProperty(value = "车辆id")
    private Long vehicleId;
    @ApiModelProperty(value = "车辆编码")
    private String vehicleCode;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
    @ApiModelProperty(value = "车辆颜色")
    private String vehicleColor;
    @ApiModelProperty(value = "车辆型号")
    private String vehicleModel;
    @ApiModelProperty(value = "运营状态")
    private Integer operatingStatus;
    @ApiModelProperty(value = "运营状态描述")
    private String operatingStatusDesc;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/vo/XpVehicleVO.java
New file
@@ -0,0 +1,21 @@
package com.xiaopeng.vehicleConvoy.model.vo;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * @author MiTao
 * @date 2026-02-12 21:36
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("车辆视图对象")
public class XpVehicleVO extends XpVehicle {
    @ApiModelProperty(value = "车队名称")
    private String fleetName;
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpFleetService.java
@@ -1,7 +1,15 @@
package com.xiaopeng.vehicleConvoy.service;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.vehicleConvoy.model.dto.FleetAddDTO;
import com.xiaopeng.vehicleConvoy.model.dto.FleetEditDTO;
import com.xiaopeng.vehicleConvoy.model.query.FleetPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetUserPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetVehiclePageQuery;
import com.xiaopeng.vehicleConvoy.model.vo.*;
import java.util.List;
@@ -22,4 +30,74 @@
     * @return 车队列表
     */
    List<XpFleet> listByIds(List<Long> ids);
    /**
     * 添加车队
     *
     * @param dto 添加车队DTO
     * @return 操作结果
     */
    R<Void> add(FleetAddDTO dto);
    /**
     * 编辑车队
     *
     * @param dto 编辑车队DTO
     * @return 操作结果
     */
    R<Void> edit(FleetEditDTO dto);
    /**
     * 查询车队详情
     *
     * @param id 车队id
     * @return 车队详情
     */
    R<FleetDetailVO> queryById(Long id);
    /**
     * 车队分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    R<PageInfo<FleetPageVO>> pageList(FleetPageQuery query);
    /**
     * 查询车队车辆分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    R<PageInfo<FleetVehicleVO>> queryVehiclePageList(FleetVehiclePageQuery query);
    /**
     * 查询车队人员分页列表
     *
     * @param query 查询参数
     * @return 分页结果
     */
    R<PageInfo<FleetUserVO>> queryUserPageList(FleetUserPageQuery query);
    /**
     * 根据ID删除车队
     * @param id
     */
    void deleteById(Long id);
    /**
     * 车队添加车辆成员
     * @param fleetId
     * @param vehicleId
     */
    void addVehicle(Long fleetId, Long vehicleId);
    /**
     * 车队添加人员成员
     *
     * @param fleetId
     * @param operationUserId
     */
    void addOperationUserId(Long fleetId, Long operationUserId);
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpVehicleService.java
@@ -3,11 +3,11 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.vehicleConvoy.model.dto.*;
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import java.util.List;
@@ -166,4 +166,13 @@
     * @return 车辆温度
     */
    VehicleTemperatureDto queryTemperatureByOrderId(Long orderId, String uid);
    /**
     * 车辆不分页列表
     *
     * @param plateNumber
     * @param fleetName
     * @return
     */
    List<XpVehicleVO> getList(String plateNumber, String fleetName);
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetServiceImpl.java
@@ -1,14 +1,46 @@
package com.xiaopeng.vehicleConvoy.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.operation.XpElectronicClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpFleetOperations;
import com.xiaopeng.api.model.vehicleConvoy.XpFleetVehicle;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.vehicleConvoy.constant.I18nKeyConstant;
import com.xiaopeng.vehicleConvoy.mapper.XpFleetMapper;
import com.xiaopeng.vehicleConvoy.model.dto.FleetAddDTO;
import com.xiaopeng.vehicleConvoy.model.dto.FleetEditDTO;
import com.xiaopeng.vehicleConvoy.model.query.FleetPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetUserPageQuery;
import com.xiaopeng.vehicleConvoy.model.query.FleetVehiclePageQuery;
import com.xiaopeng.vehicleConvoy.model.vo.FleetDetailVO;
import com.xiaopeng.vehicleConvoy.model.vo.FleetPageVO;
import com.xiaopeng.vehicleConvoy.model.vo.FleetUserVO;
import com.xiaopeng.vehicleConvoy.model.vo.FleetVehicleVO;
import com.xiaopeng.vehicleConvoy.service.IXpFleetOperationsService;
import com.xiaopeng.vehicleConvoy.service.IXpFleetService;
import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
 * <p>
@@ -19,7 +51,18 @@
 * @since 2026-02-03
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpFleetServiceImpl extends ServiceImpl<XpFleetMapper, XpFleet> implements IXpFleetService {
    private final IXpFleetOperationsService xpFleetOperationsService;
    private final IXpFleetVehicleService xpFleetVehicleService;
    private final XpOperationUserClient xpOperationUserClient;
    private final XpVehicleClient xpVehicleClient;
    private final XpElectronicClient xpElectronicClient;
    @Override
    public List<XpFleet> listByIds(List<Long> ids) {
@@ -28,4 +71,436 @@
        }
        return super.listByIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Void> add(FleetAddDTO dto) {
        // 1. 校验车队名称不能重复
        long count = this.count(new LambdaQueryWrapper<XpFleet>()
                .eq(XpFleet::getFleetName, dto.getFleetName()));
        if (count > 0) {
            throw new ServiceException("车队名称已存在");
        }
        // 2. 校验运维人员绑定
        validateUserBinding(dto.getOperationsUserIds(), null);
        // 3. 处理车辆绑定(自动解绑已绑定的车辆)
        handleVehicleBinding(dto.getVehicleIds(), null);
        // 4. 插入车队主表记录
        XpFleet fleet = new XpFleet();
        fleet.setFleetName(dto.getFleetName());
        fleet.setOperationsUserId(dto.getOperationsUserId());
        fleet.setLocationId(dto.getLocationId());
        this.save(fleet);
        // 5. 批量插入车队-人员关联表
        List<XpFleetOperations> operationsList = dto.getOperationsUserIds().stream()
                .map(userId -> {
                    XpFleetOperations operations = new XpFleetOperations();
                    operations.setFleetId(fleet.getId());
                    operations.setOperationsUserId(userId);
                    return operations;
                }).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(operationsList)) {
            xpFleetOperationsService.saveBatch(operationsList);
        }
        // 6. 批量插入车队-车辆关联表
        List<XpFleetVehicle> vehicleList = dto.getVehicleIds().stream()
                .map(vehicleId -> {
                    XpFleetVehicle fleetVehicle = new XpFleetVehicle();
                    fleetVehicle.setFleetId(fleet.getId());
                    fleetVehicle.setVehicleId(vehicleId);
                    return fleetVehicle;
                }).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(vehicleList)) {
            xpFleetVehicleService.saveBatch(vehicleList);
        }
        return R.ok();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Void> edit(FleetEditDTO dto) {
        // 1. 查询车队是否存在
        XpFleet fleet = this.getById(dto.getId());
        if (fleet == null) {
            throw new ServiceException("车队不存在");
        }
        // 2. 校验车队名称不能与其他车队重复
        long count = this.count(new LambdaQueryWrapper<XpFleet>()
                .eq(XpFleet::getFleetName, dto.getFleetName())
                .ne(XpFleet::getId, dto.getId()));
        if (count > 0) {
            throw new ServiceException("车队名称已存在");
        }
        // 3. 校验负责人绑定(如果修改了负责人)
        if (!dto.getOperationsUserId().equals(fleet.getOperationsUserId())) {
            List<Long> userIds = new ArrayList<>();
            userIds.add(dto.getOperationsUserId());
            validateUserBinding(userIds, dto.getId());
        }
        // 4. 更新车队主表记录
        fleet.setFleetName(dto.getFleetName());
        fleet.setOperationsUserId(dto.getOperationsUserId());
        fleet.setLocationId(dto.getLocationId());
        this.updateById(fleet);
        return R.ok();
    }
    /**
     * 校验人员绑定
     *
     * @param userIds        人员ID列表
     * @param excludeFleetId 排除的车队ID(编辑时使用)
     */
    private void validateUserBinding(List<Long> userIds, Long excludeFleetId) {
        if (CollectionUtils.isEmpty(userIds)) {
            return;
        }
        for (Long userId : userIds) {
            LambdaQueryWrapper<XpFleetOperations> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(XpFleetOperations::getOperationsUserId, userId);
            if (excludeFleetId != null) {
                wrapper.ne(XpFleetOperations::getFleetId, excludeFleetId);
            }
            XpFleetOperations operations = xpFleetOperationsService.getOne(wrapper);
            if (operations != null) {
                // 查询人员姓名和车队名称
                R<XpOperationsUser> userResult = xpOperationUserClient.queryById(userId);
                XpFleet fleet = this.getById(operations.getFleetId());
                String userName = userResult.getData() != null ? userResult.getData().getNickName() : "未知";
                String fleetName = fleet != null ? fleet.getFleetName() : "未知";
                List<Object> argsList = new ArrayList<>();
                argsList.add(userName);
                argsList.add(fleetName);
                Object[] args = argsList.toArray();
                throw new ServiceException("运维人员" + userName + "已绑定车队" + fleetName, I18nKeyConstant.FLEET_OPERATION_USER_BOUND_V1, args);
            }
        }
    }
    /**
     * 处理车辆绑定(自动解绑已绑定的车辆)
     *
     * @param vehicleIds     车辆ID列表
     * @param excludeFleetId 排除的车队ID(编辑时使用)
     */
    private void handleVehicleBinding(List<Long> vehicleIds, Long excludeFleetId) {
        if (CollectionUtils.isEmpty(vehicleIds)) {
            return;
        }
        for (Long vehicleId : vehicleIds) {
            LambdaQueryWrapper<XpFleetVehicle> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(XpFleetVehicle::getVehicleId, vehicleId);
            if (excludeFleetId != null) {
                wrapper.ne(XpFleetVehicle::getFleetId, excludeFleetId);
            }
            XpFleetVehicle fleetVehicle = xpFleetVehicleService.getOne(wrapper);
            if (fleetVehicle != null) {
                // 车辆已绑定其他车队,自动解绑
                xpFleetVehicleService.lambdaUpdate().set(XpFleetVehicle::getVehicleId, fleetVehicle.getVehicleId()).remove();
            }
        }
    }
    @Override
    public R<FleetDetailVO> queryById(Long id) {
        // 1. 查询车队主表
        XpFleet fleet = this.getById(id);
        if (fleet == null) {
            throw new ServiceException("车队不存在");
        }
        // 2. 组装VO
        FleetDetailVO vo = new FleetDetailVO();
        BeanUtils.copyProperties(fleet, vo);
        // 3. 统计人员数量
        long userCount = xpFleetOperationsService.count(new LambdaQueryWrapper<XpFleetOperations>()
                .eq(XpFleetOperations::getFleetId, id));
        vo.setUserCount((int) userCount);
        // 4. 统计车辆数量
        long vehicleCount = xpFleetVehicleService.count(new LambdaQueryWrapper<XpFleetVehicle>()
                .eq(XpFleetVehicle::getFleetId, id));
        vo.setVehicleCount((int) vehicleCount);
        // 5. 查询负责人信息
        if (fleet.getOperationsUserId() != null) {
            R<XpOperationsUser> userResult = xpOperationUserClient.queryById(fleet.getOperationsUserId());
            if (userResult.getData() != null) {
                vo.setOperationsUserName(userResult.getData().getNickName());
            }
        }
        return R.ok(vo);
    }
    @Override
    public R<PageInfo<FleetPageVO>> pageList(FleetPageQuery query) {
        // 1. 构建查询条件
        LambdaQueryWrapper<XpFleet> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(query.getFleetName())) {
            wrapper.like(XpFleet::getFleetName, query.getFleetName());
        }
        wrapper.orderByDesc(XpFleet::getCreateTime);
        // 2. 执行分页查询
        Page<XpFleet> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpFleet> resultPage = this.page(page, wrapper);
        // 3. 收集所有需要查询的ID
        List<Long> userIds = resultPage.getRecords().stream()
                .map(XpFleet::getOperationsUserId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        List<Long> electronicIds = resultPage.getRecords().stream()
                .map(XpFleet::getElectronicId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        // 4. 批量查询负责人信息并转换为Map
        Map<Long, XpOperationsUser> userMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(userIds)) {
            R<List<XpOperationsUser>> usersResult = xpOperationUserClient.listByIds(userIds);
            if (usersResult.getData() != null) {
                userMap = usersResult.getData().stream()
                        .collect(Collectors.toMap(XpOperationsUser::getId, user -> user));
            }
        }
        // 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));
            }
        }
        // 6. 转换为VO并填充信息
        List<FleetPageVO> records = new ArrayList<>();
        for (XpFleet fleet : resultPage.getRecords()) {
            FleetPageVO vo = new FleetPageVO();
            BeanUtils.copyProperties(fleet, vo);
            // 统计人员数量
            long userCount = xpFleetOperationsService.count(new LambdaQueryWrapper<XpFleetOperations>()
                    .eq(XpFleetOperations::getFleetId, fleet.getId()));
            vo.setUserCount((int) userCount);
            // 统计车辆数量
            long vehicleCount = xpFleetVehicleService.count(new LambdaQueryWrapper<XpFleetVehicle>()
                    .eq(XpFleetVehicle::getFleetId, fleet.getId()));
            vo.setVehicleCount((int) vehicleCount);
            // 从Map中获取负责人信息
            if (fleet.getOperationsUserId() != null) {
                XpOperationsUser user = userMap.get(fleet.getOperationsUserId());
                if (user != null) {
                    vo.setOperationsUserName(user.getNickName());
                    vo.setPhoneNumber(user.getPhoneNumber());
                }
            }
            // 从Map中获取围栏名称
            if (fleet.getElectronicId() != null) {
                XpElectronicFence fence = fenceMap.get(fleet.getElectronicId());
                if (fence != null) {
                    vo.setElectronicName(fence.getElectronicName());
                }
            }
            records.add(vo);
        }
        // 7. 构建分页结果
        PageInfo<FleetPageVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        pageInfo.setRecords(records);
        pageInfo.setTotal(resultPage.getTotal());
        return R.ok(pageInfo);
    }
    @Override
    public R<PageInfo<FleetVehicleVO>> queryVehiclePageList(FleetVehiclePageQuery query) {
        // 1. 查询车队-车辆关联表
        LambdaQueryWrapper<XpFleetVehicle> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(XpFleetVehicle::getFleetId, query.getFleetId());
        Page<XpFleetVehicle> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpFleetVehicle> resultPage = xpFleetVehicleService.page(page, wrapper);
        // 2. 获取车辆ID列表
        List<Long> vehicleIds = resultPage.getRecords().stream()
                .map(XpFleetVehicle::getVehicleId)
                .collect(Collectors.toList());
        // 3. 批量查询车辆信息
        List<FleetVehicleVO> records = new ArrayList<>();
        if (!CollectionUtils.isEmpty(vehicleIds)) {
            R<List<XpVehicle>> vehiclesResult = xpVehicleClient.listByIds(vehicleIds);
            if (vehiclesResult.getData() != null) {
                for (XpVehicle vehicle : vehiclesResult.getData()) {
                    // 如果有车牌号筛选条件,进行过滤
                    if (StringUtils.isNotBlank(query.getPlateNumber()) &&
                            !vehicle.getPlateNumber().contains(query.getPlateNumber())) {
                        continue;
                    }
                    FleetVehicleVO vo = new FleetVehicleVO();
                    vo.setVehicleId(vehicle.getId());
                    vo.setVehicleCode(vehicle.getVehicleCode());
                    vo.setPlateNumber(vehicle.getPlateNumber());
                    vo.setVehicleColor(vehicle.getVehicleColor());
                    vo.setVehicleModel(vehicle.getVehicleModel());
                    vo.setOperatingStatus(vehicle.getOperatingStatus());
                    records.add(vo);
                }
            }
        }
        // 4. 构建分页结果
        PageInfo<FleetVehicleVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        pageInfo.setRecords(records);
        pageInfo.setTotal(resultPage.getTotal());
        return R.ok(pageInfo);
    }
    @Override
    public R<PageInfo<FleetUserVO>> queryUserPageList(FleetUserPageQuery query) {
        // 1. 先根据条件查询符合条件的运维用户
        List<Long> filteredUserIds = new ArrayList<>();
        if (StringUtils.isNotBlank(query.getNickName()) ||
                StringUtils.isNotBlank(query.getPhone()) ||
                query.getRoleId() != null) {
            // 构建查询条件
            XpOperationsUserQueryDTO queryDTO = new XpOperationsUserQueryDTO();
            queryDTO.setNickName(query.getNickName());
            queryDTO.setPhoneNumber(query.getPhone());
            queryDTO.setRoleId(query.getRoleId());
            // 远程查询符合条件的用户
            R<List<XpOperationsUserWithRoleVO>> usersResult = xpOperationUserClient.queryByConditions(queryDTO);
            if (usersResult.getData() == null || usersResult.getData().isEmpty()) {
                // 没有符合条件的用户,直接返回空结果
                PageInfo<FleetUserVO> emptyPageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
                emptyPageInfo.setRecords(new ArrayList<>());
                emptyPageInfo.setTotal(0L);
                return R.ok(emptyPageInfo);
            }
            // 收集用户ID列表
            filteredUserIds = usersResult.getData().stream()
                    .map(XpOperationsUserWithRoleVO::getId)
                    .collect(Collectors.toList());
        }
        // 2. 查询车队-人员关联表(使用用户ID列表作为条件)
        LambdaQueryWrapper<XpFleetOperations> wrapper = new LambdaQueryWrapper<>();
        if (query.getFleetId() != null) {
            wrapper.eq(XpFleetOperations::getFleetId, query.getFleetId());
        }
        // 如果有用户ID过滤条件,添加到查询中
        if (!CollectionUtils.isEmpty(filteredUserIds)) {
            wrapper.in(XpFleetOperations::getOperationsUserId, filteredUserIds);
        }
        Page<XpFleetOperations> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpFleetOperations> resultPage = xpFleetOperationsService.page(page, wrapper);
        // 3. 获取人员ID列表
        List<Long> userIds = resultPage.getRecords().stream()
                .map(XpFleetOperations::getOperationsUserId)
                .collect(Collectors.toList());
        // 4. 批量查询人员及角色信息
        List<FleetUserVO> records = new ArrayList<>();
        if (!CollectionUtils.isEmpty(userIds)) {
            R<List<XpOperationsUserWithRoleVO>> usersResult = xpOperationUserClient.listByIdsWithRole(userIds);
            if (usersResult.getData() != null) {
                for (XpOperationsUserWithRoleVO user : usersResult.getData()) {
                    FleetUserVO vo = new FleetUserVO();
                    vo.setUserId(user.getId());
                    vo.setNickName(user.getNickName());
                    vo.setPhoneNumber(user.getPhoneNumber());
                    vo.setJobNumber(user.getUniqueNumber());
                    vo.setRoleName(user.getRoleName());
                    records.add(vo);
                }
            }
        }
        // 5. 构建分页结果
        PageInfo<FleetUserVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        pageInfo.setRecords(records);
        pageInfo.setTotal(resultPage.getTotal());
        return R.ok(pageInfo);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(Long id) {
        // 删除用户关联
        xpFleetOperationsService.lambdaUpdate().eq(XpFleetOperations::getFleetId, id).remove();
        // 删除车辆关联
        xpFleetVehicleService.lambdaUpdate().eq(XpFleetVehicle::getFleetId, id).remove();
        // 删除车队
        removeById(id);
    }
    @Override
    public void addVehicle(Long fleetId, Long vehicleId) {
        // 1. 检查是否已关联当前车队
        Long count = xpFleetVehicleService.lambdaQuery().eq(XpFleetVehicle::getFleetId, fleetId)
                .eq(XpFleetVehicle::getVehicleId, vehicleId).count();
        if (count > 0) {
            throw new ServiceException("当前车队已和该车辆关联", I18nKeyConstant.FLEET_VEHICLE_BOUND_V2);
        }
        // 2. 检查是否已绑定其他车队,如果是则自动解绑
        XpFleetVehicle existingBinding = xpFleetVehicleService.lambdaQuery()
                .eq(XpFleetVehicle::getVehicleId, vehicleId)
                .ne(XpFleetVehicle::getFleetId, fleetId)
                .one();
        if (existingBinding != null) {
            // 车辆已绑定其他车队,自动解绑
            xpFleetVehicleService.lambdaUpdate().eq(XpFleetVehicle::getVehicleId, vehicleId).remove();
        }
        // 3. 保存新的关联关系
        XpFleetVehicle xpFleetVehicle = new XpFleetVehicle();
        xpFleetVehicle.setFleetId(fleetId);
        xpFleetVehicle.setVehicleId(vehicleId);
        xpFleetVehicleService.save(xpFleetVehicle);
    }
    @Override
    public void addOperationUserId(Long fleetId, Long operationUserId) {
        // 查询是否已关联
        Long count = xpFleetOperationsService.lambdaQuery().eq(XpFleetOperations::getFleetId, fleetId)
                .eq(XpFleetOperations::getOperationsUserId, operationUserId).count();
        if (count > 0) {
            throw new ServiceException("当前车队已和该人员关联", I18nKeyConstant.FLEET_OPERATION_USER_BOUND_2);
        }
        //保存关联
        XpFleetOperations operations = new XpFleetOperations();
        operations.setFleetId(fleetId);
        operations.setOperationsUserId(operationUserId);
        xpFleetOperationsService.save(operations);
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -13,7 +13,6 @@
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicleAnnualRecord;
import com.xiaopeng.api.vo.operation.ElectronicFenceDetailVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandEnum;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandParamVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo;
@@ -26,6 +25,7 @@
import com.xiaopeng.vehicleConvoy.model.dto.*;
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleService;
import com.xiaopeng.vehicleConvoy.util.VehicleControlUtil;
@@ -37,7 +37,6 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
@@ -601,7 +600,7 @@
            throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS);
        }
        try{
            VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(Long.valueOf(uid), xpVehicle.getVehicleVin());
            VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(uid, xpVehicle.getVehicleVin());
            log.info("查询车辆温度成功:{}",vehicleTemperatureDto);
            return vehicleTemperatureDto;
        }catch (Exception e){
@@ -609,4 +608,9 @@
            return new VehicleTemperatureDto();
        }
    }
    @Override
    public List<XpVehicleVO> getList(String plateNumber, String fleetName) {
        return baseMapper.getList(plateNumber, fleetName);
    }
}
xp-robotaxi-vehicleConvoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/VehicleControlUtil.java
@@ -46,7 +46,7 @@
     * 测试车辆:L1NNSGHA5PB000204
     * @return
     */
    public VehicleTemperatureDto queryVehicleTemperature(Long uid, String vin) {
    public VehicleTemperatureDto queryVehicleTemperature(String uid, String vin) {
        String nonce = System.currentTimeMillis() + "";
        VehicleTemperatureVo vo = new VehicleTemperatureVo();
        vo.setVin(vin);
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages.properties
@@ -1,5 +1,9 @@
# 中文
fleet.operation.user.bound.v2=当前车队已和该人员关联
policy.application.audit.ids.notEmpty=保单申请ID列表不能为空
fleet.user.bound=运维人员[{0}]已绑定车队[{1}]
fleet.vehicle.bound.v2=当前车队已和该车辆关联
fleet.vehicle.bound.v1=车辆[{0}]已绑定车队[{1}]
policy.application.audit.status.notNull=保单审核状态不能为空
welcome.message=欢迎来到我们的应用程序!
# 车辆管理
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages_en_US.properties
@@ -1,5 +1,8 @@
# English (US)
fleet.operation.user.bound.v2=The current fleet has been associated with this person
policy.application.audit.ids.notEmpty=The list of policy application ids cannot be empty
fleet.operation.user.bound.v1=Operation user [{0}] already bound fleet:[{1}]
fleet.vehicle.bound.v2=The current team has been associated with this vehicle.
policy.application.audit.status.notNull=The review status of the insurance policy cannot be empty
welcome.message=Welcome to our application!
order.not.exists=No valid order information found
xp-robotaxi-vehicleConvoy-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -1,5 +1,8 @@
# 中文
fleet.operation.user.bound.v2=当前车队已和该人员关联
policy.application.audit.ids.notEmpty=保单申请ID列表不能为空
fleet.user.bound=运维人员[{0}]已绑定车队[{1}]
fleet.vehicle.bound.v2=当前车队已和该车辆关联
policy.application.audit.status.notNull=保单审核状态不能为空
welcome.message=欢迎来到我们的应用程序!
order.not.exists=未查询到有效订单信息
xp-robotaxi-vehicleConvoy-boot/src/main/resources/mapper/vehicleConvoy/XpVehicleMapper.xml
@@ -108,5 +108,52 @@
        </where>
        ORDER BY create_time DESC
    </select>
    <select id="getList" resultType="com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO">
        select xv.id,
        xv.vehicle_code,
        xv.vehicle_vin,
        xv.plate_number,
        xv.vehicle_model,
        xv.vehicle_color,
        xv.vehicle_pictures,
        xv.procurement_source,
        xv.remarks,
        xv.annual_inspection_date,
        xv.insurance_company,
        xv.insurance_phone,
        xv.insurance_end_time,
        xv.inspection_attachments,
        xv.policy_information,
        xv.electronic_id,
        xv.operation_qualification,
        xv.qualification_expire_time,
        xv.state,
        xv.status,
        xv.operating_status,
        xv.warehousing_time,
        xv.vehicle_type,
        xv.accept_or_test,
        xv.accept_or_test_status,
        xv.current_electricity,
        xv.is_deleted,
        xv.create_time,
        xv.update_time,
        xv.create_by,
        xv.update_by,
        xf.fleet_name
        from xp_vehicle xv
        left join xp_fleet_vehicle xfv on xv.id = xfv.vehicle_id
        left join xp_fleet xf on xfv.fleet_id = xf.id
        <where>
            xv.is_deleted = 0
            <if test="plateNumber != null and plateNumber != ''">
                and xv.plate_number like concat('%', #{plateNumber}, '%')
            </if>
            <if test="fleetName != null and fleetName != ''">
                and xf.fleet_name like concat('%', #{fleetName}, '%')
            </if>
        </where>
        order by xv.create_time desc
    </select>
</mapper>