无关风月
2026-02-27 7337d6b1a7eb87b858f30fafd41bbea14f148921
Merge remote-tracking branch 'origin/master'
10个文件已添加
43个文件已修改
1632 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/config/ThreadPoolConfig.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/TokenController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCouponController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpAppUserCouponMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpAppUserCouponService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCouponServiceImpl.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCouponMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpQualificationAuditMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/AppLoginCodeDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/AppLoginPasswordDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/operation/XpElectronicFenceClient.java 4 ●●●●● 补丁 | 查看 | 原始文档 | 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/account/XpAppUserCoupon.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpOperationsUser.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpVehicleDetection.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlan.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanPoint.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanVehicle.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpCruiseOrder.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/enums/AuditStateEnum.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpShiftMapper.xml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceAreaServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | 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/XpCouponController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpCruisePlanController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | 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/query/CruisePlanQuery.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/query/UserFeedBackQuery.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpCruisePlanService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpCruisePlanServiceImpl.java 353 ●●●●● 补丁 | 查看 | 原始文档 | 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-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java 181 ●●●●● 补丁 | 查看 | 原始文档 | 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-account-boot/src/main/java/com/xiaopeng/account/config/ThreadPoolConfig.java
New file
@@ -0,0 +1,39 @@
package com.xiaopeng.account.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class ThreadPoolConfig {
    @Bean(name = "couponSendExecutor")
    public ThreadPoolTaskExecutor couponSendExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心参数
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(20);  // 增大队列容量
        executor.setKeepAliveSeconds(60);
        // 线程名称前缀
        executor.setThreadNamePrefix("sendCoupon-");
        // 拒绝策略:调用者线程执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        // 初始化
        executor.initialize();
        return executor;
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/TokenController.java
@@ -78,7 +78,7 @@
    @PostMapping("base/v1/operationPasswordLogin")
    public R<?> operationPasswordLogin(@RequestBody AppLoginPasswordDTO appLoginPasswordDTO, HttpServletRequest request) {
        // 用户登录
        LoginOperationUser userInfo = sysLoginService.operationPasswordLogin(appLoginPasswordDTO.getPhoneNumber(), appLoginPasswordDTO.getPassword(), request);
        LoginOperationUser userInfo = sysLoginService.operationPasswordLogin(appLoginPasswordDTO.getPhoneNumber(), appLoginPasswordDTO.getPassword(), appLoginPasswordDTO.getDeviceType(), request);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", tokenService.createOperationUserToken(userInfo));
//        List<SysRole> roles = userInfo.getXpOperationsUser().getRoles();
@@ -94,7 +94,7 @@
    @PostMapping("base/v1/verificationCodeLogin")
    public R<?> verificationCodeLogin(@RequestBody AppLoginCodeDTO appLoginCodeDTO, HttpServletRequest request) {
        // 用户登录
        LoginOperationUser userInfo = sysLoginService.verificationCodeLogin(appLoginCodeDTO.getPhoneNumber(), appLoginCodeDTO.getVerificationCode(), request);
        LoginOperationUser userInfo = sysLoginService.verificationCodeLogin(appLoginCodeDTO.getPhoneNumber(), appLoginCodeDTO.getVerificationCode(),appLoginCodeDTO.getDeviceType(), request);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", tokenService.createOperationUserToken(userInfo));
//        List<SysRole> roles = userInfo.getXpOperationsUser().getRoles();
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCouponController.java
@@ -59,19 +59,19 @@
    @PostMapping("/inner/appUserCoupon/getUserCoupon")
    public R<UserCouponVo> getUserCoupon(@RequestBody GetUserCouponDto dto) {
        String phone ="18888888888";
        if(dto.getUserId()>1){
            UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(dto.getUserId().toString());
            if(baseUserByUid!=null){
                Object data = baseUserByUid.getData();
                phone = JSONObject.parseObject(data.toString()).getString("mobile");
            }
        }
//        String phone ="18888888888";
//        if(dto.getUserId()>1){
//            UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(dto.getUserId().toString());
//            if(baseUserByUid!=null){
//                Object data = baseUserByUid.getData();
//                phone = JSONObject.parseObject(data.toString()).getString("mobile");
//            }
//        }
        UserCouponVo userCouponVo = new UserCouponVo();
        // 获取用户所有优惠券
        List<XpAppUserCoupon> couponList = xpAppUserCouponService.list(new LambdaQueryWrapper<XpAppUserCoupon>().eq(XpAppUserCoupon::getStatus, 1).eq(XpAppUserCoupon::getPhone,phone).apply("FIND_IN_SET({0}, open_city_id)", dto.getOpenCityId()) );
        List<XpAppUserCoupon> couponList = xpAppUserCouponService.list(new LambdaQueryWrapper<XpAppUserCoupon>().eq(XpAppUserCoupon::getStatus, 1).eq(XpAppUserCoupon::getAppUserId,dto.getUserId()).apply("FIND_IN_SET({0}, open_city_id)", dto.getOpenCityId()) );
        if(couponList.isEmpty()){
            return R.ok(userCouponVo);
        }
@@ -116,14 +116,14 @@
    public R<XpAppUserCouponVo> list(@RequestBody XpUserCouponListQuery query, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        String phone ="18888888888";
        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
        if(baseUserByUid!=null){
            Object data = baseUserByUid.getData();
            phone = JSONObject.parseObject(data.toString()).getString("mobile");
        }
//        String phone ="18888888888";
//        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
//        if(baseUserByUid!=null){
//            Object data = baseUserByUid.getData();
//            phone = JSONObject.parseObject(data.toString()).getString("mobile");
//        }
        XpAppUserCouponVo vo = xpAppUserCouponService.userCouponList(query, phone);
        XpAppUserCouponVo vo = xpAppUserCouponService.userCouponList(query, uid);
        return R.ok(vo);
    }
@@ -154,14 +154,14 @@
    public R<XpAppUserCouponListVo> detail(@PathVariable("couponId") Long couponId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        String phone ="18888888888";
        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
        if(baseUserByUid!=null){
            Object data = baseUserByUid.getData();
            phone = JSONObject.parseObject(data.toString()).getString("mobile");
        }
//        String phone ="18888888888";
//        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
//        if(baseUserByUid!=null){
//            Object data = baseUserByUid.getData();
//            phone = JSONObject.parseObject(data.toString()).getString("mobile");
//        }
        XpAppUserCoupon coupon = xpAppUserCouponService.getById(couponId);
        if(phone.equals(coupon.getPhone())){
        if(uid.equals(coupon.getAppUserId().toString())){
            XpAppUserCouponListVo vo = new XpAppUserCouponListVo();
            vo.setCouponId(coupon.getId().toString());
            vo.setTitle(coupon.getCouponName());
@@ -189,13 +189,13 @@
    public R<XpAppUserCouponTotalVo> total(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        String phone ="18888888888";
        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
        if(baseUserByUid!=null){
            Object data = baseUserByUid.getData();
            phone = JSONObject.parseObject(data.toString()).getString("mobile");
        }
        XpAppUserCouponTotalVo vo = xpAppUserCouponService.userCouponTotal(phone);
//        String phone ="18888888888";
//        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
//        if(baseUserByUid!=null){
//            Object data = baseUserByUid.getData();
//            phone = JSONObject.parseObject(data.toString()).getString("mobile");
//        }
        XpAppUserCouponTotalVo vo = xpAppUserCouponService.userCouponTotal(uid);
        return R.ok(vo);
    }
@@ -206,7 +206,8 @@
    @PostMapping("/api/v1/appUserCoupon/send")
    @ApiOperation(value = "发放优惠券", tags = {"管理后台-发放优惠券"})
    public R<String> send(@RequestBody SendCouponDto dto) {
        return xpAppUserCouponService.send(dto);
        xpAppUserCouponService.send(dto);
        return R.ok();
    }
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpAppUserCouponMapper.java
@@ -17,6 +17,6 @@
 */
public interface XpAppUserCouponMapper extends BaseMapper<XpAppUserCoupon> {
    Page<XpAppUserCouponListVo> userCouponList(@Param("page") Page<XpAppUserCouponListVo> page, @Param("query") XpUserCouponListQuery query, @Param("phone") String phone);
    Page<XpAppUserCouponListVo> userCouponList(@Param("page") Page<XpAppUserCouponListVo> page, @Param("query") XpUserCouponListQuery query, @Param("uid") String uid);
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IXpAppUserCouponService.java
@@ -1,17 +1,16 @@
package com.xiaopeng.account.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.account.model.dto.ReceiveCouponDto;
import com.xiaopeng.account.model.dto.SendCouponDto;
import com.xiaopeng.account.model.query.XpUserCouponListQuery;
import com.xiaopeng.account.model.vo.XpAppUserCouponListVo;
import com.xiaopeng.account.model.vo.XpAppUserCouponTotalVo;
import com.xiaopeng.account.model.vo.XpAppUserCouponVo;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.common.core.domain.R;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
 * <p>
@@ -26,17 +25,17 @@
    /**
     * 获取用户优惠券列表
     * @param query
     * @param phone
     * @param uid
     * @return
     */
    XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String phone);
    XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String uid);
    /**+
     * 获取用户优惠券数量total
     * @param phone
     * @param uid
     * @return
     */
    XpAppUserCouponTotalVo userCouponTotal(String phone);
    XpAppUserCouponTotalVo userCouponTotal(String uid);
    /**
     * 领取优惠券
@@ -56,9 +55,10 @@
    /**
     * 发送优惠券
     *
     * @param dto
     * @return
     */
    R<String> send(SendCouponDto dto);
   R<String> send(SendCouponDto dto);
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java
@@ -7,7 +7,6 @@
import com.xiaopeng.api.dto.operation.UpdatePasswordDTO;
import com.xiaopeng.api.model.account.SysUser;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.operation.SysMenu;
import com.xiaopeng.api.vo.account.LoginOperationUser;
import com.xiaopeng.api.vo.account.LoginUser;
import com.xiaopeng.common.core.constant.CacheConstants;
@@ -26,9 +25,7 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * 登录校验方法
@@ -150,7 +147,7 @@
        recordLogService.recordLogininfor(request, sysUser.getId().intValue(), username, Constants.LOGIN_SUCCESS_STATUS, "注册成功");
    }
    public LoginOperationUser operationPasswordLogin(String phoneNumber, String password, HttpServletRequest request) {
    public LoginOperationUser operationPasswordLogin(String phoneNumber, String password, Integer deviceType, HttpServletRequest request) {
        LoginOperationUser loginOperationUser = new LoginOperationUser();
        phoneNumber = phoneNumber.trim();
        // 查询运维用户信息
@@ -171,9 +168,12 @@
        }
        passwordService.validateOperationsUser(operationsUser, password, request);
        loginOperationUser.setXpOperationsUser(operationsUser);
        //更新用户登录设备类型
        operationsUser.setDeviceType(deviceType);
        operationsUserService.updateById(operationsUser);
        return loginOperationUser;
    }
    public LoginOperationUser verificationCodeLogin(String phoneNumber, String code, HttpServletRequest request) {
    public LoginOperationUser verificationCodeLogin(String phoneNumber, String code, Integer deviceType, HttpServletRequest request) {
        LoginOperationUser loginOperationUser = new LoginOperationUser();
        phoneNumber = phoneNumber.trim();
        // 查询运维用户信息
@@ -201,6 +201,9 @@
        }
        loginOperationUser.setXpOperationsUser(operationsUser);
        //更新用户登录设备类型
        operationsUser.setDeviceType(deviceType);
        operationsUserService.updateById(operationsUser);
        return loginOperationUser;
    }
    public Boolean verificationCodeUpdate(UpdatePasswordDTO updatePasswordDTO, HttpServletRequest request) {
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCouponServiceImpl.java
@@ -14,20 +14,22 @@
import com.xiaopeng.account.model.vo.XpAppUserCouponVo;
import com.xiaopeng.account.service.IXpAppUserCouponService;
import com.xiaopeng.account.util.holiday.HolidayUtil;
import com.xiaopeng.api.dto.account.UserResourcesDto;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.operation.XpCouponClient;
import com.xiaopeng.api.feignClient.operation.XpWhiteListClient;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.model.operation.XpCoupon;
import com.xiaopeng.api.model.operation.XpWhitelist;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
 * <p>
@@ -49,16 +51,20 @@
    @Autowired
    private XpWhiteListClient xpWhiteListClient;
    @Autowired
    private UserResourcesClient userResourcesClient;
    /**
     * 获取用户优惠券列表
     * @param query
     * @param phone
     * @param uid
     * @return
     */
    @Override
    public XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String phone) {
    public XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String uid) {
        Page<XpAppUserCouponListVo> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpAppUserCouponListVo> list = baseMapper.userCouponList(page, query, phone);
        Page<XpAppUserCouponListVo> list = baseMapper.userCouponList(page, query, uid);
        XpAppUserCouponVo vo = new XpAppUserCouponVo();
        vo.setTotal((int) page.getTotal());
        vo.setList(list.getRecords());
@@ -67,12 +73,12 @@
    /**
     * 获取用户优惠券数量total
     * @param phone
     * @param uid
     * @return
     */
    @Override
    public XpAppUserCouponTotalVo userCouponTotal(String phone) {
        List<XpAppUserCoupon> couponList = baseMapper.selectList(new LambdaQueryWrapper<XpAppUserCoupon>().eq(XpAppUserCoupon::getPhone, phone));
    public XpAppUserCouponTotalVo userCouponTotal(String uid) {
        List<XpAppUserCoupon> couponList = baseMapper.selectList(new LambdaQueryWrapper<XpAppUserCoupon>().eq(XpAppUserCoupon::getAppUserId, uid));
        XpAppUserCouponTotalVo vo = new XpAppUserCouponTotalVo();
        vo.setAvailableNum((int) couponList.stream().filter(e -> e.getStatus() == 1).count());
        vo.setUsedNum((int) couponList.stream().filter(e -> e.getStatus() == 2).count());
@@ -89,7 +95,7 @@
    @Override
    public boolean receive(ReceiveCouponDto dto, String uid) {
        XpAppUserCoupon coupon = baseMapper.selectById(dto.getCouponId());
        if(coupon.getAppUserId().toString().equals(uid)){
        if (coupon.getAppUserId().toString().equals(uid)) {
            coupon.setStatus(1);
            return updateById(coupon);
@@ -104,12 +110,12 @@
     */
    @Override
    public List<XpAppUserCoupon> timeRule(List<XpAppUserCoupon> couponList) {
       List<XpAppUserCoupon> xpAppUserCoupons = new ArrayList<>();
        List<XpAppUserCoupon> xpAppUserCoupons = new ArrayList<>();
        for (XpAppUserCoupon coupon : couponList) {
            String timeJson = coupon.getTimeJson();
            Boolean inOperationTime = isInOperationTime(timeJson);
            if(inOperationTime){
            if (inOperationTime) {
                xpAppUserCoupons.add(coupon);
            }
        }
@@ -119,38 +125,61 @@
    /**
     * 发送优惠券
     *
     * @param dto
     * @return
     */
    @Override
    @Async("couponSendExecutor")
    public R<String> send(SendCouponDto dto) {
        R<XpCoupon> coupon = xpCouponClient.getCouponById(dto.getCouponId());
        if(coupon!=null){
        if (coupon != null) {
            XpCoupon xpCoupon = coupon.getData();
            if(dto.getPhone()==null){
            if (dto.getPhone() == null) {
                // 全部发放
                R<List<String>> allWhiteList = xpWhiteListClient.getAllWhiteList();
                if(allWhiteList!=null){
                if (allWhiteList != null) {
                    List<String> phoneList = allWhiteList.getData();
                    dto.setPhone(phoneList);
                }
            }
            for (String phone : dto.getPhone()) {
                XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon();
                xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
                xpAppUserCoupon.setMoney(xpCoupon.getAmount());
                xpAppUserCoupon.setCouponId(dto.getCouponId());
                UserResourcesDto userByMobile = userResourcesClient.getUserByMobile(phone);
                if (userByMobile != null) {
                    Long uid = JSONObject.parseObject(userByMobile.getData().toString()).getLong("uid");
                    XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon();
                    xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
                    xpAppUserCoupon.setMoney(xpCoupon.getAmount());
                    xpAppUserCoupon.setCouponId(dto.getCouponId());
                    xpAppUserCoupon.setAppUserId(uid);
                    xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(100));
                    xpAppUserCoupon.setStatus(0);
                    xpAppUserCoupon.setCouponType(xpCoupon.getCouponType());
                    xpAppUserCoupon.setPhone(phone);
                xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(100));
                xpAppUserCoupon.setStatus(0);
                xpAppUserCoupon.setCouponType(xpCoupon.getCouponType());
                xpAppUserCoupon.setPhone( phone);
                    xpAppUserCoupon.setCouponName(xpCoupon.getCouponName());
                    xpAppUserCoupon.setOpenCityId("0");
                    xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
                    baseMapper.insert(xpAppUserCoupon);
                }else {
                    Long uid = 1l;
                    XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon();
                    xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
                    xpAppUserCoupon.setMoney(xpCoupon.getAmount());
                    xpAppUserCoupon.setCouponId(dto.getCouponId());
                    xpAppUserCoupon.setAppUserId(uid);
                    xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(100));
                    xpAppUserCoupon.setStatus(0);
                    xpAppUserCoupon.setCouponType(xpCoupon.getCouponType());
                    xpAppUserCoupon.setPhone(phone);
                xpAppUserCoupon.setCouponName(xpCoupon.getCouponName());
                xpAppUserCoupon.setOpenCityId("0");
                xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
                baseMapper.insert(xpAppUserCoupon);
                    xpAppUserCoupon.setCouponName(xpCoupon.getCouponName());
                    xpAppUserCoupon.setOpenCityId("0");
                    xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
                    baseMapper.insert(xpAppUserCoupon);
                }
            }
        }
@@ -167,9 +196,9 @@
        JSONArray jsonArray = JSONArray.parseArray(timeJson);
        Integer holidayToday = holidayUtil.isHolidayToday();
        for (Object o : jsonArray) {
            JSONObject jsonObject =JSONObject.parseObject(o.toString());
            JSONObject jsonObject = JSONObject.parseObject(o.toString());
            String type = jsonObject.getString("type");
            if(type.equals(holidayToday.toString())){
            if (type.equals(holidayToday.toString())) {
                JSONArray time = jsonObject.getJSONArray("time");
                // 判断当前时间 是否在time时间段中
                for (Object object : time) {
@@ -177,17 +206,17 @@
                    String startTime = timeArray[0];
                    String endTime = timeArray[1];
                    boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
                    if(currentTimeInRange){
                    if (currentTimeInRange) {
                        return true;
                    }
                }
            }else if(type.equals("3")){
            } else if (type.equals("3")) {
                // 判断是什么类型 0每天  1-7 想起
                String typeStr = jsonObject.getString("typeStr");
                // 查看今天周几
                Integer week = DateUtils.getWeek();
                // 包含每天 或者  包含当天
                if(typeStr.contains("0") || typeStr.contains(week.toString())){
                if (typeStr.contains("0") || typeStr.contains(week.toString())) {
                    JSONArray time = jsonObject.getJSONArray("time");
                    // 判断当前时间 是否在time时间段中
                    for (Object object : time) {
@@ -195,7 +224,7 @@
                        String startTime = timeArray[0];
                        String endTime = timeArray[1];
                        boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
                        if(currentTimeInRange){
                        if (currentTimeInRange) {
                            return true;
                        }
                    }
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCouponMapper.xml
@@ -23,7 +23,7 @@
        else status
        end as `status`
        FROM xp_app_user_coupon
        WHERE phone = #{phone}
        WHERE app_user_id = #{uid}
        ) as t
        where 1=1
        <if test="query.status != null">
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpQualificationAuditMapper.xml
@@ -13,8 +13,8 @@
        xou.nick_name, xou.phone_number, xou.avatar,xou.unique_number
        from xp_qualification_audit xqa
        left join xp_operations_user xou on xqa.operations_user_id = xou.id
        left join xp_operations_role xor on xou.id = xor.operations_user_id
        <where>
        left join xp_operations_role xore on xou.id = xore.operations_user_id
        where xqa.is_deleted = 0
            <if test="query.nickName != null and query.nickName != ''">
                and xou.nick_name like concat('%',#{query.nickName},'%')
            </if>
@@ -22,13 +22,11 @@
                and xou.phone_number like concat('%',#{query.phoneNumber},'%')
            </if>
            <if test="query.roleId != null">
                and xor.role_id = #{query.roleId}
                and xore.role_id = #{query.roleId}
            </if>
            <if test="query.auditStatus != null">
                and xqa.audit_status = #{query.auditStatus}
            </if>
            AND xqa.is_deleted = 0
        </where>
        order by xqa.create_time desc
    </select>
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/AppLoginCodeDTO.java
@@ -13,4 +13,7 @@
    private String phoneNumber;
    @ApiModelProperty(value = "验证码")
    private String verificationCode;
    @ApiModelProperty(value = "设备类型 1-Android 2-iOS")
    private Integer deviceType;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/AppLoginPasswordDTO.java
@@ -13,4 +13,7 @@
    private String phoneNumber;
    @ApiModelProperty(value = "密码")
    private String password;
    @ApiModelProperty(value = "设备类型 1-Android 2-iOS")
    private Integer deviceType;
}
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/operation/XpElectronicFenceClient.java
@@ -18,11 +18,11 @@
    @PostMapping("/inner/xpElectronicFence/selectBatchIds")
    @PostMapping("/api/v1/electronicFence/inner/xpElectronicFence/selectBatchIds")
    R<List<XpElectronicFence>> selectBatchIds(@RequestBody List<String> fenceIds);
    @GetMapping("/inner/xpElectronicFence/selectById")
    @GetMapping("/api/v1/electronicFence/inner/xpElectronicFence/selectById")
    R<XpElectronicFence> selectById(@RequestParam String fenceId);
}
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/account/XpAppUserCoupon.java
@@ -31,7 +31,7 @@
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.NONE)
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    @ApiModelProperty(value = "满减金额")
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpOperationsUser.java
@@ -69,4 +69,6 @@
    @ApiModelProperty(value = "培训结业证")
    private String trainingGraduationCertificate;
    @ApiModelProperty(value = "设备类型 1-Android 2-iOS")
    private Integer deviceType;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpVehicleDetection.java
@@ -6,6 +6,7 @@
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -91,6 +92,7 @@
    private Long operationsUserId;
    @ApiModelProperty(value = "检测时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private LocalDateTime checkTime;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlan.java
@@ -1,10 +1,13 @@
package com.xiaopeng.api.model.operation;
import com.baomidou.mybatisplus.annotation.TableField;
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.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -24,7 +27,7 @@
@Accessors(chain = true)
@TableName("xp_cruise_plan")
@ApiModel(value="XpCruisePlan对象", description="巡游计划")
public class XpCruisePlan implements Serializable {
public class XpCruisePlan extends BaseModel {
    private static final long serialVersionUID = 1L;
@@ -35,20 +38,13 @@
    @ApiModelProperty(value = "计划名称")
    private String planName;
    @ApiModelProperty(value = "是否删除 0=未删除 1=已删除")
    private Boolean isDeleted;
    @ApiModelProperty(value = "关联点位数")
    @TableField(exist = false)
    private Integer pointNum;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建人")
    private String createBy;
    @ApiModelProperty(value = "修改人")
    private String updateBy;
    @ApiModelProperty(value = "关联车辆数")
    @TableField(exist = false)
    private Integer vehicleNum;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanPoint.java
@@ -5,6 +5,8 @@
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -38,23 +40,10 @@
    @ApiModelProperty(value = "点位ID")
    private Integer pointId;
    @ApiModelProperty(value = " 排序")
    @ApiModelProperty(value = "排序")
    private Integer sortOrder;
    @ApiModelProperty(value = "是否删除 0=未删除 1=已删除")
    private Boolean isDeleted;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建人")
    private String createBy;
    @ApiModelProperty(value = "修改人")
    private String updateBy;
    @ApiModelProperty(value = "是否停车 0-否 1-是")
    private Integer stopFlag;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpCruisePlanVehicle.java
@@ -5,6 +5,8 @@
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import com.xiaopeng.common.core.web.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -28,30 +30,10 @@
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty(value = "巡游计划ID")
    private Long cruisePlanId;
    @ApiModelProperty(value = "车辆ID")
    private Long vehicleId;
    @ApiModelProperty(value = "是否删除 0=未删除 1=已删除")
    private Boolean isDeleted;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建人")
    private String createBy;
    @ApiModelProperty(value = "修改人")
    private String updateBy;
}
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-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -426,7 +426,7 @@
            LonLatDto origin = new LonLatDto();
            if (orderId.contains("XU")) {
                orderType = OrderType.CRUISE_ORDER;
                // todo 下发巡游单的导航数据
                R<XpCruiseOrder> cruiseOrder = xpCruiseOrderClient.getCruiseOrder(orderId);
                XpCruiseOrder xpCruiseOrder = cruiseOrder.getData();
@@ -534,7 +534,7 @@
        Long vehicleId = null;
        String orderType = "";
        if (orderId.contains("XU")) {
            // todo 下发巡游单的导航数据
            orderType = OrderType.CRUISE_ORDER;
            //巡游单
            if (orderPhase.equals(OrderPhase.ROUTE_TO_CRUISE)) {
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-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java
@@ -75,7 +75,13 @@
        // 发车
        if(dto.getDepartureStatus()==1){
            // 找车辆的任务  生成巡游单子 下发命令 TODO
            // 找车辆的任务  生成巡游单子 下发命令 TODO v
        }else if(dto.getDepartureStatus()==3){
            // 收车 关闭当前巡游单子 生成新的返程单子  找到收车站点
            xpCruiseOrderClient.updateCruiseOrder(xpVehicle.getVehicleVin());
@@ -90,10 +96,13 @@
            xpCruiseOrder.setOrderType(2);
            xpCruiseOrder.setStartLon(x);
            xpCruiseOrder.setStartLat(y);
            // TODO 找到收车站点
            // TODO v 找到收车站点
            xpCruiseOrder.setStatus(1);
            xpCruiseOrderClient.addCruiseOrder(xpCruiseOrder);
            // 下发命令
            orderMissionClient.pushOrder(new PushOrderDto( xpCruiseOrder.getVehicleVin(),xpCruiseOrder.getId(),null));
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpChargingRecordServiceImpl.java
@@ -202,7 +202,6 @@
        // 查询车辆当前充电信息
        XpChargingRecord xpChargingRecord = this.getOne(new LambdaQueryWrapper<XpChargingRecord>()
                .eq(XpChargingRecord::getVehicleId, vehicleId)
                .isNull(XpChargingRecord::getEndRemainingBattery)
                .isNull(XpChargingRecord::getTaskId)
                .orderByDesc(XpChargingRecord::getCreateTime)
                .last("LIMIT 1"));
xp-robotaxi-maintenance-boot/src/main/resources/mapper/maintenance/XpShiftMapper.xml
@@ -6,6 +6,7 @@
        select id, shift_name, shift_color, start_time, end_time, is_rest,
       rest_start_time, rest_end_time, create_by, create_time, update_by, update_time, is_deleted
        from xp_shift
        where is_deleted = 0
        order by create_time desc
    </select>
</mapper>
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceAreaServiceImpl.java
@@ -203,7 +203,7 @@
                if (ElectricFenceUtil.areaMonitoring(collect, dto.getLongitude()+","+dto.getLatitude())) {
                    String fenceId = polygon.getFenceId();
                    R<XpElectronicFence> xpElectronicFence = xpElectronicFenceClient.selectById(fenceId);
                    if(xpElectronicFence!=null){
                    if(xpElectronicFence!=null && null != xpElectronicFence.getData()){
                        String operationStrategy = xpElectronicFence.getData().getOperationStrategy();
                        Boolean inOperationTime = isInOperationTime(operationStrategy);
                        if(inOperationTime){
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/XpCouponController.java
@@ -44,7 +44,7 @@
     * 优惠券列表
     */
    @PostMapping("/v1/api/xp-coupon/list")
    @ApiOperation(value = "优惠券列表",tags = {"管理后台-优惠券列表"},response = XpCoupon.class)
    @ApiOperation(value = "优惠券列表",tags = {"管理后台-优惠券"},response = XpCoupon.class)
    public R<Page<XpCoupon>> list(@RequestBody XpCouponListQuery query ){
        return R.ok(xpCouponService.getList(query));
    }
@@ -58,7 +58,7 @@
     * @return
     */
    @PostMapping("/v1/api/xp-coupon/add")
    @ApiOperation(value = "添加优惠券",tags = {"管理后台-添加优惠券"},response = XpCoupon.class)
    @ApiOperation(value = "添加优惠券",tags = {"管理后台-优惠券"},response = XpCoupon.class)
    public R<?> add(@RequestBody XpCoupon xpCoupon){
        xpCouponService.save(xpCoupon);
        return R.ok();
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpCruisePlanController.java
@@ -1,9 +1,30 @@
package com.xiaopeng.operation.controller;
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>
@@ -13,7 +34,44 @@
 * @author mitao
 * @since 2026-02-26
 */
@Validated
@RestController
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCruisePlanController {
    private final IXpCruisePlanService xpCruisePlanService;
    @ApiOperation(value = "获取巡游计划分页列表", tags = {"管理后台-巡游计划"})
    @PostMapping(value = {"/api/v1/xp-cruise-plan/pageList"})
    public R<PageInfo<XpCruisePlan>> getPageList(@RequestBody CruisePlanQuery query){
        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/query/CruisePlanQuery.java
New file
@@ -0,0 +1,22 @@
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-26 14:32
 *
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "巡游计划分页查询对象")
public class CruisePlanQuery extends BasePage {
    @ApiModelProperty(value = "反馈内容")
    private String planName;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/query/UserFeedBackQuery.java
@@ -15,6 +15,7 @@
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "用户反馈分页查询对象")
public class UserFeedBackQuery extends BasePage {
    @ApiModelProperty(value = "反馈内容")
    private String feedbackContent;
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpCruisePlanService.java
@@ -2,6 +2,11 @@
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>
@@ -13,4 +18,34 @@
 */
public interface IXpCruisePlanService extends IService<XpCruisePlan> {
    /**
     * 获取巡游计划分页列表
     * @param query
     * @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
@@ -1,10 +1,41 @@
package com.xiaopeng.operation.service.impl;
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.*;
import java.util.stream.Collectors;
/**
 * <p>
@@ -15,6 +46,328 @@
 * @since 2026-02-26
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpCruisePlanServiceImpl extends ServiceImpl<XpCruisePlanMapper, XpCruisePlan> implements IXpCruisePlanService {
    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());
        Page<XpCruisePlan> page = lambdaQuery()
                .like(StringUtils.isNotBlank(query.getPlanName()), XpCruisePlan::getPlanName, query.getPlanName())
                .orderByDesc(XpCruisePlan::getCreateTime)
                .page(pageInfo);
        //收集巡游计划ID
        List<Long> planIds = page.getRecords().stream().map(XpCruisePlan::getId).collect(Collectors.toList());
        // 批量查询巡游计划点位和车辆信息,并组装到巡游计划对象中
        Map<Long, List<XpCruisePlanPoint>> pointMap = CollUtil.isNotEmpty(planIds) ? xpCruisePlanPointService.lambdaQuery().in(XpCruisePlanPoint::getCruisePlanId, planIds)
                .list().stream().collect(Collectors.groupingBy(XpCruisePlanPoint::getCruisePlanId)) : Collections.emptyMap();
        Map<Long, List<XpCruisePlanVehicle>> vehicleMap = CollUtil.isNotEmpty(planIds) ? xpCruisePlanVehicleService.lambdaQuery().in(XpCruisePlanVehicle::getCruisePlanId, planIds)
                .list().stream().collect(Collectors.groupingBy(XpCruisePlanVehicle::getCruisePlanId)) : Collections.emptyMap();
        page.getRecords().forEach(plan -> {
            plan.setPointNum(pointMap.get(plan.getId()).size());
            plan.setVehicleNum(vehicleMap.get(plan.getId()).size());
        });
        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-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -4,7 +4,7 @@
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -12,6 +12,7 @@
import com.google.common.collect.ImmutableMap;
import com.xiaopeng.api.dto.account.GetUserCouponDto;
import com.xiaopeng.api.dto.account.UseUserCouponDto;
import com.xiaopeng.api.dto.account.UserResourcesDto;
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
@@ -23,6 +24,7 @@
import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.account.XpAppUserClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
@@ -43,6 +45,7 @@
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.account.GetUserListByUidsVo;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.automaticPilot.proto.down.cloudOrderOperation.OrderOperationType;
import com.xiaopeng.api.vo.operation.RouteVo;
@@ -50,6 +53,7 @@
import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo;
import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.constant.HttpStatus;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.OrderStatusEnum;
import com.xiaopeng.common.core.enums.PushMessageTypeEnum;
@@ -122,7 +126,7 @@
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpOrderServiceImpl extends ServiceImpl<XpOrderMapper, XpOrder> implements IXpOrderService {
    private final XpAppUserClient xpAppUserClient;
    private final UserResourcesClient userResourcesClient;
    private final XpPointClient xpPointClient;
    private final XpVehicleClient xpVehicleClient;
    private final XpAppUserCouponClient xpAppUserCouponClient;
@@ -212,13 +216,13 @@
        }
        // 4. 批量查询关联数据
        Map<Long, XpAppUser> userMap = batchGetUsers(userIds);
        JSONObject jsonObject = batchGetUsers(userIds);
        Map<Long, XpVehicle> vehicleMap = batchGetVehicles(vehicleIds);
        Map<Long, XpFleet> fleetMap = batchGetFleets(fleetIds);
        // 5. 组装 VO
        List<XpOrderPageVO> voList = pageResult.getRecords().stream()
                .map(order -> convertToPageVO(order, userMap, vehicleMap, fleetMap))
                .map(order -> convertToPageVO(order, jsonObject, vehicleMap, fleetMap))
                .collect(Collectors.toList());
        // 6. 返回分页结果
@@ -241,11 +245,11 @@
        // 3. 获取用户信息
        if (Objects.nonNull(order.getAppUserId())) {
            Map<Long, XpAppUser> userMap = batchGetUsers(Collections.singleton(order.getAppUserId()));
            XpAppUser user = userMap.get(order.getAppUserId());
            if (Objects.nonNull(user)) {
                vo.setNickName(user.getNickName());
                vo.setPhoneNumber(user.getPhoneNumber());
            JSONObject jsonObject = batchGetUsers(Collections.singleton(order.getAppUserId()));
            JSONObject userInfo = jsonObject.getJSONObject(order.getAppUserId().toString());
            if (Objects.nonNull(userInfo)) {
                vo.setNickName(userInfo.getString("nickName"));
                vo.setPhoneNumber(userInfo.getString("mobile"));
            }
        }
@@ -320,13 +324,13 @@
        }
        // 4. 批量查询关联数据
        Map<Long, XpAppUser> userMap = batchGetUsers(userIds);
        JSONObject jsonObject = batchGetUsers(userIds);
        Map<Long, XpVehicle> vehicleMap = batchGetVehicles(vehicleIds);
        Map<Long, XpFleet> fleetMap = batchGetFleets(fleetIds);
        // 5. 组装 VO
        return orderList.stream()
                .map(order -> convertToPageVO(order, userMap, vehicleMap, fleetMap))
                .map(order -> convertToPageVO(order, jsonObject, vehicleMap, fleetMap))
                .collect(Collectors.toList());
    }
@@ -355,20 +359,22 @@
    /**
     * 批量获取用户信息
     */
    private Map<Long, XpAppUser> batchGetUsers(Set<Long> userIds) {
    private JSONObject batchGetUsers(Set<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return new HashMap<>();
            return new JSONObject();
        }
        try {
            R<List<XpAppUser>> result = xpAppUserClient.listByIds(new ArrayList<>(userIds));
            if (Objects.nonNull(result) && Objects.nonNull(result.getData())) {
                return result.getData().stream()
                        .collect(Collectors.toMap(XpAppUser::getId, Function.identity(), (a, b) -> a));
            GetUserListByUidsVo getUserListByUidsVo = new GetUserListByUidsVo();
            getUserListByUidsVo.setUids(new ArrayList<>(userIds));
            getUserListByUidsVo.setGetPreCarOwner(Boolean.TRUE);
            UserResourcesDto userListByUids = userResourcesClient.getUserListByUids(getUserListByUidsVo);
            if (userListByUids.getCode().equals(HttpStatus.SUCCESS)) {
                return JSONObject.parseObject(userListByUids.getData().toString());
            }
        } catch (Exception e) {
            log.error("批量获取用户信息失败: {}", e.getMessage());
        }
        return new HashMap<>();
        return new JSONObject();
    }
    /**
@@ -452,7 +458,7 @@
     * 转换为分页列表 VO
     */
    private XpOrderPageVO convertToPageVO(XpOrder order,
                                          Map<Long, XpAppUser> userMap,
                                          JSONObject jsonObject,
                                          Map<Long, XpVehicle> vehicleMap,
                                          Map<Long, XpFleet> fleetMap) {
        XpOrderPageVO vo = new XpOrderPageVO();
@@ -467,10 +473,10 @@
        vo.setStatus(order.getStatus());
        // 用户信息
        if (Objects.nonNull(order.getAppUserId()) && userMap.containsKey(order.getAppUserId())) {
            XpAppUser user = userMap.get(order.getAppUserId());
            vo.setNickName(user.getNickName());
            vo.setPhoneNumber(user.getPhoneNumber());
        if (Objects.nonNull(order.getAppUserId()) && jsonObject.containsKey(order.getAppUserId().toString())) {
            JSONObject userInfo = jsonObject.getJSONObject(order.getAppUserId().toString());
            vo.setNickName(userInfo.getString("nickName"));
            vo.setPhoneNumber(userInfo.getString("mobile"));
        }
        // 车辆信息
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/AliPushUtil.java
New file
@@ -0,0 +1,181 @@
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.regionId}")
    private String regionId;
    @Value("${ali.push.appKey}")
    private Long appKey;
    @Value("${ali.push.iosAppKey}")
    private Long iosAppKey;
    /**
     * 消息类型  MESSAGE 消息  NOTICE  通知
     */
    private final String PUSH_TYPE = "MESSAGE";
    /**
     * 设备类型 ALL 全部 HARMONY 鸿蒙  IOS ios设备 ANDROID 安卓设备
     */
    private final String DEVICE_TYPE_ALL = "ALL";
    private final String DEVICE_TYPE_HARMONY = "HARMONY";
    private final String DEVICE_TYPE_IOS = "IOS";
    private final String DEVICE_TYPE_ANDROID = "ANDROID";
    /**
     * 推送目标
     * DEVICE:根据设备推送。
     * ACCOUNT:根据账号推送。
     * ALIAS:根据别名推送。
     * TAG:根据标签推送。
     * ALL:推送给全部设备(同一种 DeviceType 的两次全推的间隔至少为 1 秒)。
     */
    private final String TARGET = "ACCOUNT";
    /**
     *
     * @param account 运维手机号
     * @param title 标题
     * @param body 消息内容
     * @throws Exception
     */
    public String pushMessage(Integer deviceType,String account, String title, String body) throws Exception {
        StaticCredentialProvider credentialProvider = StaticCredentialProvider.create(
                Credential.builder()
                        .accessKeyId(accessKeyId)
                        .accessKeySecret(accessKeySecret)
                        .build()
        );
        // Configure the Client
        try (AsyncClient client = AsyncClient.builder()
                .region(regionId) // Region ID
                .credentialsProvider(credentialProvider)
                .overrideConfiguration(
                    ClientOverrideConfiguration.create()
                    .setEndpointOverride("cloudpush.aliyuncs.com")
                )
                .build()) {
            // 判断账号类型
            Long appKeys = appKey;
            if(deviceType == 2){
                appKeys = iosAppKey;
            }
            PushRequest pushRequest = PushRequest.builder()
                    .appKey(appKeys)
                    .pushType(PUSH_TYPE)
                    .deviceType(DEVICE_TYPE_ALL)
                    .target(TARGET)
                    .targetValue(account)
                    .title(title)
                    .body(body)
                    .build();
            CompletableFuture<PushResponse> response = client.push(pushRequest);
            PushResponse resp = response.get();
            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 {
        // HttpClient Configuration
        /*HttpClient httpClient = new ApacheAsyncHttpClientBuilder()
                .connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds
                .responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds
                .maxConnections(128) // Set the connection pool size
                .maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds
                // Configure the proxy
                .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001))
                        .setCredentials("<your-proxy-username>", "<your-proxy-password>"))
                // If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true))
                .x509TrustManagers(new X509TrustManager[]{})
                .keyManagers(new KeyManager[]{})
                .ignoreSSL(false)
                .build();*/
        // Configure Credentials authentication information
        StaticCredentialProvider credentialProvider = StaticCredentialProvider.create(
                Credential.builder()
                        .accessKeyId("LTAI5tRNNQCCPn6Hc42RJYLj")
                        .accessKeySecret("5b9sMu7b5Ab87e3Oa1qujQsBXwvjFx")
                        .build()
        );
        // Configure the Client
        try (AsyncClient client = AsyncClient.builder()
                .region("cn-hangzhou") // Region ID
                //.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient)
                .credentialsProvider(credentialProvider)
                //.serviceConfiguration(Configuration.create()) // Service-level configuration
                // Client-level configuration rewrite, can set Endpoint, Http request parameters, etc.
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                // Endpoint 请参考 https://api.aliyun.com/product/Push
                                .setEndpointOverride("cloudpush.aliyuncs.com")
                        //.setConnectTimeout(Duration.ofSeconds(30))
                )
                .build()) {
            // Parameter settings for API request
            PushRequest pushRequest = PushRequest.builder()
                    .appKey(335666344L)
                    .pushType("MESSAGE")
                    .deviceType("ALL")
                    .target("ACCOUNT")
                    .targetValue("account1")
                    .title("消息通知")
                    .body("测试")
                    // Request-level configuration rewrite, can set Http request parameters, etc.
                    // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
                    .build();
            // Asynchronously get the return value of the API request
            CompletableFuture<PushResponse> response = client.push(pushRequest);
            // Synchronously get the return value of the API request
            PushResponse resp = response.get();
            System.out.println(new Gson().toJson(resp));
            // Asynchronous processing of return values
            /*response.thenAccept(resp -> {
                System.out.println(new Gson().toJson(resp));
            }).exceptionally(throwable -> { // Handling exceptions
                System.out.println(throwable.getMessage());
                return null;
            });*/
        }
    }
}
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());