xuhy
2026-04-16 9905889161f020707ca2702e7b5697c6c89a4907
Merge branch '1.0' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi into 1.0
30个文件已添加
21个文件已修改
1899 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/UserSubscribeMessageController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserController.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/UserSubscribeMessageMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IUserSubscribeMessageService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/UserSubscribeMessageServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/UserSubscribeMessageVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/UserSubscribeMessageMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/map/RouteAndFareDto.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/order/OrderChangeDto.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/UserSubscribeMessageClientFallbackFactory.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPricingClientFallbackFactory.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/SubscribeMessageClientFallbackFactory.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/UserSubscribeMessageClient.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPricingClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/SubscribeMessageClient.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/UserSubscribeMessage.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrder.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderWaypoint.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/vehicleConvoy/XpVehicle.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/service/BootCommonService.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RouteAndFareVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/pushMessage/SendSubscribeMessageVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/utils/DateUtils.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/ActiveOrderController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/ActiveOrderService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpCruiseCheckInRuleServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/ActiveOrderController.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderWaypointController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderWaypointMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderWaypointService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderWaypointServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpTestOrderServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/SubscribeMessageController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/Category.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/KeywordEnum.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/MessageTemplate.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/PubTemplateKeywords.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/PubTemplatetitles.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/SubscribeMessageUtil.java 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/UserSubscribeMessageController.java
New file
@@ -0,0 +1,63 @@
package com.xiaopeng.account.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.account.service.IUserSubscribeMessageService;
import com.xiaopeng.account.vo.UserSubscribeMessageVo;
import com.xiaopeng.api.feignClient.pushMessage.SubscribeMessageClient;
import com.xiaopeng.api.model.account.UserSubscribeMessage;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping("")
public class UserSubscribeMessageController {
    @Resource
    private IUserSubscribeMessageService userSubscribeMessageService;
    @Resource
    private SubscribeMessageClient subscribeMessageClient;
    @PostMapping("/api/v1/mini-program/subscribeMessage/saveUserSubscribeMessage")
    @ApiOperation(value = "授权消息模板", tags = {"小程序-优惠券"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<?> saveUserSubscribeMessage(@RequestBody UserSubscribeMessageVo vo, HttpServletRequest request){
        String uid = request.getHeader("XP-Uid");
        String openid = subscribeMessageClient.getOpenId(vo.getJscode()).getData();
        if(StringUtils.isEmpty(openid)){
            return R.fail("获取openid失败");
        }
        List<String> templateIds = vo.getTemplateIds();
        userSubscribeMessageService.remove(new LambdaQueryWrapper<UserSubscribeMessage>()
                .eq(UserSubscribeMessage::getUserId, uid).in(UserSubscribeMessage::getTemplateId, templateIds));
        for (String templateId : templateIds) {
            UserSubscribeMessage userSubscribeMessage = new UserSubscribeMessage();
            userSubscribeMessage.setUserId(Long.valueOf(uid));
            userSubscribeMessage.setTemplateId(templateId);
            userSubscribeMessage.setStatus(1);
            userSubscribeMessage.setOpenId(openid);
            userSubscribeMessage.setDeleted(false);
            userSubscribeMessageService.save(userSubscribeMessage);
        }
        return R.ok();
    }
    @GetMapping("/logan/inner/v1/subscribeMessage/getUserOpenId/{v}")
    public R<String> getUserOpenId(@PathVariable("uid") Integer uid){
        UserSubscribeMessage one = userSubscribeMessageService.getOne(new LambdaQueryWrapper<UserSubscribeMessage>()
                .eq(UserSubscribeMessage::getUserId, uid).last(" limit 1"));
        return R.ok(null != one ? one.getOpenId() : "");
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserController.java
@@ -1,17 +1,21 @@
package com.xiaopeng.account.controller;
import com.xiaopeng.account.model.dto.CollectAddrDTO;
import com.xiaopeng.account.service.IXpAppUserService;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
import com.xiaopeng.api.dto.statistics.UserStatisticsDTO;
import com.xiaopeng.api.model.account.XpAppUser;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@@ -69,4 +73,34 @@
        XpAppUser xpAppUser = xpAppUserService.getById(id);
        return R.ok(xpAppUser);
    }
//-----------------------------------------------app接口------------------------------------------
    /**
     * 获取免密结果
     */
    @PostMapping("/api/v1/mini-program/appUser/getPayPwd")
    @ApiOperation(value = "个人中心-获取免密结果", tags = {"小程序-获取免密结果"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<Boolean> appUserGetPayPwd(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        //调用其他接口获取 免密结果
        Boolean is = true;
        return R.ok(is);
    }
    /**
     * 设置免密结果
     */
    @PostMapping("/api/v1/mini-program/appUser/setPayPwd")
    @ApiOperation(value = "个人中心-设置免密结果", tags = {"小程序-设置免密结果"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<?> appUserSetPayPwd(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        //调用其他接口设置免密结果
        return R.ok();
    }
//-----------------------------------------------app接口------------------------------------------
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/UserSubscribeMessageMapper.java
New file
@@ -0,0 +1,7 @@
package com.xiaopeng.account.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.account.UserSubscribeMessage;
public interface UserSubscribeMessageMapper extends BaseMapper<UserSubscribeMessage> {
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/IUserSubscribeMessageService.java
New file
@@ -0,0 +1,7 @@
package com.xiaopeng.account.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.account.UserSubscribeMessage;
public interface IUserSubscribeMessageService extends IService<UserSubscribeMessage> {
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/UserSubscribeMessageServiceImpl.java
New file
@@ -0,0 +1,12 @@
package com.xiaopeng.account.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.account.mapper.UserSubscribeMessageMapper;
import com.xiaopeng.account.service.IUserSubscribeMessageService;
import com.xiaopeng.api.model.account.UserSubscribeMessage;
import org.springframework.stereotype.Service;
@Service
public class UserSubscribeMessageServiceImpl extends ServiceImpl<UserSubscribeMessageMapper, UserSubscribeMessage> implements IUserSubscribeMessageService {
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/UserSubscribeMessageVo.java
New file
@@ -0,0 +1,19 @@
package com.xiaopeng.account.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel
public class UserSubscribeMessageVo {
    @ApiModelProperty("授权消息模板ID")
    private List<String> templateIds;
    @ApiModelProperty("微信jscode")
    private String jscode;
}
xp-robotaxi-account-boot/src/main/resources/mapper/account/UserSubscribeMessageMapper.xml
New file
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaopeng.account.mapper.UserSubscribeMessageMapper">
</mapper>
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/map/RouteAndFareDto.java
@@ -9,6 +9,10 @@
@lombok.Data
@ApiModel(description = "线路和运费Dto")
public class RouteAndFareDto {
    @ApiModelProperty(value = "订单id")
    private Long orderId;
    @ApiModelProperty(value = "出发地")
    @NotNull(message = "出发地不能为空")
    private LonLatDto origin;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/order/OrderChangeDto.java
New file
@@ -0,0 +1,51 @@
package com.xiaopeng.api.dto.order;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class OrderChangeDto  {
    @ApiModelProperty(value = "订单Id")
    @NotNull(message = "订单Id不能为空")
    private Long orderId;
    @ApiModelProperty("终点站点id")
    @NotNull(message = "终点站点id不能为空")
    private Long endPointId;
    /*
    @ApiModelProperty(value = "目的地经度")
    @NotNull(message = "目的地经度不能为空")
    private Double endLon;
    @ApiModelProperty(value = "目的地纬度")
    @NotNull(message = "目的地纬度不能为空")
    private Double endLat;
    @ApiModelProperty(value = "目的地地点")
    @NotNull(message = "目的地地点不能为空")
    private String endAddress;
    */
    @ApiModelProperty(value = "优惠券id")
    private Long couponId;
    @ApiModelProperty("多路线快照 ID(选路线下单时与 selectedRouteId 同时传)")
    private String navigationSnapshotId;
    @ApiModelProperty("选中的路径唯一标识(高德 routeID)")
    private String selectedRouteId;
    @ApiModelProperty("选中路线对应的高德 strategy,用于推单队列;多路线下单时由服务端写入")
    private Integer selectedRoutePlanRule;
    @ApiModelProperty(value = "距离 米")
    private Integer distance = 0;
    @ApiModelProperty(value = "时间 秒")
    private Integer duration = 0;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/UserSubscribeMessageClientFallbackFactory.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.api.factory.account;
import com.xiaopeng.api.feignClient.account.UserRoutePlanRuleClient;
import com.xiaopeng.api.feignClient.account.UserSubscribeMessageClient;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class UserSubscribeMessageClientFallbackFactory implements FallbackFactory<UserSubscribeMessageClient> {
    @Override
    public UserSubscribeMessageClient create(Throwable cause) {
        return new UserSubscribeMessageClient() {
            @Override
            public R<String> getUserOpenId(Integer uid) {
                log.error("获取用户openid失败:" + cause);
                return R.fail("获取用户openid失败");
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPricingClientFallbackFactory.java
@@ -28,7 +28,7 @@
            }
            @Override
            public R<XpPricing> getPricingByCid(Long cityId) {
            public R<XpPricing> getPricingByCid(Long cityId,String vehicleModel) {
                log.error("根据开通城市获取定价失败", cause);
                return R.ok(null);
            }
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/pushMessage/SubscribeMessageClientFallbackFactory.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.factory.pushMessage;
import com.xiaopeng.api.feignClient.pushMessage.SubscribeMessageClient;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SubscribeMessageClientFallbackFactory implements FallbackFactory<SubscribeMessageClient> {
    @Override
    public SubscribeMessageClient create(Throwable cause) {
        return new SubscribeMessageClient(){
            @Override
            public R<String> getOpenId(String code) {
                log.error("获取openid失败: {}", cause.getMessage());
                return R.fail("获取openid失败");
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/UserSubscribeMessageClient.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.api.feignClient.account;
import com.xiaopeng.api.factory.account.UserRoutePlanRuleClientFallbackFactory;
import com.xiaopeng.api.factory.account.UserSubscribeMessageClientFallbackFactory;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(contextId = "UserSubscribeMessageClient",
        value = ServiceNameConstantsEnum.XP_ACCOUNT_SERVICE,
        fallbackFactory = UserSubscribeMessageClientFallbackFactory.class)
public interface UserSubscribeMessageClient {
    /**
     * 根据用户id获取openid
     * @param uid
     * @return
     */
    @GetMapping("/logan/inner/v1/subscribeMessage/getUserOpenId/{v}")
    R<String> getUserOpenId(@PathVariable("uid") Integer uid);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPricingClient.java
@@ -22,7 +22,7 @@
    // 根据开通城市 获取定价
    @GetMapping("/logan/inner/v1/getPricingByCid")
     R<XpPricing> getPricingByCid(@RequestParam Long cityId);
     R<XpPricing> getPricingByCid(@RequestParam Long cityId,@RequestParam(required = false) String vehicleModelId);
    /**
     * 保存XpGeoQueryEntity
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/pushMessage/SubscribeMessageClient.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.feignClient.pushMessage;
import com.xiaopeng.api.factory.pushMessage.SubscribeMessageClientFallbackFactory;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(contextId = "SubscribeMessageClient",
        value = ServiceNameConstantsEnum.XP_PUSH_MESSAGE_SERVICE,
        fallbackFactory = SubscribeMessageClientFallbackFactory.class)
public interface SubscribeMessageClient {
    /**
     * 根据微信jscode获取openid
     * @param code
     * @return
     */
    @GetMapping("/logan/inner/v1/subscribeMessage/getOpenId/{code}")
    R<String> getOpenId(@PathVariable("code") String code);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/UserSubscribeMessage.java
New file
@@ -0,0 +1,42 @@
package com.xiaopeng.api.model.account;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xiaopeng.common.core.web.domain.BaseModel;
import lombok.Data;
/**
 * 用户订阅消息
 */
@Data
@TableName("xp_user_subscribe_message")
public class UserSubscribeMessage extends BaseModel {
    //生成实体类字段
    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 微信openid
     */
    private String openId;
    /**
     * 消息模板id
     */
    private String templateId;
    /**
     * 授权状态(0=否,1=是)
     */
    private Integer status;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrder.java
@@ -36,7 +36,7 @@
    @ApiModelProperty(value = "用户id")
    @TableField(updateStrategy = FieldStrategy.NEVER)
    private Long appUserId;
    @ApiModelProperty(value = "用户id")
    @ApiModelProperty(value = "开通城市id")
    private Long cityId;
    @ApiModelProperty(value = "车辆id")
    private Long vehicleId;
@@ -183,7 +183,7 @@
    @ApiModelProperty(value = "下单城市编码")
    private String cityCode;
    @ApiModelProperty(value = "下单站点id")
    @ApiModelProperty(value = "起点站点id")
    @TableField("start_point_id")
    private Long startPointId;
@@ -191,7 +191,7 @@
    @TableField("end_point_id")
    private Long endPointId;
    @ApiModelProperty(value = "下单站点名称")
    @ApiModelProperty(value = "起点站点名称")
    @TableField("start_point_name")
    private String startPointName;
@@ -239,4 +239,8 @@
    @ApiModelProperty(value = "车辆是否故障 0-否 1-是")
    private Boolean carAbnormal;
    @ApiModelProperty(value = "订单修改目的地的次数 默认0")
    private Integer orderChange;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderWaypoint.java
New file
@@ -0,0 +1,46 @@
package com.xiaopeng.api.model.order;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xp_order_waypoint")
@ApiModel(value="xp_order_waypoint", description="订单途经点")
public class XpOrderWaypoint {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.NONE)
    private Long id;
    @ApiModelProperty(value = "订单id")
    @NotNull(message = "订单id不能为空")
    private Long orderId;
    @ApiModelProperty(value = "途经点点位id")
    @NotNull(message = "途经点点位id不能为空")
    private Long pointId;
    @ApiModelProperty(value = "途经点名称")
    @NotNull(message = "途经点名称不能为空")
    private String pointName;
    @ApiModelProperty(value = "经度")
    @NotNull(message = "经度不能为空")
    private Double longitude;
    @ApiModelProperty(value = "纬度")
    @NotNull(message = "纬度不能为空")
    private Double latitude;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/vehicleConvoy/XpVehicle.java
@@ -47,7 +47,7 @@
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
    @ApiModelProperty(value = "型号")
    @ApiModelProperty(value = "型号 关联车辆型号表id")
    private String vehicleModel;
    @ApiModelProperty(value = "车辆颜色")
xp-robotaxi-api/src/main/java/com/xiaopeng/api/service/BootCommonService.java
New file
@@ -0,0 +1,59 @@
package com.xiaopeng.api.service;
import com.xiaopeng.api.feignClient.operation.XpPricingClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class BootCommonService {
    @Resource
    private  XpPricingClient xpPricingClient;
    @Resource
    private  XpVehicleClient xpVehicleClient;
    @Resource
    private XpOrderClient xpOrderClient;
    public XpPricing getXpPricing(Long orderId) {
        XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData();
        return getXpPricing(xpOrder.getCityId(),xpOrder.getVehicleId());
    }
    public XpPricing getXpPricing(Long cityId, Long vehicleId) {
        String vehicleModelId = null;
        if(vehicleId != null){
            XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
            if(null != xpVehicle){
                vehicleModelId = xpVehicle.getVehicleModel();
            }
        }
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(cityId,vehicleModelId);
        return pricingByCityId.getData();
    }
    public String getVehicleModelId(Long orderId) {
        if(orderId!= null){
            R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(orderId);
            if (xpOrderR.getCode() != R.SUCCESS || xpOrderR.getData() == null) {
                throw new ServiceException("订单不存在");
            }
            XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrderR.getData().getVehicleId()).getData();
            return xpVehicle.getVehicleModel();
        }
        return null;
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RouteAndFareVo.java
@@ -30,6 +30,9 @@
    @ApiModelProperty(value = "是否开启试运营(0=否,1=是)")
    private Integer operateFree;
    @ApiModelProperty(value = "策略id")
    private Integer amapStrategy;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/pushMessage/SendSubscribeMessageVo.java
New file
@@ -0,0 +1,26 @@
package com.xiaopeng.api.vo.pushMessage;
import lombok.Data;
@Data
public class SendSubscribeMessageVo {
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 消息模板
     */
    private String templateId;
    /**
     * 消息内容
     */
    private String json;
    /**
     * 跳转页面
     */
    private String page;
}
xp-robotaxi-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -53,4 +53,6 @@
com.xiaopeng.api.factory.map.AdcFencePushClientFallbackFactory
com.xiaopeng.api.factory.account.UserRoutePlanRuleClientFallbackFactory
com.xiaopeng.api.factory.order.XpOrderReassignClientFallbackFactory
com.xiaopeng.api.factory.map.XpStationClientFallbackFactory
com.xiaopeng.api.factory.map.XpStationClientFallbackFactory
com.xiaopeng.api.factory.pushMessage.SubscribeMessageClientFallbackFactory
com.xiaopeng.api.factory.account.UserSubscribeMessageClientFallbackFactory
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/utils/DateUtils.java
@@ -443,4 +443,17 @@
        return count;
    }
    /**
     * 获取指定时间的 毫秒
     * @return
     */
    public static long getMilliseconds(LocalDateTime localDateTime){
    // 转换为毫秒时间戳(需指定时区)
    Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
    long timestamp = instant.toEpochMilli();
    return timestamp;
}
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/ActiveOrderController.java
New file
@@ -0,0 +1,78 @@
package com.xiaopeng.itinerary.controller;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.order.OrderChangeDto;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.itinerary.service.ActiveOrderService;
import com.xiaopeng.itinerary.service.XpPricingService;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/**
 * <p>
 * 订单行程
 * </p>
 */
@Slf4j
@RestController
@RequestMapping("")
@Api(tags = "费用管理")
public class ActiveOrderController {
    @Resource
    private ActiveOrderService activeOrderService;
    @Resource
    private  XpPricingService xpPricingService;
    /**
     * 在行程中修改目的地-获取路线和费用
     * @param routeAndFareDto
     * @param request
     * @return
     */
    @PostMapping("/api/v1/mini-program/activeOrder/route-and-fare")
    @ApiOperation(value = "导航-获取路线和费用", tags = {"小程序-获取路线和费用"}, response = RouteAndFareVo.class)
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<List<RouteAndFareVo>> getRouteAndFare(@RequestBody @Valid RouteAndFareDto routeAndFareDto, HttpServletRequest request) {
        if(null == routeAndFareDto.getOrderId()){
            return R.fail("订单id不能为空");
        }
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpPricingService.getRouteAndFare(routeAndFareDto, uid));
    }
    /**
     * 行程中 修改目的地 确认按钮
     * 修改目的地,
     * 1.重新确认路线
     * 2.确认价格
     */
    @ApiOperation(value = "行程中 修改目的地 确认按钮", notes = "", tags = {"小程序-行程中修改目的地"})
    @PostMapping("/api/v1/mini-program/order/orderChange")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<?> orderChange(@RequestBody @Validated OrderChangeDto dto, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        activeOrderService.orderChange(dto, uid);
        return R.ok();
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java
@@ -7,6 +7,7 @@
import com.xiaopeng.api.dto.map.RoutePreferenceSaveDto;
import com.xiaopeng.api.dto.map.RouteStaleCheckDto;
import com.xiaopeng.api.dto.map.SelectNavigationRouteDto;
import com.xiaopeng.api.dto.order.OrderChangeDto;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.vo.operation.MultiRouteAndFareVo;
import com.xiaopeng.api.vo.operation.RoutePreferenceItemVo;
@@ -18,6 +19,7 @@
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.dto.XpOrderDto;
import com.xiaopeng.itinerary.service.ActiveOrderService;
import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService;
import com.xiaopeng.itinerary.service.XpPricingService;
import com.xiaopeng.api.vo.HotFixInfoVo;
@@ -31,6 +33,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
@@ -137,6 +140,10 @@
        return R.ok(saveOrderVo);
    }
    /**
     * 获取热修复信息
     * @param
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java
@@ -48,6 +48,9 @@
    @NotNull(message = "下单地址不能为空")
    private String placementAddress;
    @ApiModelProperty("路线规划规则")
    @NotNull(message = "路线规划规则不能为空")
    private Integer routePlanRule;
    @ApiModelProperty("用户优惠券主键:null 或 0 表示不使用券;>0 为具体券 id")
    private Long userCouponId;
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/ActiveOrderService.java
New file
@@ -0,0 +1,13 @@
package com.xiaopeng.itinerary.service;
import com.xiaopeng.api.dto.order.OrderChangeDto;
/**
 * 行驶中的订单
 */
public interface ActiveOrderService {
    void orderChange(OrderChangeDto dto, String uid);
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java
@@ -60,6 +60,8 @@
     */
    String saveOrder(XpOrderDto dto, String uid);
    Boolean isInOperationTime(String operationStrategy);
    /**
     * 多策略路径规划合并、计价与快照
     */
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java
New file
@@ -0,0 +1,226 @@
package com.xiaopeng.itinerary.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.dto.order.OrderChangeDto;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpRoutePlan;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.vo.operation.RouteOptionVo;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.constant.I18nKeyConstant;
import com.xiaopeng.itinerary.mapper.XpElectronicFenceAreaMapper;
import com.xiaopeng.itinerary.mapper.XpElectronicFenceMapper;
import com.xiaopeng.itinerary.mapper.XpOpenCityMapper;
import com.xiaopeng.itinerary.mapper.XpRoutePlanMapper;
import com.xiaopeng.itinerary.navigation.MultiRouteSnapshotPayload;
import com.xiaopeng.itinerary.service.ActiveOrderService;
import com.xiaopeng.itinerary.service.XpPointService;
import com.xiaopeng.itinerary.service.XpPricingService;
import com.xiaopeng.itinerary.utils.map.GDMapGeocodingUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class ActiveOrderServiceImpl implements ActiveOrderService {
    @Resource
    private GDMapGeocodingUtil gdMapGeocodingUtil;
    @Resource
    private XpOpenCityMapper xpOpenCityMapper;
    @Resource
    private XpOrderClient xpOrderClient;
    @Resource
    private  XpPricingService xpPricingService;
    @Resource
    private XpMapClient xpMapClient;
    @Resource
    private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper;
    @Autowired
    private XpElectronicFenceMapper xpElectronicFenceMapper;
    @Autowired
    private XpPointService xpPointService;
    @Resource
    private XpPointClient xpPointClient;
    @Resource
    private RedisService redisService;
    @Resource
    private XpRoutePlanMapper xpRoutePlanMapper;
    /*
    public void getRouteAndFareParamValidate(RouteAndFareDto routeAndFareDto, String userId) {
        //获取订单信息
        R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(routeAndFareDto.getOrderId());
        if (xpOrderR.getCode() != R.SUCCESS || xpOrderR.getData() == null) {
            throw new ServiceException("订单不存在");
        }
        // 1. 基础数据查询
        Map<String, String> geocode = gdMapGeocodingUtil.geocode(routeAndFareDto.getOrigin().getLongitude().toString(), routeAndFareDto.getOrigin().getLatitude().toString());
        if(null == geocode){
            throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND);
        }
        Long cityCode = routeAndFareDto.getOpenCityId();
        XpOpenCity openCities = xpOpenCityMapper.selectById(cityCode);
        if (openCities == null ) {
            throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND);
        }
        XpPricing xpPricing = xpPricingService.getOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId()));
        if (xpPricing == null) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        routeAndFareDto.setRoutePlanRule(xpOrderR.getData().getRoutePlanRule());
        // 算距离
        R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        if (distanceAndDuration.getCode() != 200) {
            throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND);
        }
        RouteVo routeVo = distanceAndDuration.getData();
    }
     */
    @Override
    public void orderChange(OrderChangeDto dto, String uid){
        //获取订单信息
        R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(dto.getOrderId());
        if (xpOrderR.getCode() != R.SUCCESS || xpOrderR.getData() == null) {
            throw new ServiceException("订单不存在");
        }
        Integer orderChange = 2;//todo yyy 从配置文件读取 修改订单目的地次数
        XpOrder order = xpOrderR.getData();
        //判断订单修改的次数
        if(order.getOrderChange() > orderChange){
            throw new ServiceException("目的地修改次数已超上限");
        }
        // 是否在运营区域时间内
        XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(order.getXpElectronicFenceAreaId());
        XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId());
        String operationStrategy = xpElectronicFence.getOperationStrategy();
        Boolean inOperationTime = xpPricingService.isInOperationTime(operationStrategy);
        if (!inOperationTime && xpElectronicFence.getFailureType()!=1) {
            log.info("当前区域暂未开通,下单失败 {}", uid);
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        boolean inTime = xpPointClient.nowInTimePoint(dto.getEndPointId()).getData();
        if (!inTime) {
            throw new ServiceException("终点点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        // 终点
        XpPoint endXpPoint = xpPointService.getById(dto.getEndPointId());
        if(endXpPoint ==null || endXpPoint.getOperationStatus()!=2){
            throw new ServiceException("该站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT);
        }
        //修改订单数据
        XpOrder xpOrder = new XpOrder();
        xpOrder.setId(order.getId());
        xpOrder.setEndLon(endXpPoint.getPointLon());
        xpOrder.setEndLat(endXpPoint.getPointLat());
        xpOrder.setEndAddress(endXpPoint.getPointAddress());
        xpOrder.setEndPointId(dto.getEndPointId());
        xpOrder.setEndPointName(endXpPoint.getPointName());
        Long couponRef = dto.getCouponId();
        xpOrder.setCouponId(couponRef != null && couponRef > 0L ? couponRef : null);
        xpOrder.setUpdateTime(LocalDateTime.now());
        xpOrder.setOrderChange(order.getOrderChange()+1);
        xpOrderClient.updateOrder(xpOrder);
        //获取距离和时间
        this.getDD(uid,dto);
        //将修改信息存入到redis,
        // 获取起点经纬度
        XpPoint xpPoint = xpPointService.getById(order.getStartPointId());
        XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, order.getCityCode()));
        final Integer planRuleForQueue = dto.getSelectedRoutePlanRule() != null ? dto.getSelectedRoutePlanRule()
                : (xpRoutePlan != null ? xpRoutePlan.getRoutePlanRule() : null);
//        order.getId() + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + order.getOrderNumber() + "_" + dto.getDistance() + "_" + dto.getDuration() + "_" + null
        StringJoiner points = new StringJoiner("_");
        points.add(order.getId().toString());
        points.add(xpPoint.getPointLon() + "," + xpPoint.getPointLat());
        points.add("0");
        points.add(uid);
        points.add(order.getOrderNumber());
        points.add(dto.getDistance()+"");
        points.add(dto.getDuration()+"");
        String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat();
        if (planRuleForQueue == null) {
            redisService.addToTail(key,points+"_"+null);
        } else {
            redisService.addToTail(key,points+"_"+planRuleForQueue);
        }
        log.info("订单推单:{}   {}", key, redisService.getCacheList(key));
        xpOrder.setQueueName(key);
        xpOrderClient.updateOrder(xpOrder);
    }
    /**
     * 获取距离和时间
     * @param uid
     * @param dto
     */
    public void getDD (String uid, OrderChangeDto dto ){
        Integer distance = 0;
        Integer duration = 0;
        if (dto.getNavigationSnapshotId() != null && dto.getSelectedRouteId() != null) {
            String snap = redisService.getCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + uid + ":" + dto.getNavigationSnapshotId());
            if (snap != null) {
                MultiRouteSnapshotPayload snapPayload = JSON.parseObject(snap, MultiRouteSnapshotPayload.class);
                RouteOptionVo opt = snapPayload.getRoutes().stream()
                        .filter(r2 -> dto.getSelectedRouteId().equals(r2.getPathFingerprint()))
                        .findFirst()
                        .orElse(null);
                if (opt != null && opt.getRoute() != null) {
                    distance = opt.getRoute().getDistance();
                    duration = opt.getRoute().getDuration();
                    redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + uid, JSON.toJSONString(opt.getRoute()), 24L, TimeUnit.HOURS);
                    if (dto.getSelectedRoutePlanRule() == null) {
                        dto.setSelectedRoutePlanRule(opt.getPrimaryRoutePlanRule());
                    }
                }
            }
        }
        if (distance == 0 && duration == 0) {
            Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + uid);
            if (cacheObject != null) {
                RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
                distance = routeVo.getDistance();
                duration = routeVo.getDuration();
            }
        }
        dto.setDistance(distance);
        dto.setDuration(duration);
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -7,21 +7,21 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.account.GetUserCouponDto;
import com.xiaopeng.api.dto.map.OrderRouteAndFareDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.map.RouteStaleCheckDto;
import com.xiaopeng.api.dto.map.SelectNavigationRouteDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.enums.RouteStrategyKind;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.feignClient.account.XpAppUserClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.service.BootCommonService;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.operation.FareVo;
import com.xiaopeng.api.vo.operation.MultiRouteAndFareVo;
@@ -85,6 +85,8 @@
    private XpMapClient xpMapClient;
    @Resource
    private BootCommonService bootCommonService;
    @Resource
    private XpOpenCityMapper xpOpenCityMapper;
    @Resource
@@ -101,6 +103,8 @@
    @Resource
    private XpOrderClient xpOrderClient;
    @Resource
    private XpVehicleClient xpVehicleClient;
    @Resource
    private XpTradingStrategyMapper xpTradingStrategyMapper;
@@ -200,6 +204,17 @@
        return hour + "-" + (hour + 1);
    }
    public XpPricing getInfo(Long cityId,String vehicleModel){
        LambdaQueryWrapper<XpPricing> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(XpPricing::getDeleted, 0);
        queryWrapper.eq(XpPricing::getCityId, cityId);
        if(vehicleModel != null){
            queryWrapper.eq(XpPricing::getVehicleModel, vehicleModel);
        }
        return super.getOne(queryWrapper,true);
    }
    @Override
    public List<RouteAndFareVo> getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) {
@@ -213,7 +228,6 @@
//            RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class);
//            return (RouteAndFareVo);
//        }
        ArrayList<RouteAndFareVo> routeAndFareVos = new ArrayList<>(3);
        // 1. 基础数据查询
@@ -226,6 +240,15 @@
        if (openCities == null ) {
            throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND);
        }
        //根据订单获取车型 获取定价策略
        String vehicleModel = bootCommonService.getVehicleModelId(routeAndFareDto.getOrderId());
        XpPricing xpPricing = this.getInfo(routeAndFareDto.getOpenCityId(),vehicleModel);
        if (xpPricing == null) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        XpOpenCity xpOpenCity = openCities;
        routeAndFareDto.setOpenCityId(xpOpenCity.getId());
        List<XpRoutePlan> xpRoutePlan = xpRoutePlanMapper.selectList(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode));
@@ -233,7 +256,6 @@
        // 找出用户偏好
        Object cacheObject = redisService.getCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId);
        for (XpRoutePlan routePlan : xpRoutePlan) {
            if (xpRoutePlan != null) {
                routeAndFareDto.setRoutePlanRule(routePlan.getRoutePlanRule());
@@ -246,12 +268,6 @@
                    // keep city default
                }
            }
            XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId()));
            if (xpPricing == null) {
                throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
            }
            // 算距离
            R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
@@ -409,7 +425,7 @@
            // 11. 生成订单ID并缓存
            String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8);
            redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1l, TimeUnit.DAYS);
            redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1L, TimeUnit.DAYS);
            fareVo.setId(id);
            // 12. 设置费用信息
@@ -433,7 +449,7 @@
                }
            }
            routeAndFareVo.setRoutePlanName(routePlan.getPlanName());
            routeAndFareVo.setAmapStrategy(routePlan.getRoutePlanRule());
            routeAndFareVos.add(routeAndFareVo);
@@ -612,24 +628,23 @@
        xpOrder.setPlacementAddress(dto.getPlacementAddress());
        xpOrder.setCityId(dto.getOpenCityId());
        xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId());
        xpOrder.setStartPointId(dto.getStartPointId());
        xpOrder.setStartLon(xpPoint.getPointLon());
        xpOrder.setStartLat(xpPoint.getPointLat());
        xpOrder.setBoardingLon(xpPoint.getPointLon());
        xpOrder.setBoardingLat(xpPoint.getPointLat());
        xpOrder.setBoardingAddress(xpPoint.getPointAddress());
        xpOrder.setStartAddress(xpPoint.getPointAddress());
        xpOrder.setStartPointName(xpPoint.getPointName());
        xpOrder.setEndPointId(dto.getEndPointId());
        xpOrder.setEndLon(endXpPoint.getPointLon());
        xpOrder.setEndLat(endXpPoint.getPointLat());
        xpOrder.setStartAddress(xpPoint.getPointAddress());
        xpOrder.setEndAddress(endXpPoint.getPointAddress());
        xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId());
        xpOrder.setStartPointId(dto.getStartPointId());
        xpOrder.setEndPointId(dto.getEndPointId());
        xpOrder.setStartPointName(xpPoint.getPointName());
        xpOrder.setEndPointName(endXpPoint.getPointName());
        // 当前时段
        String currentTimeSlot = DateUtils.getCurrentTimeSlot();
@@ -732,7 +747,6 @@
                xpGeoQueryEntity.setStatus(0);
                xpGeoQueryEntity.setTimestamp(System.currentTimeMillis());
                geoQueryEntityService.save(xpGeoQueryEntity);
                return orderId.toString();
            }
        }
@@ -760,8 +774,8 @@
        return orderId.toString();
    }
    private Boolean isInOperationTime(String operationStrategy) {
    @Override
    public Boolean isInOperationTime(String operationStrategy) {
        JSONArray jsonArray = JSONArray.parseArray(operationStrategy);
        Integer holidayToday = holidayUtil.isHolidayToday();
        for (Object o : jsonArray) {
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java
@@ -300,5 +300,4 @@
        return R.ok();
    }
}
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpCruiseCheckInRuleServiceImpl.java
@@ -50,7 +50,7 @@
    @Override
    public AppXpCruiseCheckInRuleVo getMyXpCruiseCheckInRule(Long uid) {
        List<XpCruiseCheckInRule> list = this.list(new LambdaQueryWrapper<XpCruiseCheckInRule>().eq(XpCruiseCheckInRule::getDeleted, 0)
                .last(" and (now() BETWEEN start_time and end_time or a.cyclic_mode = 1)"));
                .last(" and (now() BETWEEN start_time and end_time or cyclic_mode = 1)"));
        if(list.isEmpty()){
            return null;
        }
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java
@@ -80,12 +80,19 @@
    }
    // 根据开通城市 获取定价
    @GetMapping("/logan/inner/v1/getPricingByCid")
    public R<XpPricing> getPricingByCid(@RequestParam Long cityId) {
        XpPricing pricing = xpPricingService.lambdaQuery().eq(XpPricing::getCityId, cityId).last("limit 1").one();
    public R<XpPricing> getPricingByCid(@RequestParam Long cityId,@RequestParam(required = false) String vehicleModelId) {
        LambdaQueryWrapper<XpPricing> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(XpPricing::getCityId, cityId);
        if(null != vehicleModelId){
            queryWrapper.eq(XpPricing::getVehicleModel, vehicleModelId);
        }
        XpPricing pricing = xpPricingService.getOne(queryWrapper);
        return R.ok(pricing);
    }
    @GetMapping("/base/v1/mini-program/pricing/rules/{openCityId}")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    @ApiOperation(value = "获取费用规则(弃用)", tags = {"小程序-根据开通城市获取费用规则(弃用)"}, response = String.class)
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/ActiveOrderController.java
New file
@@ -0,0 +1,146 @@
package com.xiaopeng.order.controller;
import com.xiaopeng.api.dto.QueryOrderRouteDTO;
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.order.model.vo.XpOrderDetailVO;
import com.xiaopeng.order.service.IXpOrderService;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
@RestController
@Api(tags = "订单")
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class ActiveOrderController {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Resource
    XpOrderController xpOrderController;
    @Resource
    private final IXpOrderService xpOrderService;
    @Resource
    private final XpPointClient xpPointClient;
    @Resource
    private final XpVehicleClient xpVehicleClient;
    @Nullable
    private R<XpOrder> getOrderTripEnd(String orderId) {
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if(xpOrder == null){
            return R.fail("订单不存在");
        }
        if(xpOrder.getTripEnd() != null){
            Duration duration = Duration.between(xpOrder.getTripEnd(), LocalDateTime.now());
            if(duration.toHours()>=24){
                return R.fail("行程结束已超过24小时查看行程信息,请联系客服");
            }
        }
        return R.ok(xpOrder);
    }
    /**
     * 分享行程接口1-订单查询实时轨迹路线
     */
    @ApiOperation(value = "分享行程接口1 订单查询实时轨迹路线")
    @GetMapping("/base/v1/mini-program/orderActive/share/queryOrderRoute")
    @XpClientCheck(checkToken = false, checkOtpToken = false)
    public R<RouteVo> queryOrderRoute(@RequestParam String orderId, HttpServletRequest request) {
        // 查询订单信息
        if(StringUtils.isBlank(orderId) ){
            return R.fail("参数不能为空");
        }
        R r = getOrderTripEnd(orderId);
        if (r != null && r.getCode() != 200) {
            return r;
        }
        QueryOrderRouteDTO dto = new QueryOrderRouteDTO();
        return xpOrderController.queryOrderRoute(dto,request);
    }
    /**
     * 分享行程接口2-订单查询轨迹路线
     */
    @ApiOperation(value = "分享行程接口2-订单查询轨迹路线")
    @GetMapping("/base/v1/mini-program/orderActive/share/queryOrderTrajectory")
    @XpClientCheck(checkToken = false, checkOtpToken = false)
    public R<RouteVo> queryOrderTrajectory(@RequestParam String orderId,HttpServletRequest request) {
        if(StringUtils.isBlank(orderId) ){
            return R.fail("参数不能为空");
        }
        R r = getOrderTripEnd(orderId);
        if (r != null && r.getCode() != 200) {
            return r;
        }
        return xpOrderController.queryOrderTrajectory(orderId,request);
    }
    /**
     * 分享行程接口3-订单信息
     */
    @ApiOperation(value = "分享行程接口3-订单查询实时轨迹路线")
    @GetMapping("/base/v1/mini-program/orderActive/share/orderInfo")
    @XpClientCheck(checkToken = false, checkOtpToken = false)
    public R<XpOrderDetailVO> shareOrderInfo(@RequestParam String orderId, HttpServletRequest request) {
        // 查询订单信息
        if(orderId == null){
            return R.fail("参数不能为空");
        }
        R<XpOrder> r = getOrderTripEnd(orderId);
        if (r != null && r.getCode() != 200) {
            return R.fail(r.getMsg());
        }
        XpOrder xpOrder = r.getData();
        XpOrderDetailVO xpOrderDetailVO = new XpOrderDetailVO();
        BeanUtils.copyProperties(xpOrder, xpOrderDetailVO);
        R<XpPoint> xpPointR = xpPointClient.queryById(xpOrder.getStartPointId());
        if (xpPointR.getCode() == R.SUCCESS) {
            XpPoint startPoint = xpPointR.getData();
            xpOrderDetailVO.setStartParkingTimeLimit(startPoint.getParkingTimeLimit());
        }
        R<XpPoint> xpPointR1 = xpPointClient.queryById(xpOrder.getEndPointId());
        if (xpPointR1.getCode() == R.SUCCESS) {
            XpPoint endPoint = xpPointR1.getData();
            xpOrderDetailVO.setEndParkingTimeLimit(endPoint.getParkingTimeLimit());
        }
        if (xpOrder.getVehicleId() != null) {
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId());
            XpVehicle vehicle = xpVehicleR.getData();
            xpOrderDetailVO.setVehicleId(vehicle.getId());
            xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin());
            xpOrderDetailVO.setVehicleModel(vehicle.getVehicleModel());
            xpOrderDetailVO.setVehicleColor(vehicle.getVehicleColor());
            xpOrderDetailVO.setPlateNumber(vehicle.getPlateNumber());
            xpOrderDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
        }
        return R.ok(xpOrderDetailVO);
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -51,6 +51,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -79,7 +80,7 @@
    private final RedisService redisService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    @Resource
    private XpVehicleClient xpVehicleClient;
    /**
@@ -106,7 +107,7 @@
        if (!list.isEmpty()) {
            return R.ok(list.get(0).getId());
        }
        return R.ok(0l);
        return R.ok(0L);
    }
@@ -391,6 +392,17 @@
        return R.ok(xpOrderService.getActiveOrder(uid));
    }
    /**
     * 获取正在进行中的订单_行程管理
     */
    @ApiOperation(value = "获取正在进行中的订单_行程管理", tags = {"小程序-获取正在进行中的订单_行程管理"})
    @GetMapping("/api/v1/mini-program/order/active_itineraryManage")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<XpOrderDetailVO> getActiveOrder_itineraryManage(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getActiveOrder_itineraryManage(uid));
    }
    /**
     * 查询历史订单列表
@@ -421,7 +433,6 @@
     * +
     * 取消订单
     */
    @PutMapping("/api/v1/mini-program/order/cancel/{orderId}")
    @ApiOperation(value = "取消订单", tags = {"小程序-取消订单"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
@@ -555,6 +566,9 @@
    @PostMapping("/api/v1/mini-program/order/queryOrderRoute")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<RouteVo> queryOrderRoute(@Validated @RequestBody QueryOrderRouteDTO dto, HttpServletRequest request) {
        if(dto.getOrderId() == null){
            return R.fail("参数不能为空");
        }
        String uid = request.getHeader("XP-Uid");
        // 查询订单信息
        XpOrder xpOrder = xpOrderService.getById(dto.getOrderId());
@@ -584,7 +598,9 @@
    @GetMapping("/api/v1/mini-program/order/queryOrderTrajectory")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<RouteVo> queryOrderTrajectory(@RequestParam String orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        if(orderId == null){
            return R.fail("参数不能为空");
        }
        List<OrderTrajectory> trajectories = mongoTemplate.find(
                new Query(Criteria.where("orderId").is(Long.valueOf(orderId)))
                        .with(Sort.by(Sort.Direction.ASC, "locatetime")),
@@ -621,4 +637,7 @@
        return R.ok((int)count);
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderWaypointController.java
New file
@@ -0,0 +1,51 @@
package com.xiaopeng.order.controller;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.order.XpOrderWaypoint;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.order.service.IXpOrderService;
import com.xiaopeng.order.service.IXpOrderWaypointService;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
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.RestController;
import javax.annotation.Resource;
@RestController
@Api(tags = "订单途经点")
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpOrderWaypointController {
    @Resource
    private final IXpOrderWaypointService xpOrderWaypointService;
    private final IXpOrderService xpOrderService;
    /**
     * 添加订单途径点
     */
    @PostMapping("/api/v1/mini-program/orderWaypoint/save")
    @ApiOperation(value = "添加订单途径点", tags = {"小程序-添加订单途径点"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R save(@Validated  @RequestBody XpOrderWaypoint entity) {
        XpOrder xpOrder = xpOrderService.getById(entity.getOrderId());
        if(xpOrder == null){
            return R.fail("订单不存在");
        }
        entity.setId(null);
        boolean b = xpOrderWaypointService.save(entity);
        if(b){
            return R.ok();
        }
        return R.fail("订单不存在");
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderWaypointMapper.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.order.XpOrderWaypoint;
/**
 * <p>
 * 订单支付记录 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpOrderWaypointMapper extends BaseMapper<XpOrderWaypoint> {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java
@@ -2,13 +2,12 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.model.order.XpOrderWaypoint;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -47,6 +46,8 @@
    @ApiModelProperty(value = "下单城市名称")
    private String cityName;
    @ApiModelProperty(value = "下单城市编码")
    private String cityCode;
    @ApiModelProperty(value = "预计时间")
@@ -131,6 +132,11 @@
    @ApiModelProperty(value = "车辆id")
    private Long vehicleId;
    @ApiModelProperty(value = "车辆vin码")
    private String vehicleVin;
    @ApiModelProperty(value = "车辆颜色")
    private String vehicleColor;
    @ApiModelProperty(value = "车牌号")
    private String plateNumber;
@@ -205,4 +211,8 @@
    }
    @ApiModelProperty(value = "后备箱状态(1=打开,2=关闭)")
    private Integer openTrunk;
    @ApiModelProperty(value = "途径点信息")
    private List<XpOrderWaypoint> xpOrderWaypoints;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderService.java
@@ -226,6 +226,8 @@
    R<String> feeDescription(Long orderId, String uid);
    XpOrderDetailVO getActiveOrder_itineraryManage(String uid);
    /**
     * 订单改价
     * @param id
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderWaypointService.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.order.XpOrderWaypoint;
/**
 * <p>
 * 订单支付记录 服务类
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface IXpOrderWaypointService extends IService<XpOrderWaypoint> {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -16,7 +16,7 @@
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.automaticPilot.VehicleEventDTO;
import com.xiaopeng.api.dto.map.OrderRouteAndFareDto;
import com.xiaopeng.api.dto.operation.CruisePointDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.dto.order.HistoryEndPointsDto;
@@ -27,18 +27,13 @@
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.dto.vehicleConvoy.UpdateVehicleStateDto;
import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
import com.xiaopeng.api.feignClient.automaticPilot.VehicleBuriedPointClient;
import com.xiaopeng.api.feignClient.maintenance.XpTaskRuleClient;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.operation.XpCruisePlanClient;
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.feignClient.operation.XpPricingClient;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.feignClient.operation.*;
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.*;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
@@ -46,6 +41,7 @@
import com.xiaopeng.api.model.order.*;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.service.BootCommonService;
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
@@ -92,7 +88,6 @@
import com.xiaopeng.order.utils.GDMapGeocodingUtil;
import com.xiaopeng.order.utils.TaxiPricingDemoService;
import com.xiaopeng.order.utils.RegionCodeFilterUtil;
import com.xiaopeng.order.utils.TaxiPricingDemoService;
import com.xiaopeng.order.utils.XpPricingUtil;
import com.xiaopeng.order.utils.holiday.HolidayUtil;
import com.xiaopeng.order.vo.OrderDetailVo;
@@ -163,6 +158,10 @@
    private final VehicleBuriedPointClient vehicleBuriedPointClient;
    private final IXpTestOrderService xpTestOrderService;
    private final XpMapClient xpMapClient;
    @Resource
    private  XpRoutePlanClient xpRoutePlanClient;
    @Resource
    private BootCommonService bootCommonService;
    @Autowired
@@ -1591,11 +1590,11 @@
    @ShardingSphereTransactionType(TransactionType.BASE)
    public void cancelOrder(Long orderId, String uid) {
        XpOrder order = this.getById(orderId);
        if (order.getStatus() == 0) {
            return;
        }
        if (order == null) {
            throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS);
        }
        if (order.getStatus() == 0) {
            return;
        }
        if (!order.getAppUserId().toString().equals(uid)) {
            throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS);
@@ -1603,7 +1602,6 @@
        if (order.getStatus() > 5 || order.getStatus() < 1) {
            throw new ServiceException("订单状态错误", I18nKeyConstant.ORDER_STATUS_ERROR);
        }
        try {
            redisService.removeByPattern(order.getQueueName(), order.getOrderNumber());
        } catch (Exception e) {
@@ -1625,39 +1623,20 @@
                }
            }
        }
        if (order.getStatus() == 1) {
            order.setStatus(0);
            order.setAppUserId(null);
            baseMapper.updateById(order);
            redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS);
            return;
        }
        /*
        order.setStatus(0);
        order.setAppUserId(null);
        baseMapper.updateById(order);
        redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS);
        XpOrderCancel xpOrderCancel = new XpOrderCancel();
        xpOrderCancel.setOrderId(orderId);
        xpOrderCancel.setUserId(Long.valueOf(uid));
        xpOrderCancel.setCancelSource(1);
        xpOrderCancel.setCancelReason("用户取消订单");
        xpOrderCancelService.save(xpOrderCancel);
        */
        // 移除mongo数据
        xpPricingClient.deleteXpGeoQueryEntity(order.getId().toString());
        // 下发命令
        if (null != order.getVehicleId()) {
        else{// 下发命令
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(order.getVehicleId());
            // 组装调用车机参数
            CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo();
            XpVehicle xpVehicle = xpVehicleR.getData();
            if (xpVehicle == null) {
                throw new ServiceException("车辆不存在", I18nKeyConstant.VEHICLE_NOT_EXISTS);
            }
            //是否能取消行程
            boolean cancelShow = this.isCancelShow(order,xpVehicle.getVehicleVin());
            if(cancelShow){
                throw new ServiceException("当前订单状态不允许取消,行程即将完成", I18nKeyConstant.ORDER_CANCEL_FAILED);
            }
            // 组装调用车机参数
            CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo();
            cancelTaxiPassengerVo.setVin(xpVehicle.getVehicleVin());
            cancelTaxiPassengerVo.setUid(uid);
            cancelTaxiPassengerVo.setOrderNo(order.getOrderNumber());
@@ -1688,7 +1667,19 @@
            //车辆继续巡游
            addCruiseMission(xpVehicle.getId());
        }
        order.setStatus(0);
        order.setAppUserId(null);
        baseMapper.updateById(order);
        redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS);
        XpOrderCancel xpOrderCancel = new XpOrderCancel();
        xpOrderCancel.setOrderId(orderId);
        xpOrderCancel.setUserId(Long.valueOf(uid));
        xpOrderCancel.setCancelSource(1);
        xpOrderCancel.setCancelReason("用户取消订单");
        xpOrderCancelService.save(xpOrderCancel);
        // 移除mongo数据
        xpPricingClient.deleteXpGeoQueryEntity(order.getId().toString());
    }
    @Override
@@ -2058,11 +2049,13 @@
        redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 8, 24L, TimeUnit.HOURS);
        // 根据订单所属开通城市id 查询定价
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId());
        XpPricing pricing = pricingByCityId.getData();
        XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(),order.getVehicleId());
        if (pricing == null) {
            throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS);
        }
        // 费用
        // 查询订单轨迹
        List<OrderTrajectory> trajectories = mongoTemplate.find(
@@ -2434,7 +2427,7 @@
     *
     * @param order
     */
    @Override
    public void addCruiseMission(Long vehicleId, XpOrder order) {
        XpVehicle vehicle = xpVehicleClient.queryById(vehicleId).getData();
        log.info("添加巡游订单 {}", vehicle.getPlateNumber());
@@ -3136,12 +3129,11 @@
    @Override
    public R<String> feeDescription(Long orderId, String uid) {
        XpOrder order = this.getById(orderId);
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId());
        XpPricing pricing = pricingByCityId.getData();
        if (pricing == null) {
        XpPricing xpPricing = bootCommonService.getXpPricing(order.getCityId(), order.getVehicleId());
        if (xpPricing == null) {
            throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS);
        } else {
            return R.ok(pricing.getPriceRule());
            return R.ok(xpPricing.getPriceRule());
        }
    }
@@ -3478,6 +3470,9 @@
        if (xpOrder.getVehicleId() != null) {
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId());
            XpVehicle vehicle = xpVehicleR.getData();
            xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin());
            xpOrderDetailVO.setVehicleModel(vehicle.getVehicleModel());
            xpOrderDetailVO.setVehicleColor(vehicle.getVehicleColor());
            xpOrderDetailVO.setPlateNumber(vehicle.getPlateNumber());
            xpOrderDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
        }
@@ -3489,24 +3484,118 @@
            xpOrder.setAppUserId(null);
            this.updateById(xpOrder);
        }
        Boolean cancelShow = true; //是否能取消行程
        xpOrderDetailVO.setCancelShow(cancelShow);
        return xpOrderDetailVO;
    }
    @Override
    public XpOrderDetailVO getActiveOrder_itineraryManage(String uid){
        XpOrderDetailVO xpOrderDetailVO = new XpOrderDetailVO();
        XpOrder xpOrder = this.baseMapper.selectOne(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getAppUserId, Long.parseLong(uid)).in(XpOrder::getStatus, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)).orderByDesc(BaseModel::getCreateTime).last("LIMIT 1"));
        if (xpOrder == null) {
            return null;
        }
        BeanUtils.copyProperties(xpOrder, xpOrderDetailVO);
        if (xpOrder.getVehicleId() != null) {
            R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId());
            XpVehicle vehicle = xpVehicleR.getData();
            xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin());
            xpOrderDetailVO.setVehicleModel(vehicle.getVehicleModel());
            xpOrderDetailVO.setVehicleColor(vehicle.getVehicleColor());
            xpOrderDetailVO.setPlateNumber(vehicle.getPlateNumber());
            xpOrderDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
        }
        Boolean cancelShow = isCancelShow(xpOrder,xpOrderDetailVO.getVehicleVin()); //是否能取消行程
        xpOrderDetailVO.setCancelShow(cancelShow);
        return xpOrderDetailVO;
    }
    /**
     * 取消行程:若处于“送驾中”且离目的地小于10分钟路程(后台配置),【取消行程】按钮应置灰;
     * 修改行程:
     * 1. 当前剩余里程 < 800米 (后台配置)或 剩余时间 < 5分钟(后台配置)(以先到者为准),禁止修改,按钮置灰。
     * 2. 单次行程最多允许修改 2次 (后台配置)目的地,防恶意高频调用
     * 特殊豁免:若系统检测到车辆在当前坐标时速 < 3km/h 且持续时间 > 3分钟(后台配置)(判定为极度拥堵或异常停滞),此时解除限制,允许用户修改目的地或新增途径点以寻找更近的下车位置。
     * @return
     */
    private Boolean isCancelShow(){
    private boolean isCancelShow(XpOrder xpOrder,String vehicleVin) {
        boolean isCancel = false;
        //获取配置文件
        int sylc = 800;//单位米 todo yyy 需要读取后台配置文件
        int sysj = 5;//单位分钟 todo yyy 需要读取后台配置文件
        // 减少3分钟 获取3分账内的 行程数据
        long minute =3;//todo yyy 需要读取后台配置文件
        return true;
        List<OrderTrajectory> orderTrajectories = queryOrderTrajectories(xpOrder.getId());
        if(!orderTrajectories.isEmpty()){// 减少3分钟 获取3分账内的 行程数据
            //3千米/小时 转换成分钟 50米/分钟
            BigDecimal suduDefault = new BigDecimal("3");//3千米/小时
            BigDecimal suduDefault1 = suduDefault.multiply(BigDecimal.valueOf(1000)) ;
            BigDecimal suduDefaultNew =suduDefault1.divide(BigDecimal.valueOf(60),2,RoundingMode.HALF_EVEN);
            LocalDateTime minutesBefore = LocalDateTime.now().minusMinutes(minute);
            long milliseconds = DateUtils.getMilliseconds(minutesBefore);
            List<OrderTrajectory> orderTrajectoriesNew = new ArrayList<>();
            for (OrderTrajectory orderTrajectory : orderTrajectories) {
                if(orderTrajectory.getLocatetime() > milliseconds){
                    orderTrajectoriesNew.add(orderTrajectory);
                }
            }
            /*
            获取最近3分钟的 数据 速度、平均
             */
            //计算行驶总距离和时长
            Double reduce = orderTrajectoriesNew.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum);
            BigDecimal xcsudu = new BigDecimal(reduce).divide(new BigDecimal(minute),2, RoundingMode.HALF_EVEN);//米/分钟
            //特殊豁免:若系统检测到车辆在当前坐标时速 < 3km/h 且持续时间 > 3分钟(后台配置)(判定为极度拥堵或异常停滞),此时解除限制,允许用户修改目的地或新增途径点以寻找更近的下车位置。
            if(xcsudu.compareTo(suduDefaultNew)<0){
                return true;
            }
        }
        RouteVo routeVo = this.getRouteVo(xpOrder, vehicleVin);
        if(routeVo.getDistance() <sylc){
            isCancel = true;
        }
        if(routeVo.getDuration() < sysj){
            isCancel = true;
        }
        return isCancel;
    }
    /**
     * 获取两个点位车的距离 和时间
     * @param xpOrder
     * @param vehicleVin
     * @return
     */
    private RouteVo getRouteVo(XpOrder xpOrder,String vehicleVin){
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vehicleVin)), VehiclePosition.class);
        // 计算距离 和 时间
        RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
        LonLatDto lonLatDto = new LonLatDto();
        lonLatDto.setLongitude(vehiclePosition.getPoint().getX());//经度
        lonLatDto.setLatitude(vehiclePosition.getPoint().getY());
        routeAndFareDto.setOrigin(lonLatDto);
        LonLatDto lonLatDto1 = new LonLatDto();
        lonLatDto1.setLongitude(xpOrder.getEndLon());
        lonLatDto1.setLatitude(xpOrder.getEndLat());
        routeAndFareDto.setDestination(lonLatDto1);
//        XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(dto.getOpenCityId());
//        xpRoutePlanClient.
        XpRoutePlan xpRoutePlan = xpRoutePlanClient.getXpRoutePlanOpenCityId(xpOrder.getCityId()).getData();
        if (xpRoutePlan != null) {
            routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
        }
        R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        if(distanceAndDuration.getCode()!=200){
            throw new ServiceException("未找到路径", I18nKeyConstant.PATH_NOT_FOUND);
        }
        return distanceAndDuration.getData();
    }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderWaypointServiceImpl.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.model.order.XpOrderWaypoint;
import com.xiaopeng.order.mapper.XpOrderWaypointMapper;
import com.xiaopeng.order.service.IXpOrderWaypointService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 订单支付记录 服务实现类
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
@Service
public class XpOrderWaypointServiceImpl extends ServiceImpl<XpOrderWaypointMapper, XpOrderWaypoint> implements IXpOrderWaypointService {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpTestOrderServiceImpl.java
@@ -9,12 +9,10 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableMap;
import com.xiaopeng.api.dto.account.GetUserCouponDto;
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.dto.pushMessage.SendMessageDTO;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
@@ -31,16 +29,14 @@
import com.xiaopeng.api.model.order.*;
import com.xiaopeng.api.model.vehicleConvoy.XpFleet;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.service.BootCommonService;
import com.xiaopeng.api.vo.OrderType;
import com.xiaopeng.api.vo.SystemEventType;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.automaticPilot.protobuf.OrderOperationType;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.FareBreakdownDetailVo;
import com.xiaopeng.api.vo.order.XpPricingCardVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.constant.Constants;
import com.xiaopeng.common.core.constant.HttpStatus;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
@@ -54,7 +50,6 @@
import com.xiaopeng.order.constant.I18nKeyConstant;
import com.xiaopeng.order.job.base.QuartzManager;
import com.xiaopeng.order.job.base.TimeJobType;
import com.xiaopeng.order.job.jobs.AddCruiseMissionJob;
import com.xiaopeng.order.job.jobs.EndTripJob;
import com.xiaopeng.order.job.jobs.OrderCancelJob;
import com.xiaopeng.order.mangodb.model.OrderTrajectory;
@@ -88,14 +83,13 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -145,6 +139,8 @@
    private final XpTestPointClient testPointClient;
    @Resource
    private BootCommonService bootCommonService;
@@ -192,7 +188,7 @@
        if (openCity == null) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        XpPricing xpPricing = xpPricingClient.getPricingByCid(openCity.getId()).getData();
        XpPricing xpPricing = bootCommonService.getXpPricing(openCity.getId(),null);
        if (xpPricing == null) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
@@ -995,8 +991,7 @@
        redisService.setCacheObject("TEST_ORDER_STATUS:" + order.getId(), 8, 24L, TimeUnit.HOURS);
        // 根据订单所属开通城市id 查询定价
        R<XpPricing> pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId());
        XpPricing pricing = pricingByCityId.getData();
        XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(),order.getVehicleId());
        if (pricing == null) {
            throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS);
        }
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/SubscribeMessageController.java
New file
@@ -0,0 +1,51 @@
package com.xiaopeng.pushMessage.controller;
import com.aliyun.core.annotation.Path;
import com.xiaopeng.api.feignClient.account.UserSubscribeMessageClient;
import com.xiaopeng.api.vo.pushMessage.SendSubscribeMessageVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.pushMessage.utils.SubscribeMessageUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
@RestController
@RequestMapping("")
public class SubscribeMessageController {
    @Resource
    private SubscribeMessageUtil subscribeMessageUtil;
    @Resource
    private UserSubscribeMessageClient userSubscribeMessageClient;
    /**
     * 根据微信jscode获取openid
     * @param code
     * @return
     */
    @GetMapping("/logan/inner/v1/subscribeMessage/getOpenId/{code}")
    public R<String> getOpenId(@PathVariable("code") String code){
        Map<String, Object> map = subscribeMessageUtil.code2Session(code);
        if(null != map){
            return R.ok(map.get("openid").toString());
        }
        return R.fail("获取openid失败");
    }
    /**
     * 发送订阅消息
     * @param vo
     * @return
     */
    @PostMapping("/logan/inner/v1/subscribeMessage/sendSubscribeMessage")
    public R<?> sendSubscribeMessage(@RequestBody SendSubscribeMessageVo vo){
        String openid = userSubscribeMessageClient.getUserOpenId(vo.getUserId().intValue()).getData();
        return subscribeMessageUtil.send(vo.getTemplateId(), vo.getPage(), openid, vo.getJson(), "developer", "zh_CN");
    }
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/Category.java
New file
@@ -0,0 +1,18 @@
package com.xiaopeng.pushMessage.model;
import lombok.Data;
/**
 * 订阅消息类目
 */
@Data
public class Category {
    /**
     * 类目id
     */
    private String id;
    /**
     * 类目名称
     */
    private String name;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/KeywordEnum.java
New file
@@ -0,0 +1,17 @@
package com.xiaopeng.pushMessage.model;
import lombok.Data;
import java.util.List;
@Data
public class KeywordEnum {
    /**
     * 枚举参数的 key
     */
    private String keywordCode;
    /**
     * 枚举参数值范围列表
     */
    private List<String> enumValueList;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/MessageTemplate.java
New file
@@ -0,0 +1,36 @@
package com.xiaopeng.pushMessage.model;
import lombok.Data;
import java.util.List;
/**
 * 消息模板
 */
@Data
public class MessageTemplate {
    /**
     * 添加至帐号下的模板 id,发送小程序订阅消息时所需
     */
    private String priTmplId;
    /**
     * 模版标题
     */
    private String title;
    /**
     * 模版内容
     */
    private String content;
    /**
     * 模板内容示例
     */
    private String example;
    /**
     * 模版类型,2 为一次性订阅,3 为长期订阅
     */
    private Integer type;
    /**
     * 枚举参数值范围
     */
    private List<KeywordEnum> keywordEnumValueList;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/PubTemplateKeywords.java
New file
@@ -0,0 +1,26 @@
package com.xiaopeng.pushMessage.model;
import lombok.Data;
/**
 * 公共消息模板关键字
 */
@Data
public class PubTemplateKeywords {
    /**
     * 关键词 id,选用模板时需要
     */
    private Integer kid;
    /**
     * 关键词内容
     */
    private String name;
    /**
     * 关键词内容对应的示例
     */
    private String example;
    /**
     * 参数类型
     */
    private String rule;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/model/PubTemplatetitles.java
New file
@@ -0,0 +1,26 @@
package com.xiaopeng.pushMessage.model;
import lombok.Data;
/**
 * 功能模板
 */
@Data
public class PubTemplatetitles {
    /**
     * 模版标题 id
     */
    private Integer tid;
    /**
     * 模版标题
     */
    private String title;
    /**
     * 模版类型,2 为一次性订阅,3 为长期订阅
     */
    private Integer type;
    /**
     * 模版所属类目 id
     */
    private Integer categoryId;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/SubscribeMessageUtil.java
New file
@@ -0,0 +1,301 @@
package com.xiaopeng.pushMessage.utils;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.pushMessage.model.Category;
import com.xiaopeng.pushMessage.model.MessageTemplate;
import com.xiaopeng.pushMessage.model.PubTemplateKeywords;
import com.xiaopeng.pushMessage.model.PubTemplatetitles;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * 订阅消息
 */
@Slf4j
@Component
public class SubscribeMessageUtil {
    @Value("${wx.appletsAppid}")
    private String wxAppletsAppid;
    @Value("${wx.appletsAppSecret}")
    private String wxAppletsAppSecret;
    @Autowired
    private RedisService redisService;
    /**
     * 获取微信小程序token
     * @return
     */
    public String getWxAppletsAccessToken() {
        String accessToken = redisService.getCacheObject("wxAppletsAccessToken");
        if(StringUtils.isNotEmpty(accessToken)){
            return accessToken;
        }
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + wxAppletsAppid + "&secret=" + wxAppletsAppSecret;
        HttpRequest get = HttpUtil.createGet(url);
        HttpResponse execute = get.execute();
        if(execute.getStatus() != 200){
            log.info("获取微信小程序access_token失败:{}", execute.body());
            throw new RuntimeException("获取微信小程序access_token失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        redisService.setCacheObject("wxAppletsAccessToken", jsonObject.getString("access_token"), 7000L, TimeUnit.SECONDS);
        return jsonObject.getString("access_token");
    }
    /**
     * 小程序使用jscode获取openid
     * @param jscode
     * @return
     */
    public Map<String, Object> code2Session(String jscode) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppletsAppid + "&secret=" + wxAppletsAppSecret
                + "&js_code=" + jscode + "&grant_type=authorization_code";
        HttpRequest get = HttpUtil.createGet(url);
        HttpResponse execute = get.execute();
        if(execute.getStatus() != 200){
            log.info("小程序使用jscode获取openid失败:{}", execute.body());
            throw new RuntimeException("小程序使用jscode获取openid失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        int errcode = jsonObject.getIntValue("errcode");
        Map<String, Object> map = new HashMap<>();
        if(errcode == 0){//成功
            map.put("openid", jsonObject.getString("openid"));
            map.put("sessionKey", jsonObject.getString("session_key"));
            map.put("unionid", jsonObject.getString("unionid"));
            return map;
        }
        throw new RuntimeException(jsonObject.getString("errmsg"));
    }
    /**
     * 获取消息类目
     * @return
     */
    public List<Category> getCategory(){
        String url = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory?access_token=" + getWxAppletsAccessToken();
        HttpRequest get = HttpUtil.createGet(url);
        HttpResponse execute = get.execute();
        if(execute.getStatus() != 200){
            log.info("获取消息类目失败:{}", execute.body());
            throw new RuntimeException("获取消息类目失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("获取消息类目失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        JSONArray data = jsonObject.getJSONArray("data");
        List<Category> categories = data.toJavaList(Category.class);
        return categories;
    }
    /**
     * 获取所属类目下的公共模板
     * @param ids
     * @return
     */
    public List<PubTemplatetitles> getPubTemplateTitles(String ids){
        List<PubTemplatetitles> pubTemplatetitles = new ArrayList<>();
        Integer start = 0;
        while (true){
            String url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles?access_token=" + getWxAppletsAccessToken();
            Map<String, Object> params = new HashMap<>();
            params.put("ids", ids);
            params.put("start", start + "");
            params.put("limit", 30);
            HttpRequest get = HttpUtil.createGet(url);
            get.form(params);
            HttpResponse execute = get.execute();
            if(execute.getStatus() != 200){
                log.info("获取公共模板失败:{}", execute.body());
                throw new RuntimeException("获取公共模板失败");
            }
            JSONObject jsonObject = JSON.parseObject(execute.body());
            Integer errcode = jsonObject.getInteger("errcode");
            if(0 != errcode){
                log.info("获取公共模板失败:{}", jsonObject.getString("errmsg"));
                throw new RuntimeException(jsonObject.getString("errmsg"));
            }
            JSONArray data = jsonObject.getJSONArray("data");
            List<PubTemplatetitles> pubTemplatetitles1 = data.toJavaList(PubTemplatetitles.class);
            if(pubTemplatetitles1.size() == 0){
                break;
            }
            pubTemplatetitles.addAll(pubTemplatetitles1);
            start += 30;
        }
        return pubTemplatetitles;
    }
    /**
     * 获取关键词列表
     * @param tid
     * @return
     */
    public List<PubTemplateKeywords> getPubTemplateKeywords(String tid){
        String url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords?access_token=" + getWxAppletsAccessToken();
        Map<String, Object> params = new HashMap<>();
        params.put("tid", tid);
        HttpRequest get = HttpUtil.createGet(url);
        get.form(params);
        HttpResponse execute = get.execute();
        if(execute.getStatus() != 200){
            log.info("获取关键词列表失败:{}", execute.body());
            throw new RuntimeException("获取关键词列表失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("获取关键词列表失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        JSONArray data = jsonObject.getJSONArray("data");
        List<PubTemplateKeywords> pubTemplateKeywords = data.toJavaList(PubTemplateKeywords.class);
        return pubTemplateKeywords;
    }
    /**
     * 获取个人模板列表
     * @return
     */
    public List<MessageTemplate> getMessageTemplateList(){
        String url = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=" + getWxAppletsAccessToken();
        HttpRequest get = HttpUtil.createGet(url);
        HttpResponse execute = get.execute();
        if(execute.getStatus() != 200){
            log.info("获取个人模板列表失败:{}", execute.body());
            throw new RuntimeException("获取个人模板列表失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("获取个人模板列表失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        JSONArray data = jsonObject.getJSONArray("data");
        List<MessageTemplate> messageTemplates = data.toJavaList(MessageTemplate.class);
        return messageTemplates;
    }
    /**
     * 添加消息模板
     * @param tid           模板标题 id
     * @param kidList       开发者自行组合好的模板关键词列表,关键词顺序可以自由搭配(例如 [3,5,4] 或 [4,5,3]),最多支持5个,最少2个关键词组合
     * @param sceneDesc     服务场景描述,15个字以内
     * @return
     */
    public String addTemplate(String tid, String[] kidList, String sceneDesc){
        String url = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate?access_token=" + getWxAppletsAccessToken();
        Map<String, Object> params = new HashMap<>();
        params.put("tid", tid);
        params.put("kidList", kidList);
        params.put("sceneDesc", sceneDesc);
        HttpRequest post = HttpUtil.createPost(url);
        post.body(JSON.toJSONString(params));
        HttpResponse execute = post.execute();
        if(execute.getStatus() != 200){
            log.info("添加消息模板失败:{}", execute.body());
            throw new RuntimeException("添加消息模板失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("添加消息模板失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        String priTmplId = jsonObject.getString("priTmplId");
        return priTmplId;
    }
    /**
     * 删除模板
     * @param priTmplId     模板id
     * @return
     */
    public R delTemplate(String priTmplId){
        String url = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate?access_token=" + getWxAppletsAccessToken();
        Map<String, Object> params = new HashMap<>();
        params.put("priTmplId", priTmplId);
        HttpRequest post = HttpUtil.createPost(url);
        post.body(JSON.toJSONString(params));
        HttpResponse execute = post.execute();
        if (execute.getStatus() != 200){
            log.info("删除模板失败:{}", execute.body());
            throw new RuntimeException("删除模板失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("删除模板失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        return R.ok();
    }
    /**
     * 发送消息
     * @param template_id           所需下发的订阅模板id
     * @param page                  点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转
     * @param touser                接收者(用户)的 openid
     * @param data                  模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }的object
     * @param miniprogram_state     跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
     * @param lang                  进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
     * @return
     */
    public R send(String template_id, String page, String touser, String data, String miniprogram_state, String lang){
        String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + getWxAppletsAccessToken();
        Map<String, Object> params = new HashMap<>();
        params.put("template_id", template_id);
        params.put("page", page);
        params.put("touser", touser);
        params.put("data", JSON.parseObject(data));
        params.put("miniprogram_state", miniprogram_state);
        params.put("lang", lang);
        HttpRequest post = HttpUtil.createPost(url);
        post.body(JSON.toJSONString(params));
        HttpResponse execute = post.execute();
        if(execute.getStatus() != 200){
            log.info("发送消息失败:{}", execute.body());
            throw new RuntimeException("发送消息失败");
        }
        JSONObject jsonObject = JSON.parseObject(execute.body());
        Integer errcode = jsonObject.getInteger("errcode");
        if(0 != errcode){
            log.info("发送消息失败:{}", jsonObject.getString("errmsg"));
            throw new RuntimeException(jsonObject.getString("errmsg"));
        }
        return R.ok();
    }
}