xuhy
2026-02-24 251bb514612312ab8f5de405fcad2c7889d5d43b
Merge remote-tracking branch 'origin/master'
37个文件已添加
18个文件已修改
2969 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingDetailVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/config/MyLocaleResolver.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/constant/I18nKeyConstant.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceAreaMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceMapper.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpOpenCityMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPointMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPricingMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpRoutePlanMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpSystemSettingMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpTradingStrategyMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpWhitelistMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/repository/XpGeoQueryEntityRepository.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/IXpGeoQueryEntityService.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPointService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpGeoQueryEntityServiceImpl.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPointServiceImpl.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 668 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/TimeRangeChecker.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java 749 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/holiday/HolidayUtil.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/mango/VehiclePositionService.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/FastDistanceCalculator.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/GeodesyUtil.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/vo/SaveOrderVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_en_US.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_zh_CN.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceAreaMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpOpenCityMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPointMapper.xml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPricingMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpRoutePlanMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpSystemSettingMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpTradingStrategyMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpWhitelistMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointManagementController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpSystemSettingController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java
@@ -70,6 +70,12 @@
            }
            @Override
            public R deletePoint(String ids) {
                log.error("删除点位失败:{}", cause.getMessage());
                return R.fail("删除点位失败");
            }
            @Override
            public R enableDisable(XpPointEnableDisableDto dto) {
                log.error("修改点位运营状态失败:{}", cause.getMessage());
                return R.fail("修改点位运营状态失败");
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java
@@ -53,6 +53,11 @@
     */
    @GetMapping("/api/v1/point/queryPointById")
    public R<XpPoint> queryById(@RequestParam Long id);
    /**
     * 点位删除
     */
    @GetMapping("/api/v1/point/deletePoint")
    R deletePoint(@RequestParam("ids") String ids);
    /**
     * 启用/禁用
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingDetailVo.java
New file
@@ -0,0 +1,19 @@
package com.xiaopeng.api.vo.operation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
@lombok.Data
@ApiModel(description = "计价规则时段Vo")
public class PricingDetailVo {
    @ApiModelProperty(value = "时段 1普通 2夜间行驶 3高峰期 4节假日")
    private Integer type;
    @ApiModelProperty(value = "金额")
    private BigDecimal amount;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingVo.java
New file
@@ -0,0 +1,26 @@
package com.xiaopeng.api.vo.operation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
@lombok.Data
@ApiModel(description = "计价规则Vo")
public class PricingVo {
    @ApiModelProperty(value = "起步价")
    private Double startAmount;
    @ApiModelProperty(value = "起步价公里")
    private Double startMileage;
    @ApiModelProperty(value = "起步价分钟")
    private Double startMinute;
    @ApiModelProperty(value = "起步价时段")
    private List<PricingDetailVo> startTimes;
    @ApiModelProperty(value = "里程费时段")
    private List<PricingDetailVo> mileageTimes;
    @ApiModelProperty(value = "时长费")
    private List<PricingDetailVo> durationTimes;
    @ApiModelProperty(value = "远途费")
    private List<PricingDetailVo> longDistanceTimes;
}
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java
@@ -54,7 +54,7 @@
        long sequence = snowFlake.nextId();
        MqttPublishDto mqttPublishDto = xpuUtil.cloudCreateOrder(task, dto.getVin(),sequence);
        if (mqttPublishDto.getCode()== 200) {
            // 修改车子为微单状态
            // 修改车子为接单状态
            xpVehicleClient.updateVehicleStatus(new UpdateVehicleStateDto(dto.getVin(),1));
            return R.ok(true);
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -91,7 +91,6 @@
    private UserVehicleClient userVehicleClient;
    /**
     * 消费消息 - XPU上报Robotaxi云端的消息topic(高优先级)
     *
@@ -115,7 +114,7 @@
     * @param msgContent
     * @param message
     */
    public void handleXPUMessage(MsgContent msgContent, String message){
    public void handleXPUMessage(MsgContent msgContent, String message) {
        RtMsgHeader header = msgContent.getHeader();
        MessageType type = header.getType();
        //处理请求消息
@@ -186,15 +185,15 @@
        }
        //消息应答
        if(MessageType.MESSAGE_TYPE_RESPONSE == type){
        if (MessageType.MESSAGE_TYPE_RESPONSE == type) {
            com.xiaopeng.api.vo.automaticPilot.proto.up.ResponseMessage resposne = msgContent.getContent_detail().getObject("resposne", com.xiaopeng.api.vo.automaticPilot.proto.up.ResponseMessage.class);
            com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode code = resposne.getCode();
            Long requestSeq = resposne.getRequest_seq();
            if(code == com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode.OK){
            if (code == com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode.OK) {
                Object sequence = redisTemplate.opsForValue().get(requestSeq);
                if(sequence!=null){
                if (sequence != null) {
                    String[] split = sequence.toString().split("_");
                    if(split[0].equals("pushOrder")){
                    if (split[0].equals("pushOrder")) {
                        PopOrderDto dto = new PopOrderDto();
                        dto.setOrderId(Long.valueOf(split[1]));
                        dto.setXpVehicleVin(split[2]);
@@ -204,9 +203,6 @@
            }
        }
    }
    /**
@@ -314,22 +310,31 @@
                userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo);
            }
            // 取消订单
            R<?> r = xpOrderClient.cancelOrder(String.valueOf(xpOrder.getId()));
            if (r.getCode() == R.SUCCESS){
                log.info("取消订单成功");
                // TODO 重新派单
                ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO();
                reassignOrderDTO.setUid(xpOrder.getAppUserId().toString());
                reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber());
                reassignOrderDTO.setCount(10);
                RealtimePose pose = systemRuntimeStatus.getPose();
                Wgs naviPose = pose.getNavi_pose();
                Double latitude = naviPose.getLatitude();
                Double longitude = naviPose.getLongitude();
                reassignOrderDTO.setLongitude(longitude);
                reassignOrderDTO.setLatitude(latitude);
                reassignOrderClient.reassignOrder(reassignOrderDTO);
            }
//            R<?> r = xpOrderClient.cancelOrder(String.valueOf(xpOrder.getId()));
//            if (r.getCode() == R.SUCCESS){
            log.info("取消订单成功");
            // TODO 重新派单
            ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO();
            reassignOrderDTO.setUid(xpOrder.getAppUserId().toString());
            reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber());
            reassignOrderDTO.setCount(10);
            RealtimePose pose = systemRuntimeStatus.getPose();
            Wgs naviPose = pose.getNavi_pose();
            Double latitude = naviPose.getLatitude();
            Double longitude = naviPose.getLongitude();
            reassignOrderDTO.setLongitude(longitude);
            reassignOrderDTO.setLatitude(latitude);
            reassignOrderClient.reassignOrder(reassignOrderDTO);
//            }
        }
    }
@@ -556,9 +561,9 @@
        snowFlake.withStartTimestamp(1420041600000L);
        long sequence = snowFlake.nextId();
        // 处理数据
        redisTemplate.opsForValue().set(sequence,"pushOrder_"+orderId+"_"+vin,30, TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(sequence, "pushOrder_" + orderId + "_" + vin, 30, TimeUnit.MINUTES);
        //开发发送请求到XPU
        xpuUtil.cloudOrderOperation(orderOperation, vin,sequence);
        xpuUtil.cloudOrderOperation(orderOperation, vin, sequence);
    }
@@ -662,12 +667,13 @@
     * @param orderId
     * @param status
     */
    public void buildPushOrderStatus(String orderId, OrderStatusEnum status, Long sequence){
        if(orderId.contains("XU")){
    public void buildPushOrderStatus(String orderId, OrderStatusEnum status, Long sequence) {
        if (orderId.contains("XU")) {
            //巡游单
            //响应消息
            // TODO 1.处理结束当前巡游订单  2.创建新的巡游订单 返航  3.调用xpu车机接口  (判断是返航还是正常  返航需要存储之前的订单id)
            responseMessage(sequence, "");
        }else{
        } else {
            //普通单
            R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId));
            XpOrder xpOrder = xpOrderR.getData();
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/config/MyLocaleResolver.java
@@ -24,6 +24,9 @@
        if (StringUtils.isEmpty(language)) {
            locale = Locale.getDefault();
        }
        if(language==null){
            language="1";
        }
        //如果请求不为空 
        //If the request is not empty.
        if (language.equals("1")) {
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/constant/I18nKeyConstant.java
New file
@@ -0,0 +1,56 @@
package com.xiaopeng.itinerary.constant;
/**
 * @author MiTao
 * @date 2026-02-06 17:34
 *
 */
public interface I18nKeyConstant {
    String CITY_INFORMATION_DOES_NOT_EXIST = "city.information.does.not.exist";
    String NO_CURRENT_REGION = "no.current.region";
    String NO_CURRENT_POINT = "no.current.point";
    String SYSTEM_EXCEPTION = "system.exception";
    String TO_WORK = "to.work";
    String WAITING_TIME = "wait.time";
    // 编辑电子围栏失败
    String EDIT_ELECTRONIC_ERROR = "edit.electronic.error";
    // 添加电子围栏失败
    String ADD_ELECTRONIC_ERROR = "add.electronic.error";
    // 区域重叠
    String AREA_OVERLAP = "area.overlap";
    // 围栏构成不存在
    String FENCE_STRUCTURE_NOT_EXIST = "fence.structure.not.exist";
    // 围栏不存在
    String FENCE_NOT_EXIST = "fence.not.exist";
    // 围栏启用状态不可复制
    String FENCE_STATUS_CANNOT_COPIED = "fence.status.cannot.copied";
    // 订单创建失败
    String ORDER_CREATE_FAIL = "order.create.fail";
    //开通城市已存在
    String OPEN_CITY_EXISTS = "open.city.exists";
    //定价已存在
    String PRICING_EXISTS = "pricing.exists";
    //定价不存在
    String PRICING_NOT_EXIST = "pricing.not.exist";
    //价格配置json格式错误
    String PRICING_JSON_INVALID = "pricing.json.invalid";
    //价格规则不能为空
    String PRICING_RULE_EMPTY = "pricing.rule.empty";
    // 路径不存在
    String PATH_NOT_FOUND = "path.not.found";
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java
New file
@@ -0,0 +1,103 @@
package com.xiaopeng.itinerary.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.itinerary.dto.XpOrderDto;
import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService;
import com.xiaopeng.itinerary.service.XpPricingService;
import com.xiaopeng.itinerary.vo.SaveOrderVo;
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.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
 * <p>
 * 费项管理 前端控制器
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
@Slf4j
@RestController
@RequestMapping("")
@Api(tags = "费用管理")
public class XpPricingController extends BaseController {
    private final XpPricingService xpPricingService;
    private final IXpGeoQueryEntityService xpGeoQueryEntityService;
    public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService) {
        this.xpPricingService = xpPricingService;
        this.xpGeoQueryEntityService = xpGeoQueryEntityService;
    }
    @PostMapping("/base/v1/mini-program/navigation/route-and-fare")
    @ApiOperation(value = "导航-获取路线和费用", tags = {"小程序-获取路线和费用"}, response = RouteAndFareVo.class)
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<RouteAndFareVo> getRouteAndFare(@RequestBody @Valid RouteAndFareDto routeAndFareDto, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpPricingService.getRouteAndFare(routeAndFareDto, uid));
    }
    @GetMapping("/base/v1/mini-program/pricing/rules/{openCityId}")
    @ApiOperation(value = "获取费用规则", tags = {"小程序-根据开通城市获取费用规则"}, response = String.class)
    public R<?> getPricingRules(@PathVariable Long openCityId) {
        XpPricing xpPricing = xpPricingService.getOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, openCityId).eq(XpPricing::getPricingType, 1).last("limit 1"));
        if (xpPricing != null) {
            return R.ok(xpPricing.getPriceRule());
        }
        return R.ok("");
    }
    /**
     * 获取等待时间
     * @param dto
     * @return
     */
    @PostMapping("/base/v1/mini-program/pricing/getWaitTime")
    @ApiOperation(value = "获取等待时间", tags = {"小程序-获取等待时间"}, response = Integer.class)
    public R<Integer> getWaitTime(@RequestBody LonLatDto dto) {
        return R.ok(xpPricingService.getWaitTime(dto.getLongitude(), dto.getLatitude()));
    }
    /**
     * 播单
     */
    @PostMapping("/base/v1/mini-program/order/save")
    @ApiOperation(value = "保存订单", tags = {"小程序-保存订单"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<SaveOrderVo> saveOrder(@RequestBody @Validated XpOrderDto dto, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        Long l = xpPricingService.saveOrder(dto, uid);
        SaveOrderVo saveOrderVo = new SaveOrderVo();
        saveOrderVo.setOrderId(l);
        return R.ok(saveOrderVo);
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java
New file
@@ -0,0 +1,52 @@
package com.xiaopeng.itinerary.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Data
@ApiModel("拨单信息Dto")
public class XpOrderDto {
    @ApiModelProperty("开通城市id")
    @NotNull(message = "开通城市id不能为空")
    private Long openCityId;
    @ApiModelProperty("区域id")
    @NotNull(message = "区域id不能为空")
    private Long xpElectronicFenceAreaId;
    @ApiModelProperty("起点站点id")
    @NotNull(message = "起点站点id不能为空")
    private Long startPointId;
    @ApiModelProperty("终点站点id")
    @NotNull(message = "终点站点id不能为空")
    private Long endPointId;
    @ApiModelProperty("预计时间")
    @NotNull(message = "预计时间不能为空")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime estimatedTime;
    @ApiModelProperty("下单经度")
    @NotNull(message = "下单经度不能为空")
    private Double placementLon;
    @ApiModelProperty("下单纬度")
    @NotNull(message = "下单纬度不能为空")
    private Double placementLat;
    @ApiModelProperty("下单地址")
    @NotNull(message = "下单地址不能为空")
    private String placementAddress;
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceAreaMapper.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
/**
 * <p>
 * 电子围栏区域构成 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpElectronicFenceAreaMapper extends BaseMapper<XpElectronicFenceArea> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceMapper.java
New file
@@ -0,0 +1,21 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.query.operation.ElectronicPageListQuery;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * 电子围栏 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpElectronicFenceMapper extends BaseMapper<XpElectronicFence> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpOpenCityMapper.java
New file
@@ -0,0 +1,17 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpOpenCity;
/**
 * <p>
 * 开通城市 Mapper 接口
 * </p>
 *
 * @author liu
 * @since 2026-02-03
 */
public interface XpOpenCityMapper extends BaseMapper<XpOpenCity> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPointMapper.java
New file
@@ -0,0 +1,19 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpPoint;
/**
 * <p>
 * 点位管理 Mapper 接口
 * </p>
 *
 * @author liu
 * @author WuGuanFengYue
 * @since 2026-02-03
 */
public interface XpPointMapper extends BaseMapper<XpPoint> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPricingMapper.java
New file
@@ -0,0 +1,17 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpPricing;
/**
 * <p>
 * 定价管理 Mapper 接口
 * </p>
 *
 * @author liu
 * @since 2026-02-03
 */
public interface XpPricingMapper extends BaseMapper<XpPricing> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpRoutePlanMapper.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpRoutePlan;
/**
 * <p>
 * 路线规划配置 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpRoutePlanMapper extends BaseMapper<XpRoutePlan> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpSystemSettingMapper.java
New file
@@ -0,0 +1,17 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpSystemSetting;
/**
 * <p>
 * 系统设置表 Mapper 接口
 * </p>
 *
 * @author liu
 * @since 2026-02-03
 */
public interface XpSystemSettingMapper extends BaseMapper<XpSystemSetting> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpTradingStrategyMapper.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpTradingStrategy;
/**
 * <p>
 * 交易策略配置 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpTradingStrategyMapper extends BaseMapper<XpTradingStrategy> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpWhitelistMapper.java
New file
@@ -0,0 +1,16 @@
package com.xiaopeng.itinerary.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpWhitelist;
/**
 * <p>
 * 白名单管理 Mapper 接口
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
public interface XpWhitelistMapper extends BaseMapper<XpWhitelist> {
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/repository/XpGeoQueryEntityRepository.java
New file
@@ -0,0 +1,64 @@
package com.xiaopeng.itinerary.repository;
import com.xiaopeng.api.model.operation.XpGeoQueryEntity;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.Date;
import java.util.List;
/**
 * MongoDB Repository 接口
 * 用于支持地理空间查询和其他操作
 */
public interface XpGeoQueryEntityRepository extends MongoRepository<XpGeoQueryEntity, String> {
    /**
     * 通过经纬度查询范围1000米内、一周内、状态>1的数据
     * @param point 中心点坐标
     * @param distance 距离范围
     * @param startTime 开始时间(一周前)
     * @param status 状态阈值
     * @return 符合条件的实体列表
     */
    @Query("{ 'location' : { '$nearSphere' : ?0, '$maxDistance' : ?1 }, 'timestamp' : { '$gte' : ?2 } }")
    List<XpGeoQueryEntity> findByLocationNearAndTimestampAfterAndStatusGreaterThan(
            Point point,
            Distance distance,
            Date startTime,
            Integer status
    );
    /**
     * 通过经纬度查询范围500米内、一周内、指定时段、状态>1的数据
     * @param point 中心点坐标
     * @param distance 距离范围
     * @param startTime 开始时间(一周前)
     * @param time 时段(格式:HH-HH)
     * @param status 状态阈值
     * @return 符合条件的实体列表
     */
    @Query("{ 'location' : { '$nearSphere' : ?0, '$maxDistance' : ?1 }, 'timestamp' : { '$gte' : ?2 }, 'time' : ?3 }")
    List<XpGeoQueryEntity> findByLocationNearAndTimestampAfterAndTimeAndStatusGreaterThan(
            Point point,
            Integer distance,
            Date startTime,
            String time,
            Integer status
    );
    /**
     * 通过时间范围和状态查询数据
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param status 状态阈值
     * @return 符合条件的实体列表
     */
    List<XpGeoQueryEntity> findByTimestampBetweenAndStatusGreaterThan(
            Date startTime,
            Date endTime,
            Integer status
    );
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/IXpGeoQueryEntityService.java
New file
@@ -0,0 +1,57 @@
package com.xiaopeng.itinerary.service;
import com.xiaopeng.api.model.operation.XpGeoQueryEntity;
import java.util.List;
/**
 * MongoDB 地理查询服务接口
 */
public interface IXpGeoQueryEntityService {
    /**
     * 通过经纬度查询固定范围1000米内、一周内、状态>1的数据
     * @param longitude 经度
     * @param latitude 纬度
     */
    List<XpGeoQueryEntity> findByLocationWithin500MetersAndLastWeekAndStatusGreater(
            double longitude,
            double latitude,
            String time
    );
    /**
     * 保存地理查询实体
     * @param entity 地理查询实体
     * @return 保存后的实体
     */
    XpGeoQueryEntity save(XpGeoQueryEntity entity);
    /**
     * 修改
     */
   XpGeoQueryEntity updateByOrderId(String orderId);
    /**
     * 根据ID查询实体
     * @param id 实体ID
     * @return 实体
     */
    XpGeoQueryEntity findById(String id);
    /**
     * 删除实体
     * @param id 实体ID
     */
    void deleteById(String id);
    /**
     * 查询所有实体
     * @return 实体列表
     */
    List<XpGeoQueryEntity> findAll();
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPointService.java
New file
@@ -0,0 +1,30 @@
package com.xiaopeng.itinerary.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.operation.XpPoint;
import java.time.LocalTime;
/**
 * <p>
 * 点位管理 服务类
 * </p>
 *
 * @author liu
 * @author WuGuanFengYue
 * @since 2026-02-03
 */
public interface XpPointService extends IService<XpPoint> {
    /**
     * 判断点位是否时间段内
     * @param now
     * @return
     */
    boolean getInTime(LocalTime now,  Integer effectivePeriod, String effectiveTimePeriod);
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java
New file
@@ -0,0 +1,50 @@
package com.xiaopeng.itinerary.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.itinerary.dto.XpOrderDto;
/**
 * <p>
 * 定价管理 服务类
 * </p>
 *
 * @author liu
 * @since 2026-02-03
 */
public interface XpPricingService extends IService<XpPricing> {
    /**
     * +
     * 获取导航-获取路线和费用
     * @param routeAndFareDto dto对象
     * @param userId 用户id
     * @return
     */
    RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId);
    /**
     * 获取等待时间
     * @param lon 经度
     * @param lat 纬度
     * @return
     */
    Integer getWaitTime(double lon,double lat);
    /**
     * 保存订单
     * @param dto
     * @param uid
     * @return
     */
    Long saveOrder(XpOrderDto dto, String uid);
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpGeoQueryEntityServiceImpl.java
New file
@@ -0,0 +1,143 @@
package com.xiaopeng.itinerary.service.impl;
import com.xiaopeng.api.model.operation.XpGeoQueryEntity;
import com.xiaopeng.itinerary.repository.XpGeoQueryEntityRepository;
import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
 * MongoDB 地理查询服务实现类
 */
@Service
public class XpGeoQueryEntityServiceImpl implements IXpGeoQueryEntityService {
    @Autowired
    private XpGeoQueryEntityRepository repository;
    @Autowired
    private MongoTemplate mongoTemplate;
    /**
     * 通过经纬度查询固定范围500米内、一周内、指定时段的数据
     * @param longitude 经度
     * @param latitude 纬度
     * @param time 时段(格式:HH-HH,例如:19-20)
     * @return 符合条件的实体列表
     */
    @Override
    public List<XpGeoQueryEntity> findByLocationWithin500MetersAndLastWeekAndStatusGreater(
            double longitude,
            double latitude,
            String time
    ) {
//        // 计算一周前的时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DAY_OF_YEAR, -7);
        Long startTime = calendar.getTime().getTime();
        // 1. 创建查询点
        Point referencePoint = new Point(longitude, latitude);
        // 2. 构建 NearQuery - 完全使用 NearQuery 的 API
        NearQuery nearQuery = NearQuery.near(referencePoint, Metrics.KILOMETERS)
                .maxDistance(0.5)  // 转成公里
                .spherical(true);
        // 3.时间>startTime time = time参数
        // 3. 创建额外的查询条件
        Query query = new Query();
        query.addCriteria(Criteria.where("timestamp").gte(startTime));
        query.addCriteria(Criteria.where("time").is(time));
        // 将 NearQuery 和额外的查询条件合并
        nearQuery.query(query);
        // 4. 执行查询 - 明确指定集合名
        GeoResults<XpGeoQueryEntity> geoResults = mongoTemplate.geoNear(
                nearQuery,
                XpGeoQueryEntity.class,
                "xp_geo_query_entity"  // 明确指定集合名,避免映射错误
        );
        // 5. 提取结果
        return geoResults.getContent().stream()
                .map(geoResult -> geoResult.getContent())
                .collect(Collectors.toList());
    }
    /**
     * 保存地理查询实体
     * @param entity 地理查询实体
     * @return 保存后的实体
     */
    @Override
    public XpGeoQueryEntity save(XpGeoQueryEntity entity) {
        return repository.save(entity);
    }
    @Override
    public XpGeoQueryEntity updateByOrderId(String orderId) {
        Query query = new Query(Criteria.where("orderId").is(orderId));
        XpGeoQueryEntity xpGeoQueryEntity = mongoTemplate.findOne(query, XpGeoQueryEntity.class);
        Long timestamp = xpGeoQueryEntity.getTimestamp();
        long time = new Date().getTime();
        // 计算2个时间的秒数差
        long seconds = (time- timestamp) / 1000;
        Update update = new Update().set("seconds", seconds).set("status", 1);
        if(xpGeoQueryEntity.getStatus()==0){
            mongoTemplate.findAndModify(query, update, XpGeoQueryEntity.class);
        }
        return null;
    }
    /**
     * 根据ID查询实体
     * @param id 实体ID
     * @return 实体
     */
    @Override
    public XpGeoQueryEntity findById(String id) {
        return repository.findById(id).orElse(null);
    }
    /**
     * 删除实体
     * @param id 实体ID
     */
    @Override
    public void deleteById(String id) {
        repository.deleteById(id);
    }
    /**
     * 查询所有实体
     * @return 实体列表
     */
    @Override
    public List<XpGeoQueryEntity> findAll() {
        return repository.findAll();
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPointServiceImpl.java
New file
@@ -0,0 +1,87 @@
package com.xiaopeng.itinerary.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.operation.PointMapQuery;
import com.xiaopeng.api.dto.order.HistoryEndPointsDto;
import com.xiaopeng.api.dto.order.HistoryStartPointsDto;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.api.query.operation.PointPageListQuery;
import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo;
import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo;
import com.xiaopeng.api.vo.order.XpPointsListVo;
import com.xiaopeng.api.vo.order.XpPointsSearchVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.mapper.XpPointMapper;
import com.xiaopeng.itinerary.mapper.XpSystemSettingMapper;
import com.xiaopeng.itinerary.service.XpPointService;
import com.xiaopeng.itinerary.utils.TimeRangeChecker;
import com.xiaopeng.itinerary.utils.holiday.HolidayUtil;
import com.xiaopeng.itinerary.utils.map.FastDistanceCalculator;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
 * 点位管理 服务实现类
 * </p>
 *
 * @author liu
 * @author WuGuanFengYue
 * @since 2026-02-03
 */
@Service
public class XpPointServiceImpl extends ServiceImpl<XpPointMapper, XpPoint> implements XpPointService {
    @Resource
    private HolidayUtil holidayUtil;
    public boolean getInTime(LocalTime now,  Integer effectivePeriod, String effectiveTimePeriod){
        //  2非工作日  0工作日  1节假日 3调休日
        Integer holidayToday = holidayUtil.isHolidayToday();
        if(effectivePeriod==1){
            boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod);
            if (!inTime) {
                return false;
            }
        }else if(effectivePeriod==2){
            if(holidayToday!=0){
                return false;
            }
            boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod);
            if (!inTime) {
                return false;
            }
        }else if(effectivePeriod==3){
            if(holidayToday!=2){
                return false;
            }
            boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod);
            if (!inTime) {
                return false;
            }
        }else if(effectivePeriod==4){
            if(holidayToday!=1){
                return false;
            }
            boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod);
            if (!inTime) {
                return false;
            }
        }
        return true;
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
New file
@@ -0,0 +1,668 @@
package com.xiaopeng.itinerary.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
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.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
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.order.XpOrderClient;
import com.xiaopeng.api.model.account.XpAppUser;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.operation.FareVo;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.XpPricingCardVo;
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.core.utils.DateUtils;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.constant.I18nKeyConstant;
import com.xiaopeng.itinerary.dto.XpOrderDto;
import com.xiaopeng.itinerary.mapper.*;
import com.xiaopeng.itinerary.service.*;
import com.xiaopeng.itinerary.utils.XpPricingUtil;
import com.xiaopeng.itinerary.utils.holiday.HolidayUtil;
import com.xiaopeng.itinerary.utils.map.FastDistanceCalculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.stereotype.Service;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
 * <p>
 * 定价管理 服务实现类
 * </p>
 *
 * @author liu
 * @since 2026-02-03
 */
@Service
public class XpPricingServiceImpl extends ServiceImpl<XpPricingMapper, XpPricing> implements XpPricingService {
    @Resource
    private XpPricingUtil xpPricingUtil;
    @Resource
    private XpMapClient xpMapClient;
    @Resource
    private XpOpenCityMapper xpOpenCityMapper;
    @Resource
    private XpRoutePlanMapper xpRoutePlanMapper;
    @Resource
    private RedisService redisService;
    @Resource
    private XpSystemSettingMapper systemSettingMapper;
    @Resource
    private XpAppUserCouponClient xpAppUserCouponClient;
    @Resource
    private XpOrderClient xpOrderClient;
    @Resource
    private XpTradingStrategyMapper xpTradingStrategyMapper;
    @Resource
    private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper;
    @Resource
    private XpWhitelistMapper xpWhitelistMapper;
    @Resource
    private XpAppUserClient xpAppUserClient;
    @Resource
    private HolidayUtil holidayUtil;
    @Autowired
    private IXpGeoQueryEntityService geoQueryEntityService;
    @Autowired
    private XpElectronicFenceMapper xpElectronicFenceMapper;
    @Autowired
    private XpPointService xpPointService;
    // 自定义线程池用于并行执行任务
    private final ExecutorService executorService;
    // 初始化自定义线程池
    public XpPricingServiceImpl() {
        this.executorService = new ThreadPoolExecutor(
                40, // 核心线程数
                100, // 最大线程数
                60, // 线程空闲时间(秒)
                TimeUnit.SECONDS, // 时间单位
                new LinkedBlockingQueue<>(400), // 工作队列
                new ThreadFactory() {
                    private final AtomicInteger threadNumber = new AtomicInteger(1);
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("pricing-service-thread-" + threadNumber.getAndIncrement());
                        thread.setDaemon(false);
                        return thread;
                    }
                }, // 线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
    }
    // 关闭线程池
    @PreDestroy
    public void destroy() {
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
    /**
     * 根据当前时间获取时段
     * 例如:15:50 → 15-16
     * @return 时段字符串,格式:HH-HH
     */
    private String getCurrentTimeSlot() {
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        return hour + "-" + (hour + 1);
    }
    @Override
    public RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) {
        // 判断是否5分钟内进行了查询 相同站点的起始点  并且是同时段
        String key = "xp-map-query-" + routeAndFareDto.getOpenCityId()
                + "-" + routeAndFareDto.getOrigin().getLongitude() + "-" + routeAndFareDto.getOrigin().getLatitude()
                + "-" + routeAndFareDto.getDestination().getLongitude() + "-" + routeAndFareDto.getDestination().getLatitude() + "-" + getCurrentTimeSlot() + "-" + userId;
        if (redisService.hasKey(key)) {
            String jsonStr = Objects.requireNonNull(redisService.getCacheObject(key)).toString();
            RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class);
            return (RouteAndFareVo);
        }
        // 1. 基础数据查询
        XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(routeAndFareDto.getOpenCityId());
        if (xpOpenCity == null) {
            throw new ServiceException("未找到步行路线", I18nKeyConstant.CITY_INFORMATION_DOES_NOT_EXIST);
        }
        String cityCode = xpOpenCity.getCityCode();
        XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode));
        if (xpRoutePlan != null) {
            routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
        }
        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);
        if(distanceAndDuration.getCode()!=200){
            throw new ServiceException("未找到路径", I18nKeyConstant.PATH_NOT_FOUND);
        }
        RouteVo routeVo = distanceAndDuration.getData();
        // 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格)
        XpPricingCardVo xpPricingCardVo;
        if (xpPricing.getPricingType() == 1) {
            xpPricingCardVo = xpPricingUtil.calculationPrice(userId,
                    JSON.parseObject(xpPricing.getBasicPricingJson()),
                    JSON.parseArray(xpPricing.getStartingFareJson()),
                    routeVo.getDistance(), routeVo.getDuration(), 0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson()
            );
        } else {
            xpPricingCardVo = xpPricingUtil.calculationFixedPrice(userId,
                    JSON.parseObject(xpPricing.getBasicPricingJson()),
                    JSON.parseArray(xpPricing.getStartingFareJson()),
                    routeVo.getDistance(), routeVo.getDuration(),0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson()
            );
        }
        // 3. 并行执行远程服务调用 判断是否同一经营区域
        CompletableFuture<R<Boolean>> diagnosisAreaFuture = CompletableFuture.supplyAsync(
                () -> xpMapClient.diagnosisArea(routeAndFareDto),
                executorService
        );
        // 优惠券
        XpPricingCardVo finalXpPricingCardVo = xpPricingCardVo;
        CompletableFuture<R<UserCouponVo>> couponFuture = CompletableFuture.supplyAsync(
                () -> {
                    // 优惠券
                    return xpAppUserCouponClient.getUserCoupon(
                                new GetUserCouponDto(Long.valueOf(userId), finalXpPricingCardVo.getTotalPrice(),routeAndFareDto.getOpenCityId())
//                            new GetUserCouponDto(Long.valueOf(userId), 1d, routeAndFareDto.getOpenCityId())
                    );
                },
                executorService
        );
        // 4. 等待所有并行任务完成并获取结果
        R<Boolean> diagnosisResult = diagnosisAreaFuture.join();
        R<UserCouponVo> couponResult = couponFuture.join();
        // 5. 处理诊断结果
        if (diagnosisResult.getCode() != R.SUCCESS) {
            throw new RuntimeException(diagnosisResult.getMsg());
        }
        if (!diagnosisResult.getData()) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
//        // 6. 处理路线结果
//        if (routeResult.getCode() != 200) {
//            throw new RuntimeException(routeResult.getMsg());
//        }
//        RouteVo routeVo = routeResult.getData();
        // 起终点最小距离 走缓存
        Integer minOrderDistance;
        Object minOrderDistanceObj = redisService.getCacheObject(CacheConstants.MIN_ORDER_DISTANCE);
        if (minOrderDistanceObj != null) {
            minOrderDistance = Integer.valueOf(minOrderDistanceObj.toString());
        } else {
            minOrderDistance = systemSettingMapper.selectOne(null).getMinOrderDistance();
        }
        if (routeVo.getDistance() < Integer.valueOf(minOrderDistance)) {
            throw new ServiceException("当前上下车点距离过近,建议步行前往", I18nKeyConstant.TO_WORK);
        }
        // 7. 处理优惠券结果
        if (couponResult.getCode() != R.SUCCESS) {
            throw new RuntimeException(couponResult.getMsg());
        }
        UserCouponVo userCouponVo = couponResult.getData();
        if (userCouponVo != null) {
            xpPricingCardVo.setCoupon(String.valueOf(userCouponVo.getMoney()));
        }
        // 8. 构建响应对象
        RouteAndFareVo routeAndFareVo = new RouteAndFareVo();
        // MOCK数据
//        RouteVo routeVo = new RouteVo();
//        routeVo.setDistance(3000);
//        routeVo.setDuration(5);
//        routeVo.setRouteID("1");
//        List<LonLatDto> routePolyline = Arrays.asList(
//                // 起点:天府新谷
//                new LonLatDto(104.05363, 30.58937),
//
//                // 途经点1:府城大道与交子北二路交叉口附近
//                new LonLatDto(104.05821, 30.58745),
//
//                // 途经点2:交子大道与成汉南路交叉口
//                new LonLatDto(104.06418, 30.58492),
//
//                // 途经点3:交子大道与万象南路交叉口
//                new LonLatDto(104.06975, 30.58321),
//
//                // 途经点4:交子大道与益州大道交叉口
//                new LonLatDto(104.07366, 30.58198),
//
//                // 终点:金融城(地铁站附近)
//                new LonLatDto(104.076544, 30.581039)
//        );
//        routeVo.setPolyline(routePolyline);
        routeAndFareVo.setRoute(routeVo);
        routeAndFareVo.setXpPricingCard(xpPricingCardVo);
        FareVo fareVo = new FareVo();
        fareVo.setEstimatedDistanceKm(routeVo.getDistance().doubleValue() / 1000);
        fareVo.setEstimatedDurationMin(routeVo.getDuration() / 60);
        // 9. 设置费用信息
        fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice());
        if (userCouponVo != null) {
            fareVo.setDiscountAmount(userCouponVo.getMoney());
            fareVo.setFinalFare(fareVo.getOriginalFare() - fareVo.getDiscountAmount());
            fareVo.setCouponId(userCouponVo.getId());
        } else {
            fareVo.setDiscountAmount(0.0);
            fareVo.setFinalFare(fareVo.getOriginalFare());
        }
        // 10. 检查等待时间
        Integer waitTime = getWaitTime(routeAndFareDto.getOrigin().getLongitude(), routeAndFareDto.getOrigin().getLatitude());
        if (waitTime > 3) {
            throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME);
        }
        fareVo.setWaitingTime(waitTime);
        // 11. 生成订单ID并缓存
        String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8);
        redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1l, TimeUnit.DAYS);
        fareVo.setId(id);
        // 12. 设置费用信息
        routeAndFareVo.setFareEstimate(fareVo);
        String voJsonStr = JSONObject.toJSONString(routeAndFareVo);
        // 进行缓存
        redisService.setCacheObject(key, voJsonStr, 5l, TimeUnit.MINUTES);
        return routeAndFareVo;
    }
    /**
     * 获取等待时间
     * @param lon
     * @param lat
     * @return
     */
    @Override
    public Integer getWaitTime(double lon, double lat) {
        List<XpGeoQueryEntity> entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater(
                lon,
                lat,
                getCurrentTimeSlot()
        );
        if (entities.isEmpty()) {
            return 1;
        } else {
            // 已完成接单的数据
            List<XpGeoQueryEntity> validEntities = entities.stream().filter(e -> e.getStatus() == 1).collect(Collectors.toList());
            // 计算平均等待时间
            OptionalDouble averageOptional = validEntities.stream().mapToDouble(XpGeoQueryEntity::getSeconds).average();
            if (averageOptional.isPresent()) {
                double average = averageOptional.getAsDouble();
                // 未完成接单的数量
                int count = entities.size() - validEntities.size();
                if (count == 0) {
                    return 1;
                } else {
                    double waitMinutes = (average * count) / 60;
                    if (waitMinutes > 3) {
                        throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME);
                    } else if (waitMinutes == 0) {
                        return 1;
                    }
                    return (int) waitMinutes;
                }
            } else {
                return 1;
            }
        }
    }
    @Override
    public Long saveOrder(XpOrderDto dto, String uid) {
        // 是否在运营区域时间内
        XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId());
        XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId());
        String operationStrategy = xpElectronicFence.getOperationStrategy();
        Boolean inOperationTime = isInOperationTime(operationStrategy);
        if (!inOperationTime) {
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        Integer cancelCount = xpTradingStrategyMapper.selectOne(null).getCancleCount();
        // 判断是否有进行的订单
        R<Long> r = xpOrderClient.hasUnPaodOrder(uid);
        if (r.getData() > 0) {
            throw new RuntimeException("有正在进行的订单");
        }
        // 获取起点经纬度
        XpPoint xpPoint = xpPointService.getById(dto.getStartPointId());
        // 终点
        XpPoint endXpPoint = xpPointService.getById(dto.getEndPointId());
        // 判断点位是否在运营区域内
        LocalTime now = LocalTime.now();
        boolean inTime = xpPointService.getInTime(now, xpPoint.getEffectivePeriod(), xpPoint.getEffectiveTimePeriod());
        if (!inTime) {
            throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        inTime = xpPointService.getInTime(now, endXpPoint.getEffectivePeriod(), endXpPoint.getEffectiveTimePeriod());
        if (!inTime) {
            throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        // 判断当天取消的次数 后台配置 redis 查询
        Object cancelOrder = redisService.getCacheObject("CANCEL_ORDER_" + uid);
        // 静默处理
        boolean isSilent = false;
        if (cancelOrder != null) {
            Integer num = Integer.valueOf(cancelOrder.toString());
            if (num >= cancelCount) {
                isSilent = true;
            }
        }
        // 获取起步里程
//        XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, dto.getOpenCityId()));
//        Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson());
//        if (startMileage == null) {
//            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
//        }
        // 根据id获取手机号
        Boolean isPriorityDispatch = false;
        R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
        if (xpAppUserR.getCode() == R.SUCCESS) {
            Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
            if (count > 0) {
                isPriorityDispatch = true;
            }
        }
        // 生成订单数据
        XpOrder xpOrder = new XpOrder();
        xpOrder.setVehicleId(1l);
//        xpOrder.setStartMileage(Double.valueOf(startMileage));
        xpOrder.setAppUserId(Long.valueOf(uid));
        xpOrder.setOrderNumber("XP" + System.currentTimeMillis());
        xpOrder.setPlacementLon(dto.getPlacementLon());
        xpOrder.setPlacementLat(dto.getPlacementLat());
        xpOrder.setPlacementAddress(dto.getPlacementAddress());
        xpOrder.setCityId(dto.getOpenCityId());
        xpOrder.setStartLon(xpPoint.getPointLon());
        xpOrder.setStartLat(xpPoint.getPointLat());
        xpOrder.setBoardingLon(xpPoint.getPointLon());
        xpOrder.setBoardingLat(xpPoint.getPointLat());
        xpOrder.setBoardingAddress(xpPoint.getPointAddress());
        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();
        xpOrder.setTimeSlot(currentTimeSlot);
        xpOrder.setCreateTime(LocalDateTime.now());
        // 计算距离 和 时间
        RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
        LonLatDto lonLatDto = new LonLatDto();
        lonLatDto.setLongitude(xpPoint.getPointLon());
        lonLatDto.setLatitude(xpPoint.getPointLat());
        routeAndFareDto.setOrigin(lonLatDto);
        LonLatDto lonLatDto1 = new LonLatDto();
        lonLatDto1.setLongitude(endXpPoint.getPointLon());
        lonLatDto1.setLatitude(endXpPoint.getPointLat());
        routeAndFareDto.setDestination(lonLatDto1);
        XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(dto.getOpenCityId());
        XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()));
        if (xpRoutePlan != null) {
            routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
        }
        R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        Integer distance = 0;
        Integer duration = 0;
        if (distanceAndDuration.getCode() != 200) {
//            throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
        }
//        distance = distanceAndDuration.getData().getDistance();
//        duration = distanceAndDuration.getData().getDuration();
        // 产生订单
        R<Long> addOrderR = xpOrderClient.addOrder(xpOrder);
        if (addOrderR.getCode() != R.SUCCESS) {
            throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
        }
        Long orderId = addOrderR.getData();
        // 静默处理 不推单
        if (isSilent) {
            return orderId;
        }
        // 最大距离
        Object maxPushDistanceObj = redisService.getCacheObject(CacheConstants.MAX_PUSH_DISTANCE);
        Double maxPushDistance = 0.0;
        if (maxPushDistanceObj != null) {
            maxPushDistance = Double.valueOf(maxPushDistanceObj.toString());
        } else {
            maxPushDistance = xpTradingStrategyMapper.selectOne(null).getThreePushDistance();
        }
        // 找出现在所有的队列 通过计算距离 算出目前应该放在那个队列  如果没有符合的新增一个队列  符合后看是否白名单账号 是则直接放在队列前端
        Set<String> quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID);
        for (String s : quoteMatchName) {
//            QUEUE_POINT_ID-1-30.233,120.233
            String lonLat = s.split("-")[2];
            // 和上车点进行判断是否<maxPushDistance
            double v = FastDistanceCalculator.distanceFast(Double.parseDouble(lonLat.split(",")[1]), Double.parseDouble(lonLat.split(",")[0]), xpPoint.getPointLat(), xpPoint.getPointLon());
            if (v < maxPushDistance) {
                if (isPriorityDispatch) {
                    // 头部  value 订单_经纬度_状态_用户id_订单编号_距离_时间
                    redisService.addToTail(s, orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule());
                } else {
                    // 尾部
                    redisService.addToTail(s, orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule());
                }
                xpOrder.setQueueName(s);
                xpOrderClient.updateOrder(xpOrder);
                // 存储mongodb 数据
                XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity();
                xpGeoQueryEntity.setOrderId(orderId.toString());
                xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat()));
                // 计算2个时间的秒数
                xpGeoQueryEntity.setTime(currentTimeSlot);
                xpGeoQueryEntity.setStatus(0);
                xpGeoQueryEntity.setTimestamp(System.currentTimeMillis());
                geoQueryEntityService.save(xpGeoQueryEntity);
                return orderId;
            }
        }
        String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat();
        redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule()));
        xpOrder.setQueueName(key);
        xpOrderClient.updateOrder(xpOrder);
        // 存储mongodb 数据
        XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity();
        xpGeoQueryEntity.setOrderId(orderId.toString());
        xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat()));
        // 计算2个时间的秒数
        xpGeoQueryEntity.setTime(currentTimeSlot);
        xpGeoQueryEntity.setStatus(0);
        geoQueryEntityService.save(xpGeoQueryEntity);
        return orderId;
//
//        XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId());
//        // 获取电子围栏的车辆
//        R<List<XpVehicle>> vehicles = xpVehicleClient.listByFenceId(xpElectronicFenceArea.getElectronicId());
//
        // 产生订单  存订单数据  // 下发指令  消息推送用户端
//        return null;
    }
    private Boolean isInOperationTime(String operationStrategy) {
        JSONArray jsonArray = JSONArray.parseArray(operationStrategy);
        Integer holidayToday = holidayUtil.isHolidayToday();
        for (Object o : jsonArray) {
            JSONObject jsonObject = JSONObject.parseObject(o.toString());
            String type = jsonObject.getString("type");
            if (type.equals(holidayToday.toString())) {
                JSONArray time = jsonObject.getJSONArray("time");
                // 判断当前时间 是否在time时间段中
                for (Object object : time) {
                    String[] timeArray = object.toString().split("-");
                    String startTime = timeArray[0];
                    String endTime = timeArray[1];
                    boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
                    if (currentTimeInRange) {
                        return true;
                    }
                }
            } else if (type.equals("3")) {
                // 判断是什么类型 0每天  1-7 想起
                String typeStr = jsonObject.getString("typeStr");
                // 查看今天周几
                Integer week = DateUtils.getWeek();
                // 包含每天 或者  包含当天
                if (typeStr.contains("0") || typeStr.contains(week.toString())) {
                    JSONArray time = jsonObject.getJSONArray("time");
                    // 判断当前时间 是否在time时间段中
                    for (Object object : time) {
                        String[] timeArray = object.toString().split("-");
                        String startTime = timeArray[0];
                        String endTime = timeArray[1];
                        boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
                        if (currentTimeInRange) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/TimeRangeChecker.java
New file
@@ -0,0 +1,85 @@
package com.xiaopeng.itinerary.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class TimeRangeChecker {
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
    /**
     * 判断当前时间是否在时间段内
     * @param jsonArrayStr JSON数组字符串,格式如 ["08:00-12:00", "13:30-17:30", "18:00-21:00"]
     * @return true 如果在时间段内,false 如果不在
     */
    public static boolean isCurrentTimeInRanges(LocalTime now,String jsonArrayStr) {
        return isTimeInRanges(jsonArrayStr, now);
    }
    /**
     * 判断指定时间是否在时间段内
     * @param jsonArrayStr JSON数组字符串
     * @param time 要判断的时间
     * @return true 如果在时间段内,false 如果不在
     */
    public static boolean isTimeInRanges(String jsonArrayStr, LocalTime time) {
        if (jsonArrayStr == null || jsonArrayStr.isEmpty()) {
            return false;
        }
        try {
            // 直接解析为JSONArray
            JSONArray timeArray = JSON.parseArray(jsonArrayStr);
            if (timeArray == null || timeArray.isEmpty()) {
                return false;
            }
            // 遍历所有时间段
            for (int i = 0; i < timeArray.size(); i++) {
                String timeRange = timeArray.getString(i);
                if (isTimeInRange(time, timeRange)) {
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 判断时间是否在单个时间段内
     */
    private static boolean isTimeInRange(LocalTime time, String timeRange) {
        if (timeRange == null || !timeRange.contains("-")) {
            return false;
        }
        String[] parts = timeRange.split("-");
        if (parts.length != 2) {
            return false;
        }
        try {
            LocalTime start = LocalTime.parse(parts[0].trim(), TIME_FORMATTER);
            LocalTime end = LocalTime.parse(parts[1].trim(), TIME_FORMATTER);
            // 处理跨天的情况(如 22:00-02:00)
            if (end.isBefore(start)) {
                // 跨天时间段
                return !time.isBefore(start) || !time.isAfter(end);
            } else {
                // 正常时间段
                return !time.isBefore(start) && !time.isAfter(end);
            }
        } catch (Exception e) {
            return false;
        }
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java
New file
@@ -0,0 +1,749 @@
package com.xiaopeng.itinerary.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.feignClient.order.XpOrderClient;
import com.xiaopeng.api.vo.order.XpPricingCardVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.utils.holiday.HolidayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
 * 计算价格的工具类
 */
@Slf4j
@Component
public class XpPricingUtil {
    @Autowired
    private HolidayUtil holidayUtil;
    @Autowired
    private RedisService redisService;
    @Autowired
    private XpOrderClient xpOrderClient;
    /**
     * 获取起步里程
     * @param startingFareJsonStr
     * @return
     */
    public static Integer getStartMileage(String startingFareJsonStr) {
        Date date = new Date();
        JSONArray startingFareJson = JSONObject.parseArray(startingFareJsonStr);
        for (Object o : startingFareJson) {
            JSONObject startingFare = (JSONObject) o;
            Calendar calendarStart = Calendar.getInstance();
            calendarStart.setTime(date);
            calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0]));
            calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1]));
            Calendar calendarEnd = Calendar.getInstance();
            calendarEnd.setTime(date);
            calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0]));
            calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1]));
            // 找到符合条件的直接跳出循环
            if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
                Integer mileage = startingFare.getInteger("num4");
                return mileage;
            }
        }
        return null;
    }
    /**
     * 计算一口价
     */
    public XpPricingCardVo calculationFixedPrice(String uid, JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney, Integer isDynamicPriceStatus, Long openCityId, String dynamicPricingJson) {
        // 默认费率1
        double priceRate=1;
        if(isDynamicPriceStatus==1){
            // 先判断是否已经调价过了
            Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid);
            if(userDynamicPriceObj!=null){
                // 是否调价过了
                priceRate = Double.parseDouble(userDynamicPriceObj.toString());
            }else {
                // 没有调过
                //是否触发动态调价 redis获取
                Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId);
                if(dynamicPriceObj!=null){
                    JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson);
                    // 触发动态调价
                    // value=时间
                    String timeStr = dynamicPriceObj.toString();
                    // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率
                    R<Integer> r = xpOrderClient.getOrderNum(new GetOrderNumDto(timeStr,openCityId));
                    if(r.getData()==200){
                        int num = r.getData();
                        // X单上升一次调价
                        Integer orderNum = getOrderNum(jsonObject, 4);
                        // 需要调价的次数
                        int count = num / orderNum;
                        if(count>0){
                            // 计算每次上升的费率
                            Integer upRate = getOrderNum(jsonObject, 5);
                            BigDecimal divide = new BigDecimal(upRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
                            for (int i = count; i > 0; i--) {
                                // 当前费率+(费率*上升百分比)
                                priceRate =  new BigDecimal(priceRate).multiply(divide).add(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
                            }
                            // 冷却时间
                            Integer time = getOrderNum(jsonObject, 6);
                            // 触发调价了
                            redisService.setCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES);
                        }
                    }
                }
            }
        }
        XpPricingCardVo xpPricingCardVo = new XpPricingCardVo();
        double amount = 0;
        double realDistance = distance / 1000;//实际公里
        double realTime = duration / 60;//实际时间
        double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里
        double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间
        // 起步价
        Double startPrice = rule.getDouble("num1");
        // 超出公里数价格
        Double distancePrice = rule.getDouble("num4") * exceedDistance;
        // 超出时间价格
        Double durationPrice = rule.getDouble("num5") * exceedTime;
        startPrice = new BigDecimal(startPrice).multiply(BigDecimal.valueOf(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
        xpPricingCardVo.setStartPrice(startPrice);
        xpPricingCardVo.setDistancePrice(distancePrice);
        xpPricingCardVo.setDurationPrice(durationPrice);
        xpPricingCardVo.setTotalPrice(startPrice + distancePrice + durationPrice);
        // 里程
        xpPricingCardVo.setDistance(realDistance);
        xpPricingCardVo.setDuration(realTime);
        return xpPricingCardVo;
    }
    /**
     *  计算基础价格
     * @param rule 基础规则
     * @param distance 里程
     * @param duration  时间
     * @param wait  时间
     * @param waitMoney 等待费
     * @param openCityId 开通城市id
     * @param isDynamicPriceStatus 是否开启动态调价
     * @return
     * @throws Exception
     */
    public XpPricingCardVo calculationPrice(String uid,JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney,Integer isDynamicPriceStatus,Long openCityId,String dynamicPricingJson) {
        // 默认费率1
        double priceRate=1;
        if(isDynamicPriceStatus==1){
            // 先判断是否已经调价过了
            Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid);
            if(userDynamicPriceObj!=null){
                // 是否调价过了
                priceRate = Double.parseDouble(userDynamicPriceObj.toString());
            }else {
                // 没有调过
                //是否触发动态调价 redis获取
                Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId);
                if(dynamicPriceObj!=null){
                    JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson);
                    // 触发动态调价
                    // value=时间
                    String timeStr = dynamicPriceObj.toString();
                    // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率
                    R<Integer> r = xpOrderClient.getOrderNum(new GetOrderNumDto(timeStr,openCityId));
                    if(r.getData()==200){
                        int num = r.getData();
                        // X单上升一次调价
                        Integer orderNum = getOrderNum(jsonObject, 4);
                        // 需要调价的次数
                        int count = num / orderNum;
                        if(count>0){
                            // 计算每次上升的费率
                            Integer upRate = getOrderNum(jsonObject, 5);
                            BigDecimal divide = new BigDecimal(upRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
                            for (int i = count; i > 0; i--) {
                                // 当前费率+(费率*上升百分比)
                                priceRate =  new BigDecimal(priceRate).multiply(divide).add(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
                            }
                            // 冷却时间
                            Integer time = getOrderNum(jsonObject, 6);
                            // 触发调价了
                            redisService.setCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES);
                        }
                    }
                }
            }
        }
        XpPricingCardVo xpPricingCardVo = new XpPricingCardVo();
        double amount = 0;
        //等待费
        Date date = new Date();
        // 计算出是在那个时间段内
        for (Object o : startingFareJson) {
            JSONObject startingFare = (JSONObject) o;
            Calendar calendarStart = Calendar.getInstance();
            calendarStart.setTime(date);
            calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0]));
            calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1]));
            Calendar calendarEnd = Calendar.getInstance();
            calendarEnd.setTime(date);
            calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0]));
            calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1]));
            // 找到符合条件的直接跳出循环
            if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
                rule.put("num1", startingFare.get("num3"));
                rule.put("num2", startingFare.get("num4"));
                rule.put("num3", startingFare.get("num5"));
                rule.put("num4", startingFare.get("num6"));
                rule.put("num5", startingFare.get("num7"));
                rule.put("startTime", startingFare.getString("num1"));
                rule.put("endTime", startingFare.getString("num2"));
                break;
            }
        }
        double realDistance = distance / 1000;//实际公里
        double realTime = duration / 60;//实际时间
        double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里
        double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间
        double longDistanceOne = 0;//远途1段
        double longDistanceTwo = 0;//远途2段
        double longDistanceThree = 0;//远途3段
        //夜间服务处理逻辑
        Calendar calendarNight = Calendar.getInstance();
        calendarNight.setTime(date);
        calendarNight.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0]));
        calendarNight.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1]));
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0]));
        calendar.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1]));
        // 先判断今日是否节假日
        boolean holiday = holidayUtil.isHoliday();
        if (holiday) {
            if (realDistance > rule.getDouble("num6")) {
                longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6"));
            }
            if (realDistance > rule.getDouble("num9")) {
                longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9"));
            }
            if (realDistance > rule.getDouble("num12")) {
                longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12"));
            }
            amount = rule.getDouble("num29") + (exceedDistance * rule.getDouble("num30")) + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
            // 起步价
            xpPricingCardVo.setStartPrice(rule.getDouble("num29"));
            // 时间段
            xpPricingCardVo.setTime("节假日");
            // 里程
            xpPricingCardVo.setDistance(realDistance);
            // 里程费
            double distancePrice = (exceedDistance * rule.getDouble("num30")) + longDistanceOne + longDistanceTwo + longDistanceThree;
            xpPricingCardVo.setDistancePrice(distancePrice);
            // 时长
            xpPricingCardVo.setDuration(realTime);
            // 时长费
            xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num31")));
        } else {
            if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) {
                if (realDistance > rule.getDouble("num6")) {
                    longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6"));
                }
                if (realDistance > rule.getDouble("num9")) {
                    longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9"));
                }
                if (realDistance > rule.getDouble("num12")) {
                    longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12"));
                }
                amount = rule.getDouble("num15") + (exceedDistance * rule.getDouble("num16")) + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                // 起步价
                xpPricingCardVo.setStartPrice(rule.getDouble("num15"));
                // 时间段
                xpPricingCardVo.setTime(rule.getString("num14"));
                // 里程
                xpPricingCardVo.setDistance(realDistance);
                // 里程费
                double distancePrice = (exceedDistance * rule.getDouble("num16")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                xpPricingCardVo.setDistancePrice(distancePrice);
                // 时长
                xpPricingCardVo.setDuration(realTime);
                // 时长费
                xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num17")));
            } else {
                // 高峰处理
                Calendar calendarStart = Calendar.getInstance();
                calendarStart.setTime(date);
                calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0]));
                calendarStart.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1]));
                Calendar calendarEnd = Calendar.getInstance();
                calendarEnd.setTime(date);
                calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0]));
                calendarEnd.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1]));
                Calendar s2 = Calendar.getInstance();
                s2.setTime(date);
                s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0]));
                s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1]));
                Calendar e2 = Calendar.getInstance();
                e2.setTime(date);
                e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0]));
                e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1]));
                //高峰时段处理逻辑
                if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) {
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6"));
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9"));
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12"));
                    }
                    amount = rule.getDouble("num23") + (exceedDistance * rule.getDouble("num24")) + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
                    xpPricingCardVo.setStartPrice(rule.getDouble("num23"));
                    // 时间段
                    if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
                        xpPricingCardVo.setTime(rule.getString("num21"));
                    } else {
                        xpPricingCardVo.setTime(rule.getString("num22"));
                    }
                    // 里程
                    xpPricingCardVo.setDistance(realDistance);
                    // 里程费
                    double distancePrice = (exceedDistance * rule.getDouble("num24")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    xpPricingCardVo.setDistancePrice(distancePrice);
                    // 时长
                    xpPricingCardVo.setDuration(realTime);
                    // 时长费
                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num25")));
                } else {
                    //其他时间段的计算
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6"));
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9"));
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12"));
                    }
                    amount = rule.getDouble("num1") + (exceedDistance * rule.getDouble("num4")) + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
                    xpPricingCardVo.setStartPrice(rule.getDouble("num1"));
                    // 时间段
                    xpPricingCardVo.setTime(rule.getString("startTime") + " - " + rule.getString("endTime"));
                    xpPricingCardVo.setDistancePriceNormal(rule.getDouble("num2"));
                    xpPricingCardVo.setDurationPriceNormal(rule.getDouble("num3"));
                    // 里程
                    xpPricingCardVo.setDistance(realDistance);
                    // 里程费
                    double distancePrice = (exceedDistance * rule.getDouble("num4")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    xpPricingCardVo.setDistancePrice(distancePrice);
                    // 时长
                    xpPricingCardVo.setDuration(realTime);
                    // 时长费
                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num5")));
                }
            }
        }
        // 订单总额
        Double startPrice = xpPricingCardVo.getStartPrice();
        // 先减去起步价重新计算
        amount = amount - startPrice;
        // 动态计算费用
        startPrice= new BigDecimal(startPrice).multiply(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
        amount = amount+startPrice;
        xpPricingCardVo.setTotalPrice(amount);
        return xpPricingCardVo;
    }
    /**
     *  行程结束后计算费用
     * @param rule 基础规则
     * @param distance 里程
     * @param duration  时间
     * @param wait  时间
     * @param waitMoney 等待费
     * @param openCityId 开通城市id
     * @param isDynamicPriceStatus 是否开启动态调价
     * @return
     * @throws Exception
     */
//    public XpPricingCardVo calculationFinalPrice(String uid,JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney,Integer isDynamicPriceStatus,Long openCityId,String dynamicPricingJson) {
//
//
//        // 默认费率1
//        double priceRate=1;
//        if(isDynamicPriceStatus==1){
//            // 先判断是否已经调价过了
//            Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid);
//            if(userDynamicPriceObj!=null){
//                // 是否调价过了
//                priceRate = Double.parseDouble(userDynamicPriceObj.toString());
//            }else {
//                // 没有调过
//                //是否触发动态调价 redis获取
//                Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId);
//                if(dynamicPriceObj!=null){
//                    JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson);
//                    // 触发动态调价
//                    // value=时间
//                    String timeStr = dynamicPriceObj.toString();
//                    // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率
//                    R<Integer> r = xpOrderClient.getOrderNum(timeStr);
//                    if(r.getData()==200){
//                        int num = r.getData();
//                        // X单上升一次调价
//                        Integer orderNum = getOrderNum(jsonObject, 4);
//                        // 需要调价的次数
//                        int count = num / orderNum;
//                        if(count>0){
//                            // 计算每次上升的费率
//                            Integer upRate = getOrderNum(jsonObject, 5);
//                            BigDecimal divide = new BigDecimal(upRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
//                            for (int i = count; i > 0; i--) {
//                                // 当前费率+(费率*上升百分比)
//                                priceRate =  new BigDecimal(priceRate).multiply(divide).add(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
//                            }
//
//                            // 冷却时间
//                            Integer time = getOrderNum(jsonObject, 6);
//                            // 触发调价了
//                            redisService.setCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES);
//                        }
//                    }
//
//                }
//            }
//
//        }
//
//
//
//        XpPricingCardVo xpPricingCardVo = new XpPricingCardVo();
//        double amount = 0;
//        //等待费
//        Date date = new Date();
//        // 计算出是在那个时间段内
//        for (Object o : startingFareJson) {
//            JSONObject startingFare = (JSONObject) o;
//            Calendar calendarStart = Calendar.getInstance();
//            calendarStart.setTime(date);
//            calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0]));
//            calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1]));
//
//            Calendar calendarEnd = Calendar.getInstance();
//            calendarEnd.setTime(date);
//            calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0]));
//            calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1]));
//            // 找到符合条件的直接跳出循环
//            if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
//                rule.put("num1", startingFare.get("num3"));
//                rule.put("num2", startingFare.get("num4"));
//                rule.put("num3", startingFare.get("num5"));
//                rule.put("num4", startingFare.get("num6"));
//                rule.put("num5", startingFare.get("num7"));
//
//                rule.put("startTime", startingFare.getString("num1"));
//                rule.put("endTime", startingFare.getString("num2"));
//                break;
//            }
//        }
//
//
//        double realDistance = distance / 1000;//实际公里
//        double realTime = duration / 60;//实际时间
//        double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里
//        double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间
//        double longDistanceOne = 0;//远途1段
//        double longDistanceTwo = 0;//远途2段
//        double longDistanceThree = 0;//远途3段
//
//
//        //夜间服务处理逻辑
//        Calendar calendarNight = Calendar.getInstance();
//        calendarNight.setTime(date);
//        calendarNight.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0]));
//        calendarNight.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1]));
//
//        Calendar calendar = Calendar.getInstance();
//        calendar.setTime(date);
//        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0]));
//        calendar.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1]));
//
//        // 先判断今日是否节假日
//        boolean holiday = holidayUtil.isHoliday();
//        if (holiday) {
//            if (realDistance > rule.getDouble("num6")) {
//                longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6"));
//            }
//            if (realDistance > rule.getDouble("num9")) {
//                longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9"));
//            }
//            if (realDistance > rule.getDouble("num12")) {
//                longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12"));
//            }
//            amount = rule.getDouble("num29") + (exceedDistance * rule.getDouble("num30")) + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
//
//
//            // 起步价
//            xpPricingCardVo.setStartPrice(rule.getDouble("num29"));
//            // 时间段
//            xpPricingCardVo.setTime("节假日");
//
//
//            // 里程
//            xpPricingCardVo.setDistance(realDistance);
//
//            // 里程费
//            double distancePrice = (exceedDistance * rule.getDouble("num30")) + longDistanceOne + longDistanceTwo + longDistanceThree;
//            xpPricingCardVo.setDistancePrice(distancePrice);
//
//            // 时长
//            xpPricingCardVo.setDuration(realTime);
//
//            // 时长费
//            xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num31")));
//
//
//        } else {
//            if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) {
//                if (realDistance > rule.getDouble("num6")) {
//                    longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6"));
//                }
//                if (realDistance > rule.getDouble("num9")) {
//                    longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9"));
//                }
//                if (realDistance > rule.getDouble("num12")) {
//                    longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12"));
//                }
//                amount = rule.getDouble("num15") + (exceedDistance * rule.getDouble("num16")) + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
//
//
//                // 起步价
//                xpPricingCardVo.setStartPrice(rule.getDouble("num15"));
//                // 时间段
//                xpPricingCardVo.setTime(rule.getString("num14"));
//
//                // 里程
//                xpPricingCardVo.setDistance(realDistance);
//
//                // 里程费
//                double distancePrice = (exceedDistance * rule.getDouble("num16")) + longDistanceOne + longDistanceTwo + longDistanceThree;
//                xpPricingCardVo.setDistancePrice(distancePrice);
//
//                // 时长
//                xpPricingCardVo.setDuration(realTime);
//
//                // 时长费
//                xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num17")));
//
//
//            } else {
//
//                // 高峰处理
//                Calendar calendarStart = Calendar.getInstance();
//                calendarStart.setTime(date);
//                calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0]));
//                calendarStart.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1]));
//
//                Calendar calendarEnd = Calendar.getInstance();
//                calendarEnd.setTime(date);
//                calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0]));
//                calendarEnd.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1]));
//
//                Calendar s2 = Calendar.getInstance();
//                s2.setTime(date);
//                s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0]));
//                s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1]));
//
//                Calendar e2 = Calendar.getInstance();
//                e2.setTime(date);
//                e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0]));
//                e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1]));
//                //高峰时段处理逻辑
//                if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) {
//                    if (realDistance > rule.getDouble("num6")) {
//                        longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6"));
//                    }
//                    if (realDistance > rule.getDouble("num9")) {
//                        longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9"));
//                    }
//                    if (realDistance > rule.getDouble("num12")) {
//                        longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12"));
//                    }
//                    amount = rule.getDouble("num23") + (exceedDistance * rule.getDouble("num24")) + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
//
//
//                    // 起步价
//                    xpPricingCardVo.setStartPrice(rule.getDouble("num23"));
//                    // 时间段
//                    if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
//                        xpPricingCardVo.setTime(rule.getString("num21"));
//                    } else {
//                        xpPricingCardVo.setTime(rule.getString("num22"));
//                    }
//
//
//                    // 里程
//                    xpPricingCardVo.setDistance(realDistance);
//
//                    // 里程费
//                    double distancePrice = (exceedDistance * rule.getDouble("num24")) + longDistanceOne + longDistanceTwo + longDistanceThree;
//                    xpPricingCardVo.setDistancePrice(distancePrice);
//
//                    // 时长
//                    xpPricingCardVo.setDuration(realTime);
//
//                    // 时长费
//                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num25")));
//
//                } else {
//                    //其他时间段的计算
//                    if (realDistance > rule.getDouble("num6")) {
//                        longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6"));
//                    }
//                    if (realDistance > rule.getDouble("num9")) {
//                        longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9"));
//                    }
//                    if (realDistance > rule.getDouble("num12")) {
//                        longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12"));
//                    }
//                    amount = rule.getDouble("num1") + (exceedDistance * rule.getDouble("num4")) + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
//
//
//                    // 起步价
//                    xpPricingCardVo.setStartPrice(rule.getDouble("num1"));
//                    // 时间段
//                    xpPricingCardVo.setTime(rule.getString("startTime") + " - " + rule.getString("endTime"));
//
//                    xpPricingCardVo.setDistancePriceNormal(rule.getDouble("num2"));
//
//                    xpPricingCardVo.setDurationPriceNormal(rule.getDouble("num3"));
//
//
//                    // 里程
//                    xpPricingCardVo.setDistance(realDistance);
//
//                    // 里程费
//                    double distancePrice = (exceedDistance * rule.getDouble("num4")) + longDistanceOne + longDistanceTwo + longDistanceThree;
//                    xpPricingCardVo.setDistancePrice(distancePrice);
//
//                    // 时长
//                    xpPricingCardVo.setDuration(realTime);
//
//                    // 时长费
//                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num5")));
//
//                }
//            }
//        }
//
//        // 订单总额
//        Double startPrice = xpPricingCardVo.getStartPrice();
//        // 先减去起步价重新计算
//        amount = amount - startPrice;
//        // 动态计算费用
//        startPrice= new BigDecimal(startPrice).multiply(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue();
//        amount = amount+startPrice;
//        xpPricingCardVo.setTotalPrice(amount);
//
//
//        return xpPricingCardVo;
//    }
    /**
     * 获取配置  4每X单  5上升X%  6冷却
     *
     * @param rule
     * @param num
     * @return
     */
    private Integer getOrderNum(JSONObject rule, int num) {
        Integer param = rule.getInteger("num" + num);
        return param;
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/holiday/HolidayUtil.java
New file
@@ -0,0 +1,112 @@
package com.xiaopeng.itinerary.utils.holiday;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.utils.HttpUtils;
import com.xiaopeng.common.redis.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.concurrent.TimeUnit;
@Component
public class HolidayUtil {
    @Autowired
    private RedisService redisService;
    public  boolean isHoliday() {
        LocalDate now = LocalDate.now();
        // 判断缓存里面是否有节假日算法
        if (redisService.getCacheObject(CacheConstants.IS_HOLIDAY+now) != null && ("1").equals(redisService.getCacheObject(CacheConstants.IS_HOLIDAY+now).toString())) {
            return true;
        }
        String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" + now);
//        String result = HttpUtil.get("https://publicapi.xiaoai.me/holiday/day?date=" +now);
        JSONObject jsonObject = JSONObject.parseObject(result);
        Object data = jsonObject.get("data");
        JSONArray parse = JSONArray.parse(data.toString());
        for (int i = 0; i < parse.size(); i++) {
            JSONObject jsonObject1 = parse.getJSONObject(i);
            if (jsonObject1.get("daytype").toString().equals("1")) {
                redisService.setCacheObject(CacheConstants.IS_HOLIDAY+now, "1", 1l, TimeUnit.DAYS);
                return true;
            }
        }
        redisService.setCacheObject(CacheConstants.IS_HOLIDAY+now, "0", 1l, TimeUnit.DAYS);
        return false;
    }
    /**
     * 2非工作日  0工作日  1节假日 3调休日
     * @return
     */
    public  Integer isHolidayToday() {
        LocalDate now = LocalDate.now();
        Object cacheObject = redisService.getCacheObject(CacheConstants.IS_DAY + now);
        // 判断缓存里面是否有节假日算法
        if (cacheObject!=null) {
            return Integer.valueOf(cacheObject.toString());
        }
        String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" +now);
//        String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=2026-01-04");
        JSONObject jsonObject = JSONObject.parseObject(result);
        Object data = jsonObject.get("data");
        JSONArray parse = JSONArray.parse(data.toString());
        for (int i = 0; i < parse.size(); i++) {
            JSONObject jsonObject1 = parse.getJSONObject(i);
            String daytype = jsonObject1.get("daytype").toString();
            if (daytype.equals("0")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS);
                return 0;
            } else if (daytype.equals("1")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "1", 1l, TimeUnit.DAYS);
                return 1;
            } else if (daytype.equals("2")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS);
                return 0;
            }
        }
        return 0;
    }
    /**
     * 2非工作日  0工作日  1节假日 3调休日
     * @return
     */
    public  Integer isHolidayToday(LocalDate now) {
        Object cacheObject = redisService.getCacheObject(CacheConstants.IS_DAY + now);
        // 判断缓存里面是否有节假日算法
        if (cacheObject!=null) {
            return Integer.valueOf(cacheObject.toString());
        }
        String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" +now);
//        String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=2026-01-04");
        JSONObject jsonObject = JSONObject.parseObject(result);
        Object data = jsonObject.get("data");
        JSONArray parse = JSONArray.parse(data.toString());
        for (int i = 0; i < parse.size(); i++) {
            JSONObject jsonObject1 = parse.getJSONObject(i);
            String daytype = jsonObject1.get("daytype").toString();
            if (daytype.equals("0")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS);
                return 0;
            } else if (daytype.equals("1")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "1", 1l, TimeUnit.DAYS);
                return 1;
            } else if (daytype.equals("2")) {
                redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS);
                return 0;
            }
        }
        return 0;
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/mango/VehiclePositionService.java
New file
@@ -0,0 +1,92 @@
package com.xiaopeng.itinerary.utils.mango;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class VehiclePositionService {
    @Resource
    private MongoTemplate mongoTemplate;
    /**
     * 查询附近指定距离内且状态正常的车辆
     * @param longitude 经度
     * @param latitude 纬度
     * @param distanceInMeters 距离(米)
     * @return 符合条件的车辆列表
     */
    public List<VehiclePosition> findAvailableVehiclesNearby(double longitude,
                                                             double latitude,
                                                             double distanceInMeters,int num) {
        try {
            // 1. 创建查询点
            Point referencePoint = new Point(longitude, latitude);
            // 2. 构建 NearQuery - 完全使用 NearQuery 的 API
            NearQuery nearQuery = NearQuery.near(referencePoint, Metrics.KILOMETERS)
                    .maxDistance(distanceInMeters / 1000)  // 转成公里
                    .spherical(true);
            // 4. 执行查询 - 明确指定集合名
            GeoResults<VehiclePosition> geoResults = mongoTemplate.geoNear(
                    nearQuery,
                    VehiclePosition.class,
                    "vehicle_position"  // 明确指定集合名,避免映射错误
            );
            // 5. 提取结果
            return geoResults.getContent().stream()
                    .map(geoResult -> geoResult.getContent())
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
    /**
     * 根据车辆ID查询车辆信息
     * @param vehicleId 车辆ID
     * @return 车辆信息,不存在则返回null
     */
    public VehiclePosition findByVehicleId(Long vehicleId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("vehicle_id").is(vehicleId));
        return mongoTemplate.findOne(query, VehiclePosition.class);
    }
    /**
     * 所有的车辆
     */
    public List<VehiclePosition> findAll() {
        // 构建查询条件
        Query query = new Query();
        query.addCriteria(Criteria.where("status").is(1)); // 运营中
        query.addCriteria(Criteria.where("state").is(1));  // 接单中
        // 可以添加排序,按距离排序
        query.with(Sort.by(Sort.Direction.ASC, "point"));
        return mongoTemplate.find(query, VehiclePosition.class);
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/FastDistanceCalculator.java
New file
@@ -0,0 +1,46 @@
package com.xiaopeng.itinerary.utils.map;
public class FastDistanceCalculator {
    private static final double R = 6371.0; // 地球半径(千米)
    private static final double DEG_TO_RAD = Math.PI / 180.0;
    /**
     * 快速近似算法 - 性能极高,误差<0.5%
     * 适合需要大量计算的场景(如KNN、聚类)
     * 米
     */
    public static double distanceFast(double lat1, double lon1,
                                      double lat2, double lon2) {
        double x = (lon2 - lon1) * DEG_TO_RAD * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD);
        double y = (lat2 - lat1) * DEG_TO_RAD;
        return R * Math.sqrt(x * x + y * y)*1000;
    }
    /**
     * 快速近似算法 - 误差<0.5%
     * 适合需要大量计算的场景(如KNN、聚类)
     * 千米
     */
    public static double distanceFastKm(double lat1, double lon1,
                                      double lat2, double lon2) {
        double x = (lon2 - lon1) * DEG_TO_RAD * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD);
        double y = (lat2 - lat1) * DEG_TO_RAD;
        return R * Math.sqrt(x * x + y * y);
    }
    /**
     * 平面近似 - 极高性能,适用于小范围(城市内)
     */
    public static double distanceFlatApprox(double lat1, double lon1,
                                           double lat2, double lon2) {
        // 每度纬度约111km,每度经度随纬度变化
        double kmPerDegreeLat = 111.0;
        double kmPerDegreeLon = 111.0 * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD);
        double dLat = lat2 - lat1;
        double dLon = lon2 - lon1;
        return Math.sqrt(Math.pow(dLat * kmPerDegreeLat, 2) +
                        Math.pow(dLon * kmPerDegreeLon, 2));
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/GeodesyUtil.java
New file
@@ -0,0 +1,71 @@
package com.xiaopeng.itinerary.utils.map;
import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GeodeticCalculator;
import org.gavaghan.geodesy.GeodeticCurve;
import org.gavaghan.geodesy.GlobalCoordinates;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
 * 计算两个金纬度坐标之间的直线距离
 */
@Component
public class GeodesyUtil {
    /**
     * 获取直线距离
     * @param fromLonLat
     * @param toLonLat
     * @return
     */
    public Map<String, Double> getDistance(String fromLonLat, String toLonLat){
        String[] from = fromLonLat.split(",");
        String[] to = toLonLat.split(",");
        GlobalCoordinates source = new GlobalCoordinates(Double.valueOf(from[1]), Double.valueOf(from[0]));
        GlobalCoordinates target = new GlobalCoordinates(Double.valueOf(to[1]), Double.valueOf(to[0]));
        double Sphere = getDistanceMeter(source, target, Ellipsoid.Sphere);
        double WGS84 = getDistanceMeter(source, target, Ellipsoid.WGS84);
        double GRS80 = getDistanceMeter(source, target, Ellipsoid.GRS80);
        double GRS67 = getDistanceMeter(source, target, Ellipsoid.GRS67);
        double ANS = getDistanceMeter(source, target, Ellipsoid.ANS);
        double WGS72 = getDistanceMeter(source, target, Ellipsoid.WGS72);
        double Clarke1858 = getDistanceMeter(source, target, Ellipsoid.Clarke1858);
        double Clarke1880 = getDistanceMeter(source, target, Ellipsoid.Clarke1880);
//        System.out.println("Sphere坐标系计算结果:"+Sphere + "米");
//        System.out.println("WGS84坐标系计算结果:"+WGS84 + "米");
//        System.out.println("GRS80坐标系计算结果:"+GRS80 + "米");
//        System.out.println("GRS67坐标系计算结果:"+GRS67 + "米");
//        System.out.println("ANS坐标系计算结果:"+ANS + "米");
//        System.out.println("WGS72坐标系计算结果:"+WGS72 + "米");
//        System.out.println("Clarke1858坐标系计算结果:"+Clarke1858 + "米");
//        System.out.println("Clarke1880坐标系计算结果:"+Clarke1880 + "米");
        Map<String, Double> map = new HashMap<>();
        map.put("Sphere", Sphere);
        map.put("WGS84", WGS84);
        map.put("GRS80", GRS80);
        map.put("GRS67", GRS67);
        map.put("ANS", ANS);
        map.put("WGS72", WGS72);
        map.put("Clarke1858", Clarke1858);
        map.put("Clarke1880", Clarke1880);
        return map;
    }
    private double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid){
        //创建GeodeticCalculator,调用计算方法,传入坐标系、经纬度用于计算距离
        GeodeticCurve geoCurve = new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo);
        return geoCurve.getEllipsoidalDistance();
    }
    public static void main(String[] ages){
        GeodesyUtil geodesyUtil = new GeodesyUtil();
        geodesyUtil.getDistance("115.481028,39.989643", "114.465302,40.004717");
    }
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/vo/SaveOrderVo.java
New file
@@ -0,0 +1,12 @@
package com.xiaopeng.itinerary.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "保存订单返回")
public class SaveOrderVo {
    @ApiModelProperty(value = "订单id 0静默不推单")
    private Long orderId;
}
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages.properties
@@ -1,2 +1,4 @@
# 中文
path.not.found=?????
to.work= ??????????,??????
welcome.message=欢迎来到我们的应用程序!
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_en_US.properties
@@ -1,2 +1,4 @@
# English (US)
path.not.found=Path does not exist
to.work=\ The current pick-up and drop-off points are too close to each other. It is recommended to walk there
welcome.message=Welcome to our application!
xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -1,2 +1,4 @@
# 中文
path.not.found=?????
to.work=\ ??????????,??????
welcome.message=欢迎来到我们的应用程序!
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceAreaMapper.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.itinerary.mapper.XpElectronicFenceAreaMapper">
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceMapper.xml
New file
@@ -0,0 +1,10 @@
<?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.itinerary.mapper.XpElectronicFenceMapper">
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, electronic_name, electronic_number, status, effect_type, effect_time, failure_type, failure_time, operation_strategy, is_deleted, create_time, update_time, create_by, update_by
    </sql>
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpOpenCityMapper.xml
New file
@@ -0,0 +1,6 @@
<?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.itinerary.mapper.XpOpenCityMapper">
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPointMapper.xml
New file
@@ -0,0 +1,34 @@
<?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.itinerary.mapper.XpPointMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xiaopeng.api.model.operation.XpPoint">
        <id column="id" property="id" />
        <result column="point_name" property="pointName" />
        <result column="point_poi" property="pointPoi" />
        <result column="point_number" property="pointNumber" />
        <result column="operation_status" property="operationStatus" />
        <result column="point_type" property="pointType" />
        <result column="verification_status" property="verificationStatus" />
        <result column="parking_time_limit" property="parkingTimeLimit" />
        <result column="operation_type" property="operationType" />
        <result column="effective_period" property="effectivePeriod" />
        <result column="effective_time_period" property="effectiveTimePeriod" />
        <result column="remarks" property="remarks" />
        <result column="point_pictures" property="pointPictures" />
        <result column="point_lon" property="pointLon" />
        <result column="point_lat" property="pointLat" />
        <result column="is_deleted" property="deleted" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="create_by" property="createBy" />
        <result column="update_by" property="updateBy" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id,electronic_area_id, point_name, point_poi, point_number, operation_status, point_type, verification_status, parking_time_limit, operation_type, effective_period, effective_time_period, remarks, point_pictures, point_lon, point_lat, is_deleted, create_time, update_time, create_by, update_by
    </sql>
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPricingMapper.xml
New file
@@ -0,0 +1,6 @@
<?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.itinerary.mapper.XpPricingMapper">
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpRoutePlanMapper.xml
New file
@@ -0,0 +1,10 @@
<?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.itinerary.mapper.XpRoutePlanMapper">
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, open_city_id, plan_name, province_name, province_code, city_name, city_code, district_name, district_code, remarks, route_plan_rule, audit_status, audit_remarks, audit_user_id, audit_time, is_deleted, create_time, update_time, create_by, update_by
    </sql>
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpSystemSettingMapper.xml
New file
@@ -0,0 +1,6 @@
<?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.itinerary.mapper.XpSystemSettingMapper">
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpTradingStrategyMapper.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.itinerary.mapper.XpTradingStrategyMapper">
</mapper>
xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpWhitelistMapper.xml
New file
@@ -0,0 +1,10 @@
<?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.itinerary.mapper.XpWhitelistMapper">
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, nick_name, account, status, is_priority_dispatch, whitelist_source, is_deleted, create_time, update_time, create_by, update_by
    </sql>
</mapper>
xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java
@@ -37,9 +37,9 @@
    private XpVehicleClient xpVehicleClient;
    /**
     * 车辆详情-车辆调度-发车
     * 车辆详情-车辆调度-发车 收车
     */
    @ApiOperation(value = "车辆运维-车辆调度-发车", tags = {"运维APP-车辆管理"})
    @ApiOperation(value = "车辆运维-车辆调度-发车/收车", tags = {"运维APP-车辆管理"})
    @PostMapping("/api/v1/vehicle/vehicleDispatchDeparture")
    public R<String> vehicleDispatchDeparture(@RequestBody VehicleDispatchDepartureDTO dto) {
        // 查询车辆
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java
@@ -72,6 +72,16 @@
        return xpPointClient.edit(pointAddDto);
    }
    /**
     * 批量删除点位
     */
    @GetMapping("/deletePoint")
    @ResponseBody
    @ApiOperation(value = "删除点位")
    public R deletePoint(@RequestParam("ids") String ids) {
        xpPointClient.deletePoint(ids);
        return R.ok();
    }
    /**
     * 编辑点位
     */
    @GetMapping("/queryById")
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointManagementController.java
@@ -20,6 +20,7 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -70,6 +71,16 @@
        return R.ok();
    }
    /**
     * 批量删除点位
     */
    @GetMapping("/deletePoint")
    @ResponseBody
    @ApiOperation(value = "删除点位")
    public R deletePoint(@RequestParam("ids") String ids) {
        xpPointService.removeBatchByIds(Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList()));
        return R.ok();
    }
    /**
     * 编辑点位
     */
    @PostMapping("/editPoint")
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java
@@ -1,10 +1,14 @@
package com.xiaopeng.operation.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.dto.account.UserResourcesDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.model.operation.XpWhitelist;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
@@ -17,6 +21,7 @@
import com.xiaopeng.operation.dto.XpOrderDto;
import com.xiaopeng.operation.query.PricingPageListQuery;
import com.xiaopeng.operation.service.IXpGeoQueryEntityService;
import com.xiaopeng.operation.service.IXpWhitelistService;
import com.xiaopeng.operation.service.XpPricingService;
import com.xiaopeng.operation.vo.PricingPageListVO;
import com.xiaopeng.operation.vo.SaveOrderVo;
@@ -47,11 +52,15 @@
    private final XpPricingService xpPricingService;
    private final IXpGeoQueryEntityService xpGeoQueryEntityService;
    private final UserResourcesClient userResourcesClient;
    private final IXpWhitelistService xpWhitelistService;
    public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService) {
    public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService, UserResourcesClient userResourcesClient, IXpWhitelistService xpWhitelistService) {
        this.xpPricingService = xpPricingService;
        this.xpGeoQueryEntityService = xpGeoQueryEntityService;
        this.userResourcesClient = userResourcesClient;
        this.xpWhitelistService = xpWhitelistService;
    }
@@ -102,6 +111,31 @@
    /**
     * 获取用户是否在白名单中
     */
    @PostMapping("/base/v1/mini-program/pricing/isInWhiteList")
    @ApiOperation(value = "获取用户是否在白名单中", tags = {"小程序-获取用户是否在白名单中"}, response = Boolean.class)
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<Boolean> isInWhiteList(HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid);
        if (baseUserByUid!=null) {
            try {
                Object data = baseUserByUid.getData();
                String phone = JSONObject.parseObject(data.toString()).getString("mobile");
                long count = xpWhitelistService.count(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, phone).eq(XpWhitelist::getStatus, 1));
                return R.ok(count>0);
            }catch (Exception e){
                log.error("获取用户信息失败: {}", e.getMessage());
                return R.ok(false);
            }
        }
        return R.ok(false);
    }
    /**
     * 保存信息
     * @param
     * @return
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpSystemSettingController.java
@@ -32,7 +32,7 @@
    @GetMapping("/base/v1/mini-program/support/phone")
    @ApiOperation(value = "获取客服电话",tags = {"小程序首页"},response = String.class)
    public AjaxResult<CustomerServicePhoneVo> getBannerList() {
    public AjaxResult<CustomerServicePhoneVo> getCustomerServicePhone() {
        Object customerServicePhone = redisService.getCacheObject(CacheConstants.CUSTOMER_SERVICE_PHONE);
        if(customerServicePhone!=null){
            return success(new CustomerServicePhoneVo(customerServicePhone.toString()));
xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties
@@ -18,3 +18,4 @@
pricing.json.invalid=价格配置JSON格式不正确
pricing.rule.empty=价格规则不能为空
no.current.point=?????????
path.not.found=?????
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -18,3 +18,4 @@
pricing.json.invalid=Invalid JSON format for pricing configuration
pricing.rule.empty=Price rule cannot be empty
no.current.point=The current location is not open for service
path.not.found=Path does not exist
xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -18,3 +18,4 @@
pricing.json.invalid=价格配置JSON格式不正确
pricing.rule.empty=价格规则不能为空
no.current.point=?????????
path.not.found=?????
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java
@@ -1,6 +1,8 @@
package com.xiaopeng.order.model.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xiaopeng.api.dto.operation.LonLatDto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -69,10 +71,34 @@
    private String endAddress;
    @ApiModelProperty(value = "出发站点名称")
    private String startStationName;
    private String startPointName;
    @ApiModelProperty(value = "终点站点名称")
    private String endStationName;
    private String endPointName;
    @ApiModelProperty(value = "出发地经度值")
    private Double startLon;
    @ApiModelProperty(value = "出发地纬度值")
    private Double startLat;
    @ApiModelProperty(value = "目的地经度")
    private Double endLon;
    @ApiModelProperty(value = "目的地纬度")
    private Double endLat;
    @ApiModelProperty(value = "区域id")
    private Long xpElectronicFenceAreaId;
    @ApiModelProperty(value = "下单站点id")
    @TableField("start_point_id")
    private Long startPointId;
    @ApiModelProperty(value = "下单终点站点Id")
    @TableField("end_point_id")
    private Long endPointId;
    @ApiModelProperty(value = "上车点")
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -736,8 +736,9 @@
        cancelTaxiPassengerVo.setAppId("");
        BaseVehicleUserDto baseVehicleUserDto = userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo);
        if (baseVehicleUserDto.getCode() != 200) {
            throw new RuntimeException("取消订单失败");
        if (baseVehicleUserDto==null) {
            // TODO 本地无法调通
//            throw new RuntimeException("取消订单失败");
        }
        baseMapper.updateById(order);
@@ -1439,6 +1440,11 @@
        res.setKilometer(BigDecimal.valueOf(order.getMileageKilometers()));
        LocalDateTime createTime = order.getCreateTime();
        LocalDateTime tripEnd = order.getTripEnd();
        if (tripEnd==null){
            tripEnd = LocalDateTime.now();
            order.setTripEnd(tripEnd);
            this.updateById(order);
        }
        // 计算行程时间 单位分钟
        // 计算时间差
        Duration durationBetween = Duration.between(createTime, tripEnd);
@@ -1639,7 +1645,7 @@
        XpOrderDetailVO xpOrderDetailVO = new XpOrderDetailVO();
        XpOrder xpOrder = this.baseMapper.selectOne(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getAppUserId, uid).in(XpOrder::getStatus, Arrays.asList(1,2,3, 4, 5, 6, 7, 8)).orderByDesc(BaseModel::getCreateTime).last("LIMIT 1"));
        if (xpOrder == null) {
            return xpOrderDetailVO;
            return null;
        }
        BeanUtils.copyProperties(xpOrder, xpOrderDetailVO);
        // 算时间