liujie
2026-02-26 3c3ee4ac31a3a6c72aec6e2a78604a2703f3d55c
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi
7个文件已添加
19个文件已修改
1004 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpFleetVehicleClientFallbackFactory.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpFleetVehicleClient.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpCruiseOrder.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/AuditStateEnum.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpCruisePlanController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/XpCruisePlanAddDTO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/XpCruisePlanEditDTO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpCruisePlanService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpCruisePlanServiceImpl.java 319 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanDetailVO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanPointVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanVehicleVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpCruiseOrderController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/query/XpCruiseOrderPageQuery.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpCruiseOrderPageVO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpCruiseOrderService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpCruiseOrderServiceImpl.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetVehicleController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpFleetVehicleService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetVehicleServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpPolicyApplicationServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpFleetVehicleClientFallbackFactory.java
@@ -6,6 +6,9 @@
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
 * 车队车辆Feign客户端降级工厂
 *
@@ -25,6 +28,12 @@
                // 降级返回null,让调用方自行处理
                return R.ok(null);
            }
            @Override
            public R<List<Long>> listVehicleIdsByFleetId(Long fleetId) {
                log.error("根据车队ID查询车辆ID列表失败,车队ID: {},原因: {}", fleetId, cause.getMessage());
                return R.ok(Collections.emptyList());
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpFleetVehicleClient.java
@@ -7,6 +7,8 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
 * 车队车辆Feign客户端
 *
@@ -25,4 +27,13 @@
     */
    @GetMapping("/inner/fleet-vehicle/getFleetIdByVehicleId/{vehicleId}")
    R<Long> getFleetIdByVehicleId(@PathVariable("vehicleId") Long vehicleId);
    /**
     * 根据车队ID查询车辆ID列表
     *
     * @param fleetId 车队ID
     * @return 车辆ID列表
     */
    @GetMapping("/inner/fleet-vehicle/listVehicleIdsByFleetId/{fleetId}")
    R<List<Long>> listVehicleIdsByFleetId(@PathVariable("fleetId") Long fleetId);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpCruiseOrder.java
@@ -81,4 +81,8 @@
    @ApiModelProperty(value = "返程上次的订单id")
    @TableField("last_order_id")
    private String lastOrderId;
    @ApiModelProperty(value = "订单号")
    @TableField("order_number")
    private String orderNumber;
}
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/AuditStateEnum.java
@@ -11,10 +11,10 @@
    /*审核状态 0待审批 1通过 2驳回 3已撤回*/
    PENDING_APPROVAL(0, "待审核"),
    APPROVED(1, "已通过"),
    APPROVAL_REJECTION(2, "已驳回"),
    WITHDRAWN(3, "已撤回");
    PENDING_APPROVAL(1, "待审核"),
    APPROVED(2, "已通过"),
    APPROVAL_REJECTION(3, "已驳回"),
    WITHDRAWN(4, "已撤回");
    @Getter
    private String desc;
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java
@@ -56,4 +56,33 @@
    // 存在进行中的订单
    String HAS_UNPAID_ORDER = "has.unpaid.order";
    // 巡游计划名称不能为空
    String CRUISE_PLAN_NAME_EMPTY = "cruise.plan.name.empty";
    // 巡游计划ID不能为空
    String CRUISE_PLAN_ID_EMPTY = "cruise.plan.id.empty";
    // 巡游计划名称已存在
    String CRUISE_PLAN_NAME_EXISTS = "cruise.plan.name.exists";
    // 巡游计划不存在
    String CRUISE_PLAN_NOT_EXIST = "cruise.plan.not.exist";
    // 巡游计划点位列表不能为空
    String CRUISE_PLAN_POINT_EMPTY = "cruise.plan.point.empty";
    // 巡游计划车辆列表不能为空
    String CRUISE_PLAN_VEHICLE_EMPTY = "cruise.plan.vehicle.empty";
    // 巡游计划点位参数不完整
    String CRUISE_PLAN_POINT_INCOMPLETE = "cruise.plan.point.incomplete";
    // 巡游计划点位停车标识不合法
    String CRUISE_PLAN_STOP_FLAG_INVALID = "cruise.plan.stop.flag.invalid";
    // 巡游计划点位ID重复
    String CRUISE_PLAN_POINT_DUPLICATE = "cruise.plan.point.duplicate";
    // 巡游计划车辆参数不完整
    String CRUISE_PLAN_VEHICLE_INCOMPLETE = "cruise.plan.vehicle.incomplete";
    // 巡游计划车辆ID重复
    String CRUISE_PLAN_VEHICLE_DUPLICATE = "cruise.plan.vehicle.duplicate";
    // 巡游计划车辆已绑定其他计划
    String CRUISE_PLAN_VEHICLE_ALREADY_BOUND = "cruise.plan.vehicle.already.bound";
    // 新增巡游计划失败
    String CRUISE_PLAN_ADD_FAIL = "cruise.plan.add.fail";
    // 编辑巡游计划失败
    String CRUISE_PLAN_EDIT_FAIL = "cruise.plan.edit.fail";
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpCruisePlanController.java
@@ -4,16 +4,27 @@
import com.xiaopeng.api.model.operation.XpCruisePlan;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.dto.XpCruisePlanAddDTO;
import com.xiaopeng.operation.dto.XpCruisePlanEditDTO;
import com.xiaopeng.operation.query.CruisePlanQuery;
import com.xiaopeng.operation.service.IXpCruisePlanService;
import com.xiaopeng.operation.vo.XpCruisePlanDetailVO;
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.DeleteMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import io.swagger.annotations.ApiParam;
/**
 * <p>
@@ -23,6 +34,7 @@
 * @author mitao
 * @since 2026-02-26
 */
@Validated
@RestController
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCruisePlanController {
@@ -34,5 +46,32 @@
        return R.ok(xpCruisePlanService.getPageList(query));
    }
    @ApiOperation(value = "新增巡游计划", tags = {"管理后台-巡游计划"})
    @PostMapping(value = {"/api/v1/xp-cruise-plan/add"})
    public R<?> add(@Valid @RequestBody XpCruisePlanAddDTO dto){
        xpCruisePlanService.add(dto);
        return R.ok();
    }
    @ApiOperation(value = "编辑巡游计划", tags = {"管理后台-巡游计划"})
    @PostMapping(value = {"/api/v1/xp-cruise-plan/edit"})
    public R<?> edit(@Valid @RequestBody XpCruisePlanEditDTO dto){
        xpCruisePlanService.edit(dto);
        return R.ok();
    }
    @ApiOperation(value = "删除巡游计划", tags = {"管理后台-巡游计划"})
    @DeleteMapping(value = {"/api/v1/xp-cruise-plan/deleteById/{id}"})
    public R<?> deleteById(@ApiParam(name = "id", value = "巡游计划ID") @PathVariable Long id){
        xpCruisePlanService.deleteById(id);
        return R.ok();
    }
    @ApiOperation(value = "巡游计划详情", tags = {"管理后台-巡游计划"})
    @GetMapping(value = {"/api/v1/xp-cruise-plan/detail"})
    public R<XpCruisePlanDetailVO> detail(@ApiParam(name = "id", value = "巡游计划ID") @RequestParam Long id){
        return R.ok(xpCruisePlanService.detail(id));
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/XpCruisePlanAddDTO.java
New file
@@ -0,0 +1,47 @@
package com.xiaopeng.operation.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
 * @author MiTao
 * @date 2026-02-26 18:36
 *
 */
@Data
@Accessors(chain = true)
@ApiModel("巡游计划新增数据传输对象")
public class XpCruisePlanAddDTO {
    @ApiModelProperty(value = "计划名称")
    @NotBlank(message = "计划名称不能为空")
    private String planName;
    @ApiModelProperty(value = "关联点位列表")
    @NotEmpty(message = "关联点位列表不能为空")
    private List<CruisePlanPointDTO> pointList;
    @ApiModelProperty(value = "关联车辆列表")
    @NotEmpty(message = "关联车辆列表不能为空")
    private List<Long> vehicleIdList;
    @Data
    @Accessors(chain = true)
    public static class CruisePlanPointDTO{
        @ApiModelProperty(value = "点位ID")
        private Integer pointId;
        @ApiModelProperty(value = "排序")
        private Integer sortOrder;
        @ApiModelProperty(value = "是否停车 0-否 1-是")
        private Integer stopFlag;
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/dto/XpCruisePlanEditDTO.java
New file
@@ -0,0 +1,28 @@
package com.xiaopeng.operation.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
 * @author MiTao
 * @date 2026-02-26 18:36
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@ApiModel("巡游计划新增数据传输对象")
public class XpCruisePlanEditDTO extends XpCruisePlanAddDTO{
    @ApiModelProperty(value = "id")
    private Long id;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpCruisePlanService.java
@@ -3,7 +3,10 @@
import com.xiaopeng.api.model.operation.XpCruisePlan;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.dto.XpCruisePlanAddDTO;
import com.xiaopeng.operation.dto.XpCruisePlanEditDTO;
import com.xiaopeng.operation.query.CruisePlanQuery;
import com.xiaopeng.operation.vo.XpCruisePlanDetailVO;
/**
 * <p>
@@ -21,4 +24,28 @@
     * @return
     */
    PageInfo<XpCruisePlan> getPageList(CruisePlanQuery query);
    /**
     * 新增巡游计划
     * @param dto
     */
    void add(XpCruisePlanAddDTO dto);
    /**
     * 编辑巡游计划
     * @param dto
     */
    void edit(XpCruisePlanEditDTO dto);
    /**
     * 删除巡游计划
     * @param id
     */
    void deleteById(Long id);
    /**
     * 巡游计划详情
     * @param id
     */
    XpCruisePlanDetailVO detail(Long id);
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpCruisePlanServiceImpl.java
@@ -2,24 +2,39 @@
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpCruisePlan;
import com.xiaopeng.api.model.operation.XpCruisePlanPoint;
import com.xiaopeng.api.model.operation.XpCruisePlanVehicle;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
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.common.security.utils.SecurityUtils;
import com.xiaopeng.operation.constant.I18nKeyConstant;
import com.xiaopeng.operation.dto.XpCruisePlanAddDTO;
import com.xiaopeng.operation.dto.XpCruisePlanEditDTO;
import com.xiaopeng.operation.mapper.XpCruisePlanMapper;
import com.xiaopeng.operation.query.CruisePlanQuery;
import com.xiaopeng.operation.service.IXpCruisePlanPointService;
import com.xiaopeng.operation.service.IXpCruisePlanService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.operation.service.IXpCruisePlanVehicleService;
import com.xiaopeng.operation.service.IXpPointService;
import com.xiaopeng.operation.vo.XpCruisePlanDetailVO;
import com.xiaopeng.operation.vo.XpCruisePlanPointVO;
import com.xiaopeng.operation.vo.XpCruisePlanVehicleVO;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -36,7 +51,10 @@
    private final IXpCruisePlanPointService xpCruisePlanPointService;
    private final IXpCruisePlanVehicleService xpCruisePlanVehicleService;
    private final IXpPointService xpPointService;
    private final XpVehicleClient xpVehicleClient;
    private final XpFleetVehicleClient xpFleetVehicleClient;
    private final XpFleetClient xpFleetClient;
    @Override
    public PageInfo<XpCruisePlan> getPageList(CruisePlanQuery query) {
        PageInfo<XpCruisePlan> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
@@ -59,4 +77,297 @@
        pageInfo.setRecords(page.getRecords());
        return pageInfo;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(XpCruisePlanAddDTO dto) {
        //1.校验巡游计划名称
        validatePlanName(dto.getPlanName(), null);
        //2.校验点位参数
        validatePointList(dto.getPointList());
        Set<Long> vehicleIdSet = validateVehicleList(dto.getVehicleIdList());
        //3.校验车辆是否已绑定其他巡游计划
        validateVehicleBind(vehicleIdSet, null);
        XpCruisePlan plan = new XpCruisePlan();
        plan.setPlanName(dto.getPlanName());
        plan.setCreateBy(SecurityUtils.getLoginUser().getSysUser().getNickName());
        boolean planSaved = save(plan);
        if (!planSaved || plan.getId() == null) {
            throw new ServiceException("新增巡游计划失败", I18nKeyConstant.CRUISE_PLAN_ADD_FAIL);
        }
        List<XpCruisePlanPoint> pointList = buildPointList(plan.getId(), dto.getPointList());
        boolean pointSaved = xpCruisePlanPointService.saveBatch(pointList);
        if (!pointSaved) {
            throw new ServiceException("新增巡游计划失败", I18nKeyConstant.CRUISE_PLAN_ADD_FAIL);
        }
        List<XpCruisePlanVehicle> vehicleList = buildVehicleList(plan.getId(), dto.getVehicleIdList());
        boolean vehicleSaved = xpCruisePlanVehicleService.saveBatch(vehicleList);
        if (!vehicleSaved) {
            throw new ServiceException("新增巡游计划失败", I18nKeyConstant.CRUISE_PLAN_ADD_FAIL);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(XpCruisePlanEditDTO dto) {
        if (dto.getId() == null) {
            throw new ServiceException("巡游计划ID不能为空", I18nKeyConstant.CRUISE_PLAN_ID_EMPTY);
        }
        XpCruisePlan plan = getById(dto.getId());
        if (plan == null) {
            throw new ServiceException("巡游计划不存在", I18nKeyConstant.CRUISE_PLAN_NOT_EXIST);
        }
        validatePlanName(dto.getPlanName(), dto.getId());
        validatePointList(dto.getPointList());
        Set<Long> vehicleIdSet = validateVehicleList(dto.getVehicleIdList());
        validateVehicleBind(vehicleIdSet, dto.getId());
        plan.setPlanName(dto.getPlanName());
        plan.setUpdateBy(SecurityUtils.getLoginUser().getSysUser().getNickName());
        boolean planUpdated = updateById(plan);
        if (!planUpdated) {
            throw new ServiceException("编辑巡游计划失败", I18nKeyConstant.CRUISE_PLAN_EDIT_FAIL);
        }
        xpCruisePlanPointService.lambdaUpdate()
                .eq(XpCruisePlanPoint::getCruisePlanId, dto.getId())
                .remove();
        xpCruisePlanVehicleService.lambdaUpdate()
                .eq(XpCruisePlanVehicle::getCruisePlanId, dto.getId())
                .remove();
        List<XpCruisePlanPoint> pointList = buildPointList(dto.getId(), dto.getPointList());
        boolean pointSaved = xpCruisePlanPointService.saveBatch(pointList);
        if (!pointSaved) {
            throw new ServiceException("编辑巡游计划失败", I18nKeyConstant.CRUISE_PLAN_EDIT_FAIL);
        }
        List<XpCruisePlanVehicle> vehicleList = buildVehicleList(dto.getId(), dto.getVehicleIdList());
        boolean vehicleSaved = xpCruisePlanVehicleService.saveBatch(vehicleList);
        if (!vehicleSaved) {
            throw new ServiceException("编辑巡游计划失败", I18nKeyConstant.CRUISE_PLAN_EDIT_FAIL);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(Long id) {
        xpCruisePlanPointService.lambdaUpdate()
                .eq(XpCruisePlanPoint::getCruisePlanId, id)
                .remove();
        xpCruisePlanVehicleService.lambdaUpdate()
                .eq(XpCruisePlanVehicle::getCruisePlanId, id)
                .remove();
        removeById(id);
    }
    @Override
    public XpCruisePlanDetailVO detail(Long id) {
        if (id == null) {
            throw new ServiceException("巡游计划ID不能为空", I18nKeyConstant.CRUISE_PLAN_ID_EMPTY);
        }
        XpCruisePlan plan = getById(id);
        if (plan == null) {
            throw new ServiceException("巡游计划不存在", I18nKeyConstant.CRUISE_PLAN_NOT_EXIST);
        }
        List<XpCruisePlanPoint> planPoints = xpCruisePlanPointService.lambdaQuery()
                .eq(XpCruisePlanPoint::getCruisePlanId, id)
                .orderByAsc(XpCruisePlanPoint::getSortOrder)
                .list();
        List<Long> pointIds = planPoints.stream()
                .map(XpCruisePlanPoint::getPointId)
                .filter(Objects::nonNull)
                .map(Integer::longValue)
                .collect(Collectors.toList());
        Map<Long, XpPoint> pointMap = CollUtil.isNotEmpty(pointIds)
                ? xpPointService.listByIds(pointIds).stream()
                .collect(Collectors.toMap(XpPoint::getId, v -> v))
                : Collections.emptyMap();
        List<XpCruisePlanPointVO> pointList = planPoints.stream().map(planPoint -> {
            XpCruisePlanPointVO vo = new XpCruisePlanPointVO();
            vo.setPointId(planPoint.getPointId());
            vo.setSortOrder(planPoint.getSortOrder());
            XpPoint point = pointMap.get(planPoint.getPointId() == null ? null : planPoint.getPointId().longValue());
            if (point != null) {
                vo.setPointName(point.getPointName());
                vo.setPointAddress(point.getPointAddress());
            }
            return vo;
        }).collect(Collectors.toList());
        List<XpCruisePlanVehicle> planVehicles = xpCruisePlanVehicleService.lambdaQuery()
                .eq(XpCruisePlanVehicle::getCruisePlanId, id)
                .list();
        List<Long> vehicleIds = planVehicles.stream()
                .map(XpCruisePlanVehicle::getVehicleId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        Map<Long, XpVehicle> vehicleMap;
        if (CollUtil.isNotEmpty(vehicleIds)) {
            R<List<XpVehicle>> vehicleResult = xpVehicleClient.listByIds(vehicleIds);
            if (vehicleResult != null && CollUtil.isNotEmpty(vehicleResult.getData())) {
                vehicleMap = vehicleResult.getData().stream()
                        .collect(Collectors.toMap(XpVehicle::getId, v -> v));
            } else {
                vehicleMap = Collections.emptyMap();
            }
        } else {
            vehicleMap = Collections.emptyMap();
        }
        Map<Long, Long> vehicleFleetIdMap = new HashMap<>();
        if (CollUtil.isNotEmpty(vehicleIds)) {
            for (Long vehicleId : vehicleIds) {
                R<Long> fleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(vehicleId);
                if (fleetIdResult != null && fleetIdResult.getData() != null) {
                    vehicleFleetIdMap.put(vehicleId, fleetIdResult.getData());
                }
            }
        }
        Map<Long, String> fleetNameMap;
        if (!vehicleFleetIdMap.isEmpty()) {
            List<Long> fleetIds = vehicleFleetIdMap.values().stream().distinct().collect(Collectors.toList());
            R<List<XpFleet>> fleetResult = xpFleetClient.listByIds(fleetIds);
            if (fleetResult != null && CollUtil.isNotEmpty(fleetResult.getData())) {
                fleetNameMap = fleetResult.getData().stream()
                        .collect(Collectors.toMap(XpFleet::getId, XpFleet::getFleetName, (a, b) -> a));
            } else {
                fleetNameMap = Collections.emptyMap();
            }
        } else {
            fleetNameMap = Collections.emptyMap();
        }
        List<XpCruisePlanVehicleVO> vehicleList = planVehicles.stream().map(planVehicle -> {
            XpCruisePlanVehicleVO vo = new XpCruisePlanVehicleVO();
            vo.setVehicleId(planVehicle.getVehicleId());
            XpVehicle vehicle = vehicleMap.get(planVehicle.getVehicleId());
            if (vehicle != null) {
                vo.setVehicleCode(vehicle.getVehicleCode());
                vo.setPlateNumber(vehicle.getPlateNumber());
                vo.setVehicleModel(vehicle.getVehicleModel());
            }
            Long fleetId = vehicleFleetIdMap.get(planVehicle.getVehicleId());
            if (fleetId != null) {
                vo.setFleetName(fleetNameMap.get(fleetId));
            }
            return vo;
        }).collect(Collectors.toList());
        XpCruisePlanDetailVO detailVO = new XpCruisePlanDetailVO();
        detailVO.setId(plan.getId());
        detailVO.setPlanName(plan.getPlanName());
        detailVO.setPointNum(planPoints.size());
        detailVO.setVehicleNum(planVehicles.size());
        detailVO.setCreateTime(plan.getCreateTime());
        detailVO.setPointList(pointList);
        detailVO.setVehicleList(vehicleList);
        return detailVO;
    }
    /**
     * 校验巡游计划名称
     * @param planName
     * @param excludePlanId
     */
    private void validatePlanName(String planName, Long excludePlanId) {
        if (StringUtils.isBlank(planName)) {
            throw new ServiceException("计划名称不能为空", I18nKeyConstant.CRUISE_PLAN_NAME_EMPTY);
        }
        Long count = lambdaQuery()
                .eq(XpCruisePlan::getPlanName, planName)
                .ne(excludePlanId != null, XpCruisePlan::getId, excludePlanId)
                .count();
        if (count > 0) {
            throw new ServiceException("计划名称已存在", I18nKeyConstant.CRUISE_PLAN_NAME_EXISTS);
        }
    }
    /**
     * 校验关联点位列表
     * @param pointList
     */
    private void validatePointList(List<XpCruisePlanAddDTO.CruisePlanPointDTO> pointList) {
        if (CollUtil.isEmpty(pointList)) {
            throw new ServiceException("关联点位列表不能为空", I18nKeyConstant.CRUISE_PLAN_POINT_EMPTY);
        }
        Set<Integer> pointIdSet = new HashSet<>();
        for (XpCruisePlanAddDTO.CruisePlanPointDTO pointDTO : pointList) {
            if (pointDTO.getPointId() == null || pointDTO.getSortOrder() == null || pointDTO.getStopFlag() == null) {
                throw new ServiceException("点位参数不完整", I18nKeyConstant.CRUISE_PLAN_POINT_INCOMPLETE);
            }
            if (pointDTO.getStopFlag() != 0 && pointDTO.getStopFlag() != 1) {
                throw new ServiceException("点位停车标识不合法", I18nKeyConstant.CRUISE_PLAN_STOP_FLAG_INVALID);
            }
            if (!pointIdSet.add(pointDTO.getPointId())) {
                throw new ServiceException("点位ID重复", I18nKeyConstant.CRUISE_PLAN_POINT_DUPLICATE);
            }
        }
    }
    /**
     * 校验关联车辆列表
     * @param vehicleIdList
     * @return
     */
    private Set<Long> validateVehicleList(List<Long> vehicleIdList) {
        if (CollUtil.isEmpty(vehicleIdList)) {
            throw new ServiceException("关联车辆列表不能为空", I18nKeyConstant.CRUISE_PLAN_VEHICLE_EMPTY);
        }
        Set<Long> vehicleIdSet = new HashSet<>();
        for (Long vehicleId : vehicleIdList) {
            if (vehicleId == null) {
                throw new ServiceException("车辆参数不完整", I18nKeyConstant.CRUISE_PLAN_VEHICLE_INCOMPLETE);
            }
            if (!vehicleIdSet.add(vehicleId)) {
                throw new ServiceException("车辆ID重复", I18nKeyConstant.CRUISE_PLAN_VEHICLE_DUPLICATE);
            }
        }
        return vehicleIdSet;
    }
    /**
     * 校验车辆是否已绑定其他巡游计划
     * @param vehicleIdSet
     * @param excludePlanId
     */
    private void validateVehicleBind(Set<Long> vehicleIdSet, Long excludePlanId) {
        List<XpCruisePlanVehicle> boundVehicles = xpCruisePlanVehicleService.lambdaQuery()
                .in(XpCruisePlanVehicle::getVehicleId, vehicleIdSet)
                .ne(excludePlanId != null, XpCruisePlanVehicle::getCruisePlanId, excludePlanId)
                .list();
        if (CollUtil.isNotEmpty(boundVehicles)) {
            throw new ServiceException("车辆已关联其他巡游计划", I18nKeyConstant.CRUISE_PLAN_VEHICLE_ALREADY_BOUND);
        }
    }
    /**
     * 构建巡游计划点位列表
     * @param planId
     * @param pointList
     * @return
     */
    private List<XpCruisePlanPoint> buildPointList(Long planId, List<XpCruisePlanAddDTO.CruisePlanPointDTO> pointList) {
        List<XpCruisePlanPoint> result = new ArrayList<>(pointList.size());
        for (XpCruisePlanAddDTO.CruisePlanPointDTO pointDTO : pointList) {
            XpCruisePlanPoint point = new XpCruisePlanPoint();
            point.setCruisePlanId(planId);
            point.setPointId(pointDTO.getPointId());
            point.setSortOrder(pointDTO.getSortOrder());
            point.setStopFlag(pointDTO.getStopFlag());
            result.add(point);
        }
        return result;
    }
    /**
     * 构建巡游计划车辆列表
     * @param planId
     * @param vehicleIdList
     * @return
     */
    private List<XpCruisePlanVehicle> buildVehicleList(Long planId, List<Long> vehicleIdList) {
        List<XpCruisePlanVehicle> result = new ArrayList<>(vehicleIdList.size());
        for (Long vehicleId : vehicleIdList) {
            XpCruisePlanVehicle vehicle = new XpCruisePlanVehicle();
            vehicle.setCruisePlanId(planId);
            vehicle.setVehicleId(vehicleId);
            result.add(vehicle);
        }
        return result;
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanDetailVO.java
New file
@@ -0,0 +1,44 @@
package com.xiaopeng.operation.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
 * <p>
 * 巡游计划详情VO
 * </p>
 *
 * @author mitao
 * @since 2026-02-26
 */
@Data
@ApiModel(value = "巡游计划详情VO", description = "巡游计划详情响应对象")
public class XpCruisePlanDetailVO {
    @ApiModelProperty(value = "主键ID")
    private Long id;
    @ApiModelProperty(value = "计划名称")
    private String planName;
    @ApiModelProperty(value = "关联点位数")
    private Integer pointNum;
    @ApiModelProperty(value = "关联车辆数")
    private Integer vehicleNum;
    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "巡游点位列表")
    private List<XpCruisePlanPointVO> pointList;
    @ApiModelProperty(value = "巡游车辆列表")
    private List<XpCruisePlanVehicleVO> vehicleList;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanPointVO.java
New file
@@ -0,0 +1,30 @@
package com.xiaopeng.operation.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * <p>
 * 巡游计划点位VO
 * </p>
 *
 * @author mitao
 * @since 2026-02-26
 */
@Data
@ApiModel(value = "巡游计划点位VO", description = "巡游计划点位响应对象")
public class XpCruisePlanPointVO {
    @ApiModelProperty(value = "点位ID")
    private Integer pointId;
    @ApiModelProperty(value = "点位名称")
    private String pointName;
    @ApiModelProperty(value = "详细地址")
    private String pointAddress;
    @ApiModelProperty(value = "排序")
    private Integer sortOrder;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/XpCruisePlanVehicleVO.java
New file
@@ -0,0 +1,33 @@
package com.xiaopeng.operation.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * <p>
 * 巡游计划车辆VO
 * </p>
 *
 * @author mitao
 * @since 2026-02-26
 */
@Data
@ApiModel(value = "巡游计划车辆VO", description = "巡游计划车辆响应对象")
public class XpCruisePlanVehicleVO {
    @ApiModelProperty(value = "车辆ID")
    private Long vehicleId;
    @ApiModelProperty(value = "车辆编码")
    private String vehicleCode;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
    @ApiModelProperty(value = "型号")
    private String vehicleModel;
    @ApiModelProperty(value = "所属车队")
    private String fleetName;
}
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties
@@ -20,3 +20,17 @@
no.current.point=?????????
path.not.found=路径不存在
has.unpaid.order=有正在进行的订单
cruise.plan.name.empty=计划名称不能为空
cruise.plan.id.empty=巡游计划ID不能为空
cruise.plan.name.exists=计划名称已存在
cruise.plan.not.exist=巡游计划不存在
cruise.plan.point.empty=关联点位列表不能为空
cruise.plan.vehicle.empty=关联车辆列表不能为空
cruise.plan.point.incomplete=点位参数不完整
cruise.plan.stop.flag.invalid=点位停车标识不合法
cruise.plan.point.duplicate=点位ID重复
cruise.plan.vehicle.incomplete=车辆参数不完整
cruise.plan.vehicle.duplicate=车辆ID重复
cruise.plan.vehicle.already.bound=车辆已关联其他巡游计划
cruise.plan.add.fail=新增巡游计划失败
cruise.plan.edit.fail=编辑巡游计划失败
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -20,3 +20,10 @@
no.current.point=The current location is not open for service
path.not.found=Path does not exist
has.unpaid.order=There are ongoing orders
cruise.plan.name.exists=The name of the plan already exists
cruise.plan.point.incomplete=The point parameters are incomplete
cruise.plan.stop.flag.invalid=The parking sign at the location is invalid.
cruise.plan.point.duplicate=The point ID is duplicated
cruise.plan.vehicle.duplicate=Duplicate vehicle ID
cruise.plan.vehicle.already.bound=Vehicle [{0}] has been associated with other cruising plans
cruise.plan.add.fail=The new cruise plan failed
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -20,3 +20,10 @@
no.current.point=?????????
path.not.found=路径不存在
has.unpaid.order=有正在进行的订单
cruise.plan.name.exists=计划名称已存在
cruise.plan.point.incomplete=点位参数不完整
cruise.plan.stop.flag.invalid=点位停车标识不合法
cruise.plan.point.duplicate=点位ID重复
cruise.plan.vehicle.duplicate=车辆ID重复
cruise.plan.vehicle.already.bound=车辆[{0}]已关联其他巡游计划
cruise.plan.add.fail=新增巡游计划失败
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpCruiseOrderController.java
@@ -5,10 +5,15 @@
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
import com.xiaopeng.api.model.order.XpCruiseOrder;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.order.service.IXpCruiseOrderService;
import com.xiaopeng.order.model.query.XpCruiseOrderPageQuery;
import com.xiaopeng.order.model.vo.XpCruiseOrderPageVO;
import io.swagger.annotations.Api;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@@ -22,8 +27,8 @@
 * @author mitao
 * @since 2026-02-03
 */
@Validated
@RestController
@Api(tags = "巡游订单")
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCruiseOrderController {
@@ -87,5 +92,17 @@
        return R.ok(order.getId());
    }
    /**
     * 巡游订单记录分页列表
     *
     * @param query 分页查询参数
     * @return 分页结果
     */
    @ApiOperation(value = "巡游订单记录分页列表", tags = {"管理后台-巡游订单"})
    @PostMapping("/api/v1/cruise-order/pageList")
    public R<PageInfo<XpCruiseOrderPageVO>> pageList(@Validated @RequestBody XpCruiseOrderPageQuery query) {
        PageInfo<XpCruiseOrderPageVO> pageInfo = xpCruiseOrderService.pageList(query);
        return R.ok(pageInfo);
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/query/XpCruiseOrderPageQuery.java
New file
@@ -0,0 +1,46 @@
package com.xiaopeng.order.model.query;
import com.fasterxml.jackson.annotation.JsonFormat;
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;
import java.time.LocalDate;
/**
 * 巡游订单分页查询请求
 *
 * @author mitao
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "巡游订单分页查询请求", description = "巡游订单分页查询参数")
public class XpCruiseOrderPageQuery extends BasePage {
    @ApiModelProperty(value = "巡游计划ID", required = true)
    @NotNull(message = "巡游计划ID不能为空")
    private Long missionId;
    @ApiModelProperty(value = "订单号")
    private String orderNumber;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
    @ApiModelProperty(value = "车队ID")
    private Long fleetId;
    @ApiModelProperty(value = "状态 1进行中 2已停止 3已完成")
    private Integer status;
    @ApiModelProperty(value = "开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate startTime;
    @ApiModelProperty(value = "结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate endTime;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpCruiseOrderPageVO.java
New file
@@ -0,0 +1,76 @@
package com.xiaopeng.order.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;
/**
 * 巡游订单分页VO
 *
 * @author mitao
 */
@Data
@ApiModel(value = "巡游订单分页VO", description = "巡游订单分页返回对象")
public class XpCruiseOrderPageVO {
    @ApiModelProperty(value = "订单ID")
    private String id;
    @ApiModelProperty(value = "订单号")
    private String orderNumber;
    @ApiModelProperty(value = "车辆id")
    private Long vehicleId;
    @ApiModelProperty(value = "车辆vin")
    private String vehicleVin;
    @ApiModelProperty(value = "订单类型 1=正常巡游单 2返程单")
    private Integer orderType;
    @ApiModelProperty(value = "出发地经度值")
    private Double startLon;
    @ApiModelProperty(value = "出发地纬度值")
    private Double startLat;
    @ApiModelProperty(value = "目的地经度")
    private Double endLon;
    @ApiModelProperty(value = "目的地纬度")
    private Double endLat;
    @ApiModelProperty(value = "状态 1进行中 2已停止")
    private Integer status;
    @ApiModelProperty(value = "下单站点id")
    private Long startPointId;
    @ApiModelProperty(value = "下单终点站点Id")
    private Long endPointId;
    @ApiModelProperty(value = "下单站点名称")
    private String startPointName;
    @ApiModelProperty(value = "下单终点站点名称")
    private String endPointName;
    @ApiModelProperty(value = "任务id")
    private String missionId;
    @ApiModelProperty(value = "返程上次的订单id")
    private String lastOrderId;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
    @ApiModelProperty(value = "所属车队")
    private String fleetName;
    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpCruiseOrderService.java
@@ -19,6 +19,8 @@
import com.xiaopeng.order.model.dto.XpOrderPayDto;
import com.xiaopeng.order.model.query.XpHistoryOrderListQuery;
import com.xiaopeng.order.model.query.XpOrderPageQuery;
import com.xiaopeng.order.model.query.XpCruiseOrderPageQuery;
import com.xiaopeng.order.model.vo.XpCruiseOrderPageVO;
import com.xiaopeng.order.model.vo.XpOrderDetailVO;
import com.xiaopeng.order.model.vo.XpOrderHistoryVO;
import com.xiaopeng.order.model.vo.XpOrderPageVO;
@@ -41,5 +43,12 @@
    String closeCruiseOrder(String vin);
    /**
     * 巡游订单分页列表
     * @param query
     * @return
     */
    PageInfo<XpCruiseOrderPageVO> pageList(XpCruiseOrderPageQuery query);
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpCruiseOrderServiceImpl.java
@@ -71,6 +71,7 @@
import com.xiaopeng.order.mapper.XpOrderMapper;
import com.xiaopeng.order.model.dto.XpOrderCommentDto;
import com.xiaopeng.order.model.dto.XpOrderPayDto;
import com.xiaopeng.order.model.query.XpCruiseOrderPageQuery;
import com.xiaopeng.order.model.query.XpHistoryOrderListQuery;
import com.xiaopeng.order.model.query.XpOrderPageQuery;
import com.xiaopeng.order.model.vo.*;
@@ -114,6 +115,9 @@
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCruiseOrderServiceImpl extends ServiceImpl<XpCruiseOrderMapper, XpCruiseOrder> implements IXpCruiseOrderService {
    private final XpVehicleClient xpVehicleClient;
    private final XpFleetVehicleClient xpFleetVehicleClient;
    private final XpFleetClient xpFleetClient;
    /**
     * 关闭当前巡游单子
@@ -142,4 +146,101 @@
        }
        return null;
    }
    @Override
    public PageInfo<XpCruiseOrderPageVO> pageList(XpCruiseOrderPageQuery query) {
        PageInfo<XpCruiseOrderPageVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        LambdaQueryWrapper<XpCruiseOrder> wrapper = new LambdaQueryWrapper<XpCruiseOrder>()
                .eq(XpCruiseOrder::getMissionId, String.valueOf(query.getMissionId()))
                .like(StringUtils.isNotBlank(query.getOrderNumber()), XpCruiseOrder::getOrderNumber, query.getOrderNumber())
                .eq(query.getStatus() != null, XpCruiseOrder::getStatus, query.getStatus());
        if (query.getStartTime() != null) {
            wrapper.ge(BaseModel::getCreateTime, query.getStartTime().atStartOfDay());
        }
        if (query.getEndTime() != null) {
            wrapper.le(BaseModel::getCreateTime, query.getEndTime().plusDays(1).atStartOfDay());
        }
        if (query.getFleetId() != null) {
            R<List<Long>> fleetVehicleResult = xpFleetVehicleClient.listVehicleIdsByFleetId(query.getFleetId());
            if (fleetVehicleResult == null || CollectionUtils.isEmpty(fleetVehicleResult.getData())) {
                pageInfo.setRecords(Collections.emptyList());
                return pageInfo;
            }
            wrapper.in(XpCruiseOrder::getVehicleId, fleetVehicleResult.getData());
        }
        if (StringUtils.isNotBlank(query.getPlateNumber())) {
            R<List<XpVehicle>> vehicleResult = xpVehicleClient.listByPlateNumber(query.getPlateNumber());
            if (vehicleResult == null || CollectionUtils.isEmpty(vehicleResult.getData())) {
                pageInfo.setRecords(Collections.emptyList());
                return pageInfo;
            }
            List<Long> vehicleIds = vehicleResult.getData().stream().map(XpVehicle::getId).collect(Collectors.toList());
            wrapper.in(XpCruiseOrder::getVehicleId, vehicleIds);
        }
        wrapper.orderByDesc(BaseModel::getCreateTime);
        Page<XpCruiseOrder> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpCruiseOrder> pageResult = this.page(page, wrapper);
        if (CollectionUtils.isEmpty(pageResult.getRecords())) {
            pageInfo.setTotal(0L);
            pageInfo.setRecords(Collections.emptyList());
            return pageInfo;
        }
        Set<Long> vehicleIds = pageResult.getRecords().stream()
                .map(XpCruiseOrder::getVehicleId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, XpVehicle> vehicleMap;
        if (!vehicleIds.isEmpty()) {
            R<List<XpVehicle>> vehicleResult = xpVehicleClient.listByIds(new ArrayList<>(vehicleIds));
            if (vehicleResult != null && !CollectionUtils.isEmpty(vehicleResult.getData())) {
                vehicleMap = vehicleResult.getData().stream().collect(Collectors.toMap(XpVehicle::getId, v -> v));
            } else {
                vehicleMap = Collections.emptyMap();
            }
        } else {
            vehicleMap = Collections.emptyMap();
        }
        Map<Long, Long> vehicleFleetIdMap = new HashMap<>();
        if (!vehicleIds.isEmpty()) {
            for (Long vehicleId : vehicleIds) {
                R<Long> fleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(vehicleId);
                if (fleetIdResult != null && fleetIdResult.getData() != null) {
                    vehicleFleetIdMap.put(vehicleId, fleetIdResult.getData());
                }
            }
        }
        Map<Long, String> fleetNameMap;
        if (!vehicleFleetIdMap.isEmpty()) {
            List<Long> fleetIds = vehicleFleetIdMap.values().stream().distinct().collect(Collectors.toList());
            R<List<XpFleet>> fleetResult = xpFleetClient.listByIds(fleetIds);
            if (fleetResult != null && !CollectionUtils.isEmpty(fleetResult.getData())) {
                fleetNameMap = fleetResult.getData().stream()
                        .collect(Collectors.toMap(XpFleet::getId, XpFleet::getFleetName, (a, b) -> a));
            } else {
                fleetNameMap = Collections.emptyMap();
            }
        } else {
            fleetNameMap = Collections.emptyMap();
        }
        List<XpCruiseOrderPageVO> voList = pageResult.getRecords().stream().map(order -> {
            XpCruiseOrderPageVO vo = new XpCruiseOrderPageVO();
            BeanUtils.copyProperties(order, vo);
            XpVehicle vehicle = vehicleMap.get(order.getVehicleId());
            if (vehicle != null) {
                vo.setPlateNumber(vehicle.getPlateNumber());
            }
            Long fleetId = vehicleFleetIdMap.get(order.getVehicleId());
            if (fleetId != null) {
                vo.setFleetName(fleetNameMap.get(fleetId));
            }
            return vo;
        }).collect(Collectors.toList());
        pageInfo.setTotal(pageResult.getTotal());
        pageInfo.setRecords(voList);
        return pageInfo;
    }
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java
@@ -1,27 +1,32 @@
package com.xiaopeng.pushMessage.utils;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.push20160801.models.*;
import com.aliyun.sdk.service.push20160801.*;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
//import javax.net.ssl.KeyManager;
//import javax.net.ssl.X509TrustManager;
import java.util.concurrent.CompletableFuture;
@Slf4j
public class AliPushUtil {
    @Value("${ali.push.accessKeyId}")
    private String accessKeyId;
    @Value("${ali.push.accessKeySecret}")
    private String accessKeySecret;
    @Value("${ali.push.appKey}")
    private Long appKey;
    @Value("${ali.push.regionId}")
    private String regionId;
    @Value("${ali.push.appKey}")
    private Long appKey;
    @Value("${ali.push.iosAppKey}")
    private Long iosAppKey;
    /**
     * 消息类型  MESSAGE 消息  NOTICE  通知
     */
@@ -51,7 +56,7 @@
     * @param body 消息内容
     * @throws Exception
     */
    public void push(String account, String title, String body) throws Exception {
    public String pushMessage(Integer deviceType,String account, String title, String body) throws Exception {
        StaticCredentialProvider credentialProvider = StaticCredentialProvider.create(
                Credential.builder()
@@ -70,9 +75,14 @@
                )
                .build()) {
            // Parameter settings for API request
            // 判断账号类型
            Long appKeys = appKey;
            if(deviceType == 2){
                appKeys = iosAppKey;
            }
            PushRequest pushRequest = PushRequest.builder()
                    .appKey(appKey)
                    .appKey(appKeys)
                    .pushType(PUSH_TYPE)
                    .deviceType(DEVICE_TYPE_ALL)
                    .target(TARGET)
@@ -82,10 +92,21 @@
                    .build();
            CompletableFuture<PushResponse> response = client.push(pushRequest);
            PushResponse resp = response.get();
            System.out.println(new Gson().toJson(resp));
            String json = new Gson().toJson(resp);
            log.info("运维APP-消息推送:{}",json);
            JSONObject jsonObject = JSONObject.parseObject(json);
            Integer statusCode = jsonObject.getInteger("statusCode");
            if(statusCode == 200){
                log.info("运维APP-消息推送成功");
                JSONObject resultBody = jsonObject.getJSONObject("body");
                return resultBody.getString("messageId");
            }else{
                log.info("运维APP-消息推送失败");
                return null;
            }
        }
    }
    public static void main(String[] args) throws Exception {
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetVehicleController.java
@@ -9,6 +9,7 @@
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
/**
@@ -41,4 +42,21 @@
        Long fleetId = xpFleetVehicleService.getFleetIdByVehicleId(vehicleId);
        return R.ok(fleetId);
    }
    /**
     * 根据车队ID查询车辆ID列表(内部接口)
     *
     * @param fleetId 车队ID
     * @return 车辆ID列表
     */
    @ApiOperation(value = "根据车队ID查询车辆ID列表", tags = {"内部接口"})
    @GetMapping("/inner/fleet-vehicle/listVehicleIdsByFleetId/{fleetId}")
    public R<List<Long>> listVehicleIdsByFleetId(
            @PathVariable @ApiParam(value = "车队ID", required = true) Long fleetId) {
        if (Objects.isNull(fleetId)) {
            return R.fail("车队ID不能为空");
        }
        List<Long> vehicleIds = xpFleetVehicleService.listVehicleIdsByFleetId(fleetId);
        return R.ok(vehicleIds);
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpFleetVehicleService.java
@@ -20,4 +20,12 @@
     * @return 车队ID,如果车辆未关联车队则返回null
     */
    Long getFleetIdByVehicleId(Long vehicleId);
    /**
     * 根据车队ID查询车辆ID列表
     *
     * @param fleetId 车队ID
     * @return 车辆ID列表
     */
    List<Long> listVehicleIdsByFleetId(Long fleetId);
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpFleetVehicleServiceImpl.java
@@ -8,8 +8,10 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * <p>
@@ -47,4 +49,18 @@
        return list.get(0).getFleetId();
    }
    @Override
    public List<Long> listVehicleIdsByFleetId(Long fleetId) {
        if (Objects.isNull(fleetId)) {
            log.warn("查询车辆ID列表时,车队ID为空");
            return Collections.emptyList();
        }
        return this.lambdaQuery()
                .eq(XpFleetVehicle::getFleetId, fleetId)
                .list()
                .stream()
                .map(XpFleetVehicle::getVehicleId)
                .collect(Collectors.toList());
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpPolicyApplicationServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -81,13 +82,18 @@
        }
        // 3.分页查询保单申请列表
        IPage<XpPolicyApplication> page = lambdaQuery().in(CollUtil.isNotEmpty(operationUserIds), XpPolicyApplication::getApplicantId, operationUserIds)
        LambdaQueryWrapper<XpPolicyApplication> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (Objects.nonNull(query.getStartTime())) {
            lambdaQueryWrapper.ge(XpPolicyApplication::getCreateTime, LocalDateTime.of(query.getStartTime(), LocalTime.MIN));
        }
        if (Objects.nonNull(query.getStartTime())) {
            lambdaQueryWrapper.le(XpPolicyApplication::getCreateTime, LocalDateTime.of(query.getEndTime(), LocalTime.MAX));
        }
        lambdaQueryWrapper.in(CollUtil.isNotEmpty(operationUserIds), XpPolicyApplication::getApplicantId, operationUserIds)
                .in(CollUtil.isNotEmpty(vehicleIds), XpPolicyApplication::getVehicleId, vehicleIds)
                .eq(Objects.nonNull(query.getAuditStatus()), XpPolicyApplication::getAuditStatus, query.getAuditStatus())
                .ge(Objects.nonNull(query.getStartTime()), XpPolicyApplication::getCreateTime, LocalDateTime.of(query.getStartTime(), LocalTime.MIN))
                .le(Objects.nonNull(query.getEndTime()), XpPolicyApplication::getCreateTime, LocalDateTime.of(query.getEndTime(), LocalTime.MAX))
                .orderByDesc(XpPolicyApplication::getCreateTime).page(new Page<>(query.getPageNum(), query.getPageSize()));
                .orderByDesc(XpPolicyApplication::getCreateTime);
        Page<XpPolicyApplication> page = page(new Page<>(query.getPageNum(), query.getPageSize()), lambdaQueryWrapper);
        //4.批量获取相关用户和车辆信息
        Set<Long> operationsUserIdSet = page.getRecords().stream().map(XpPolicyApplication::getApplicantId).filter(Objects::nonNull).collect(Collectors.toSet());
        Set<Long> vehicleIdSet = page.getRecords().stream().map(XpPolicyApplication::getVehicleId).filter(Objects::nonNull).collect(Collectors.toSet());
@@ -98,9 +104,7 @@
        // 5. 组装 VO
        List<XpPolicyApplicationVO> voList = page.getRecords().stream()
                .map(item ->{
                    return convertToPageVO(item, longXpVehicleMap, longXpOperationsUserMap, auditUserMap);
                } )
                .map(item -> convertToPageVO(item, longXpVehicleMap, longXpOperationsUserMap, auditUserMap))
                .collect(Collectors.toList());
        // 6. 返回分页结果
        resultPage.setTotal(page.getTotal());