From ac52ff126bf9ca4eb7018ae1252b39fa6f91a2bb Mon Sep 17 00:00:00 2001
From: 无关风月 <443237572@qq.com>
Date: 星期五, 13 二月 2026 22:02:24 +0800
Subject: [PATCH] bug修改,计算费用工具类

---
 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java                     |  300 +++++++++++++++++++++
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java                           |   24 +
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java                                |   70 +++++
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java     |   26 +
 xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java   |   31 ++
 xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java      |   29 ++
 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java                   |   17 +
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java |   36 ++
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java       |   21 +
 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java                      |   17 +
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java                  |   24 +
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java                    |   83 +++++
 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java           |   23 +
 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java                                   |   13 
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java                |   22 +
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java          |   46 +++
 xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java            |   37 ++
 17 files changed, 819 insertions(+), 0 deletions(-)

diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java
new file mode 100644
index 0000000..10c46a0
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java
@@ -0,0 +1,83 @@
+package com.xiaopeng.api.dto.automaticPilot;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class VehicleBaseRespDto implements Serializable {
+
+    private static final long serialVersionUID = -1251766408952592117L;
+
+    /**
+     * 车架号
+     */
+    private String vin;
+
+    /**
+     * 大屏虚拟id(兼容旧接口)
+     */
+    private String vid;
+
+    /**
+     * 大屏硬件id
+     */
+    private String cduId;
+
+    /**
+     * sim卡id
+     */
+    private String iccid;
+
+    /**
+     * 物料编码id
+     */
+    private String vehicleMaterielId;
+
+    /**
+     * 车型code.
+     * DB, DC, DE就是G3
+     * ED 就是 P7
+     * VehicleTypeType
+     */
+    private String vehicleTypeCode;
+
+    /**
+     * 车型名称,eg:D10,D20,D21..
+     */
+    private String vehicleTypeName;
+
+    /**
+     * 对外名称,eg:1.0,G3,P5...
+     */
+    private String vehicleSaleName;
+
+
+    /**
+     * 车牌信息(车牌号)
+     */
+    private String plateNo;
+
+    /**
+     * 车辆用途
+     */
+    private Integer usage;
+
+    /**
+     * 公告车型code
+     */
+    private String proclamationCode;
+
+
+    /***
+     * 给实时流
+     */
+    private String batchNo;
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java
new file mode 100644
index 0000000..7d34ea3
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java
@@ -0,0 +1,24 @@
+package com.xiaopeng.api.dto.operation;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@ApiModel(value = "改派订单参数")
+public class ReassignOrderDTO implements Serializable {
+
+    @ApiModelProperty(value = "播单次数")
+    private Integer count;
+    @ApiModelProperty(value = "用户id")
+    private String uid;
+    @ApiModelProperty(value = "经度")
+    private Double longitude;
+    @ApiModelProperty(value = "纬度")
+    private Double latitude;
+    @ApiModelProperty(value = "订单号")
+    private String orderNo;
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java
new file mode 100644
index 0000000..efeedeb
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java
@@ -0,0 +1,36 @@
+package com.xiaopeng.api.factory.automaticPilot;
+
+import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
+import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
+import com.xiaopeng.common.core.domain.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * 车辆整备Feign客户端降级工厂
+ *
+ * @author mitao
+ * @since 2026-02-11
+ */
+@Slf4j
+@Component
+public class OrderMissionClientFallbackFactory implements FallbackFactory<OrderMissionClient> {
+
+    @Override
+    public OrderMissionClient create(Throwable cause) {
+        return new OrderMissionClient() {
+            @Override
+            public R<Boolean> pushOrder(PushOrderDto dto) {
+                log.error("远程调用获取检查点位信息异常:{}", cause.getMessage());
+                return R.fail("远程调用获取检查点位信息异常");
+            }
+
+            @Override
+            public R<Boolean> cancelOrder(PushOrderDto dto) {
+                log.error("远程调用获取检查点位信息异常:{}", cause.getMessage());
+                return null;
+            }
+        };
+    }
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java
new file mode 100644
index 0000000..4684bf5
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java
@@ -0,0 +1,21 @@
+package com.xiaopeng.api.factory.automaticPilot;
+
+import com.xiaopeng.api.feignClient.automaticPilot.XpuVinClient;
+import com.xiaopeng.common.core.domain.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FallbackFactory;
+
+
+@Slf4j
+public class XpuVimClientFallbackFactory implements FallbackFactory<XpuVinClient> {
+    @Override
+    public XpuVinClient create(Throwable cause) {
+        return new XpuVinClient() {
+            @Override
+            public R<String> getXpuIdByVin(String vin) {
+                log.error("查询XPUID失败: {}", cause.getMessage());
+                return null;
+            }
+        };
+    }
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java
new file mode 100644
index 0000000..eaf9a31
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java
@@ -0,0 +1,26 @@
+package com.xiaopeng.api.factory.operation;
+
+import com.xiaopeng.api.dto.operation.ReassignOrderDTO;
+import com.xiaopeng.api.feignClient.operation.ReassignOrderClient;
+import com.xiaopeng.common.core.domain.R;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.openfeign.FallbackFactory;
+
+public class ReassignOrderClientFallbackFactory implements FallbackFactory<ReassignOrderClient> {
+
+    private static final Logger log = LoggerFactory.getLogger(ReassignOrderClientFallbackFactory.class);
+
+    @Override
+    public ReassignOrderClient create(Throwable cause) {
+        log.error("改派服务调用失败: {}", cause.getMessage());
+        return new ReassignOrderClient(){
+
+            @Override
+            public R<String> reassignOrder(ReassignOrderDTO dto) {
+                return R.fail("改派派单失败,"+cause.getMessage());
+            }
+        };
+    }
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java
new file mode 100644
index 0000000..ca2a6f7
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java
@@ -0,0 +1,37 @@
+package com.xiaopeng.api.feignClient.automaticPilot;
+
+import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
+import com.xiaopeng.api.factory.automaticPilot.OrderMissionClientFallbackFactory;
+import com.xiaopeng.common.core.domain.R;
+import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+/**
+ * 小程序用户Feign客户端
+ *
+ * @author mitao
+ */
+@FeignClient(contextId = "OrderMissionClient",
+        value = ServiceNameConstantsEnum.XP_AUTOMATIC_PILOT_SERVICE,
+        fallbackFactory = OrderMissionClientFallbackFactory.class)
+public interface OrderMissionClient {
+
+    /**
+     * 下发命令
+     * @param
+     * @return
+     */
+    @PostMapping("/inner/pushOrder")
+     R<Boolean> pushOrder(@RequestBody PushOrderDto dto);
+
+
+    /**
+     * 取消订单
+     * @param
+     * @return
+     */
+    @PostMapping("/inner/cancelOrder")
+    R<Boolean> cancelOrder(@RequestBody PushOrderDto dto);
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java
new file mode 100644
index 0000000..c93e852
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java
@@ -0,0 +1,46 @@
+package com.xiaopeng.api.feignClient.automaticPilot;
+
+import com.xiaopeng.api.dto.automaticPilot.VehicleBaseRespDto;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+import java.util.Map;
+
+@FeignClient(name = "xp-vmp-open-boot")
+public interface VmpOpenServiceClient {
+
+    /**
+     * 通过vin获取车辆基本信息
+     *
+     * @param vin
+     * @return
+     */
+    @GetMapping("/logan/vehicleInfo/vin")
+    VehicleBaseRespDto getVehicleInfoByVin(@RequestParam("vin") String vin, @RequestParam("respList") List<String> respList);
+
+    /**
+     * 查询车辆绑定信息, 通过vin获取车辆xpu id
+     * typeList类型,1:XPUID,2:待扩展
+     * 参数示例:?vin=TESTE290000000000&typeList=1,2
+     * 返回示例:{
+     *     "1": "01010101010101010101010101010101",
+     *     "2": "xxx"
+     * }
+     */
+    @GetMapping("/logan/vehicle/bind/info/query")
+    Map<String, String> getVehicleBindInfoByVin(@RequestParam("vin") String vin, @RequestParam("typeList") List<String> typeList);
+
+    /**
+     * 通过车辆绑定信息获取车架号, 通过xpu id获取车辆vin
+     * type类型,1:XPUID,2:待扩展
+     * 参数示例:?value=00000000000000000000000000000001&type=1
+     * 返回示例:{
+     *     L1NSPGHB9RB900099
+     * }
+     */
+    @GetMapping("/logan/vehicle/bind/info/getVin")
+    String getVinByXpuId(@RequestParam("value") String xpuId, @RequestParam("type") Integer type);
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java
new file mode 100644
index 0000000..17c5e84
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java
@@ -0,0 +1,24 @@
+package com.xiaopeng.api.feignClient.automaticPilot;
+
+
+import com.xiaopeng.api.factory.automaticPilot.XpuVimClientFallbackFactory;
+import com.xiaopeng.common.core.domain.R;
+import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@FeignClient(contextId = "XpuVinClient",
+        value = ServiceNameConstantsEnum.XP_AUTOMATIC_PILOT_SERVICE,
+        fallbackFactory = XpuVimClientFallbackFactory.class)
+public interface XpuVinClient {
+
+
+    /**
+     * 根据车辆VIN查询XPUID
+     * @param vin
+     * @return
+     */
+    @GetMapping("/xpu/getXpuIdByVin")
+    R<String> getXpuIdByVin(@RequestParam("vin") String vin);
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java
new file mode 100644
index 0000000..3a7ab85
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java
@@ -0,0 +1,22 @@
+package com.xiaopeng.api.feignClient.operation;
+
+import com.xiaopeng.api.dto.operation.ReassignOrderDTO;
+import com.xiaopeng.api.factory.operation.ReassignOrderClientFallbackFactory;
+import com.xiaopeng.api.factory.operation.XpSystemSettingClientFallbackFactory;
+import com.xiaopeng.api.model.operation.XpFeedbackType;
+import com.xiaopeng.common.core.domain.R;
+import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient(contextId = "ReassignOrderClient", value = ServiceNameConstantsEnum.XP_OPERATION_SERVICE, fallbackFactory = ReassignOrderClientFallbackFactory.class)
+public interface ReassignOrderClient {
+
+
+    @PostMapping("/inner/v1/order/reassignOrder")
+    R<String> reassignOrder(@RequestBody ReassignOrderDTO dto);
+
+}
diff --git a/xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java
new file mode 100644
index 0000000..a6a497c
--- /dev/null
+++ b/xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java
@@ -0,0 +1,70 @@
+package com.xiaopeng.api.model.order;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.xiaopeng.common.core.web.domain.BaseModel;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 订单费用明细表
+ * </p>
+ *
+ * @author mitao
+ * @since 2026-02-03
+ */
+@Data
+@TableName("xp_order_detail")
+@ApiModel(value = "XpOrderDetail对象", description = "订单费用明细表")
+public class XpOrderDetail extends BaseModel {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty(value = "自增id")
+    private Long id;
+
+    @ApiModelProperty(value = "订单id")
+    private Long orderId;
+
+    @ApiModelProperty(value = "type=1 普通时段 type=2 夜间行驶时段 3高峰时段 4节假日时段")
+    private Integer type;
+    @ApiModelProperty(value = "1=起步价 2=里程费")
+    private Integer detailType;
+
+    @ApiModelProperty(value = "时段")
+    private String time;
+
+    @ApiModelProperty(value = "时长分钟")
+    private Integer duration;
+
+    @ApiModelProperty(value = "里程")
+    private BigDecimal mileage;
+
+    @ApiModelProperty(value = "金额")
+    private BigDecimal money;
+
+    @TableLogic
+    @ApiModelProperty(value = "是否删除 0=未删除 1=已删除")
+    private Boolean isDeleted;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "修改时间")
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建人")
+    private String createBy;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "修改人")
+    private String updateBy;
+}
diff --git a/xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java b/xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java
new file mode 100644
index 0000000..b130bf0
--- /dev/null
+++ b/xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java
@@ -0,0 +1,31 @@
+package com.xiaopeng.automaticPilot.controller;
+
+
+import com.xiaopeng.automaticPilot.util.XPUUtil;
+import com.xiaopeng.common.core.domain.R;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping("")
+public class XpuVinController {
+
+    @Resource
+    private XPUUtil xpuUtil;
+
+
+    /**
+     * 根据车辆VIN查询XPUID
+     * @param vin
+     * @return
+     */
+    @GetMapping("/xpu/getXpuIdByVin")
+    public R<String> getXpuIdByVin(@RequestParam("vin") String vin){
+        String xpuId = xpuUtil.getXpuIdByVin(vin);
+        return R.ok(xpuId);
+    }
+}
diff --git a/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java b/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java
new file mode 100644
index 0000000..c73af44
--- /dev/null
+++ b/xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java
@@ -0,0 +1,29 @@
+package com.xiaopeng.operation.controller;
+
+import com.xiaopeng.common.core.domain.R;
+import com.xiaopeng.api.dto.operation.ReassignOrderDTO;
+import com.xiaopeng.operation.utils.TaskOrderUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * token 控制
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@RestController
+public class ReassignOrderController {
+    @Autowired
+    private TaskOrderUtil taskOrderUtil;
+
+    @PostMapping("/inner/v1/order/reassignOrder")
+    public R<String> reassignOrder(@RequestBody ReassignOrderDTO dto) {
+        taskOrderUtil.reassignPushOrder(dto.getCount(),dto.getUid(),dto.getLongitude(),dto.getLatitude(),dto.getOrderNo());
+        return R.ok();
+    }
+
+}
diff --git a/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java
new file mode 100644
index 0000000..6116172
--- /dev/null
+++ b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java
@@ -0,0 +1,17 @@
+package com.xiaopeng.order.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xiaopeng.api.model.order.XpOrderCancel;
+import com.xiaopeng.api.model.order.XpOrderDetail;
+
+/**
+ * <p>
+ * 订单取消 Mapper 接口
+ * </p>
+ *
+ * @author mitao
+ * @since 2026-02-03
+ */
+public interface XpOrderDetailMapper extends BaseMapper<XpOrderDetail> {
+
+}
diff --git a/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java
new file mode 100644
index 0000000..d07d800
--- /dev/null
+++ b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java
@@ -0,0 +1,17 @@
+package com.xiaopeng.order.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xiaopeng.api.model.order.XpOrderCancel;
+import com.xiaopeng.api.model.order.XpOrderDetail;
+
+/**
+ * <p>
+ * 订单取消 服务类
+ * </p>
+ *
+ * @author mitao
+ * @since 2026-02-03
+ */
+public interface IXpOrderDetailService extends IService<XpOrderDetail> {
+
+}
diff --git a/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java
new file mode 100644
index 0000000..449500d
--- /dev/null
+++ b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java
@@ -0,0 +1,23 @@
+package com.xiaopeng.order.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xiaopeng.api.model.order.XpOrderCancel;
+import com.xiaopeng.api.model.order.XpOrderDetail;
+import com.xiaopeng.order.mapper.XpOrderCancelMapper;
+import com.xiaopeng.order.mapper.XpOrderDetailMapper;
+import com.xiaopeng.order.service.IXpOrderCancelService;
+import com.xiaopeng.order.service.IXpOrderDetailService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 订单取消 服务实现类
+ * </p>
+ *
+ * @author mitao
+ * @since 2026-02-03
+ */
+@Service
+public class XpOrderDetailServiceImpl extends ServiceImpl<XpOrderDetailMapper, XpOrderDetail> implements IXpOrderDetailService {
+
+}
diff --git a/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java
new file mode 100644
index 0000000..f6fbe0b
--- /dev/null
+++ b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java
@@ -0,0 +1,13 @@
+package com.xiaopeng.order.test;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class GpsPoint {
+    private Double distance;   // 与上一点距离(米)
+    private Long locatetime;   // 毫秒时间戳
+    private Date createTime;   // 创建日期
+    // ... getter & setter
+}
\ No newline at end of file
diff --git a/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java
new file mode 100644
index 0000000..1d3310e
--- /dev/null
+++ b/xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java
@@ -0,0 +1,300 @@
+package com.xiaopeng.order.test;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 出租车计费引擎服务
+ */
+public class TaxiPricingDemoService {
+
+    public static void main(String[] args) {
+        PricingConfig config = setupMockConfig();
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(2026, Calendar.MARCH, 13, 14, 40, 15);
+        Date orderTime = cal.getTime();
+
+        // 模拟总里程 25.0 km,以触发多级远途加价
+        double totalMillige = 7.874;
+
+        // 模拟 GPS 轨迹点
+        List<GpsPoint> points = createMockGpsPoints(orderTime);
+
+        TaxiPricingService service = new TaxiPricingService();
+        Map<String, BigDecimal> bill = service.calculateOrderPrice(orderTime, totalMillige, points, config);
+
+        System.err.println("======= 订单计费账单 =======");
+        bill.forEach((detail, price) -> System.err.println(detail + ": " + price + " 元"));
+
+        BigDecimal totalAmount = bill.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
+        System.err.println("--------------------------");
+        System.err.println("应付总额: " + totalAmount + " 元");
+    }
+
+    public static class TaxiPricingService {
+        public Map<String, BigDecimal> calculateOrderPrice(Date orderTime, double totalMileageKm, List<GpsPoint> points, PricingConfig config) {
+            Map<String, BigDecimal> result = new LinkedHashMap<>();
+            BigDecimal totalMileage = BigDecimal.valueOf(totalMileageKm);
+
+            PricingRule baseRule = config.findMatchedRule(orderTime);
+            result.put("起步价(" + baseRule.ruleName + ")"+"-"+baseRule.start+"-"+baseRule.end, baseRule.baseFare);
+
+            if (totalMileage.compareTo(baseRule.baseKm) <= 0) {
+                return formatMap(result);
+            }
+
+            BigDecimal remainBaseM = baseRule.baseKm.multiply(new BigDecimal("1000"));
+            BigDecimal remainBaseMs = baseRule.baseMin.multiply(new BigDecimal("60000"));
+            BigDecimal currentTotalM = BigDecimal.ZERO;
+            BigDecimal currentTotalMs = BigDecimal.ZERO;
+
+            boolean isOverBaseM = false;
+            boolean isOverBaseMs = false;
+
+            // 超出里程和分钟数的集合
+            Map<String, BigDecimal> bill = new LinkedHashMap<>();
+            Map<String, BigDecimal> activeRuleNameRate = new LinkedHashMap<>();
+            Set<String> activeRuleNameList = new HashSet<>();
+
+            for (int i = 1; i < points.size(); i++) {
+                GpsPoint curr = points.get(i);
+                GpsPoint prev = points.get(i - 1);
+
+                BigDecimal segM = BigDecimal.valueOf(curr.getDistance());
+                BigDecimal segMs = BigDecimal.valueOf(curr.getLocatetime() - prev.getLocatetime());
+                currentTotalM = currentTotalM.add(segM); // 累计当前已行驶的总里程
+                currentTotalMs = currentTotalMs.add(segMs); // 累计当前已行驶的总时间
+
+                PricingRule activeRule = config.findMatchedRule(new Date(curr.getLocatetime()));
+
+                // 扣除起步额度逻辑
+                BigDecimal billM = BigDecimal.ZERO;
+                if (currentTotalM.compareTo(remainBaseM) > 0) {
+//                    billM = currentTotalM.subtract(remainBaseM);
+                    isOverBaseM = true;
+                }
+                BigDecimal billMs = BigDecimal.ZERO;
+                if (currentTotalMs.compareTo(remainBaseMs) > 0) {
+//                    billMs = currentTotalMs.subtract(remainBaseMs);
+                    isOverBaseMs = true;
+                }
+                // 1. 基础费率计算
+//                BigDecimal distFee = billM.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP).multiply(activeRule.perKmRate);
+//                BigDecimal timeFee = billMs.multiply(activeRule.perMinRate);
+
+                // 2. 远途加价计算 (基于区间判定)
+//                BigDecimal longExtra = calculateLongDistExtra(currentTotalM, billM, config.longSteps);
+
+//                BigDecimal totalSeg = distFee.add(timeFee).add(longExtra);
+                // 超出里程
+                if (isOverBaseM) {
+                    bill.put(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml", bill.getOrDefault(activeRule.ruleName + activeRule.start + "-" + activeRule.end + "_Ml", BigDecimal.ZERO).add(segM));
+                }
+                // 超出时间
+                if (isOverBaseMs) {
+                    bill.put(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms", bill.getOrDefault(activeRule.ruleName + activeRule.start + "-" + activeRule.end + "_Ms", BigDecimal.ZERO).add(segMs));
+                }
+
+                // 若不存在规则名称,则添加
+                activeRuleNameRate.computeIfAbsent(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml", k -> activeRule.perKmRate); // 超出里程的加价
+                activeRuleNameRate.computeIfAbsent(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms",k ->activeRule.perMinRate); // 超出时间的加价
+
+                activeRuleNameList.add(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ml");
+                activeRuleNameList.add(activeRule.ruleName + "-"+activeRule.start + "-" + activeRule.end + "_Ms");
+//                result.put(activeRule.ruleName, result.getOrDefault(activeRule.ruleName, BigDecimal.ZERO).add(totalSeg));
+            }
+            // 1. 基础费率计算
+            for (String ruleName : activeRuleNameList) {
+                String[] split = ruleName.split("_");
+                if(ruleName.endsWith("_Ml")){
+                    BigDecimal exceedValue = bill.get(ruleName);
+                    if(Objects.nonNull(exceedValue)){
+                        exceedValue = exceedValue.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP);
+                    }else{
+                        exceedValue = BigDecimal.ZERO;
+                    }
+                    BigDecimal exceedRate = activeRuleNameRate.get(ruleName);
+                    result.put(split[0], result.getOrDefault(split[0], BigDecimal.ZERO).add(exceedValue.multiply(exceedRate)));
+                }else{
+                    BigDecimal exceedValue = bill.get(ruleName);
+                    if(Objects.nonNull(exceedValue)){
+                        exceedValue = exceedValue.divide(new BigDecimal(60000), 2, RoundingMode.HALF_UP);
+                    }else{
+                        exceedValue = BigDecimal.ZERO;
+                    }
+                    BigDecimal exceedRate = activeRuleNameRate.get(ruleName);
+                    result.put(split[0], result.getOrDefault(split[0], BigDecimal.ZERO).add(exceedValue.multiply(exceedRate)));
+                }
+            }
+            // 2. 远途加价计算 (基于区间判定)
+            BigDecimal longExtra = calculateLongDistExtra(BigDecimal.valueOf(totalMileageKm), config.longSteps);
+            result.put("远途费", longExtra);
+            return formatMap(result);
+        }
+
+        /**
+         * 远途加价核心逻辑:判断当前行驶到的里程处于哪个加价区间
+         */
+        private BigDecimal calculateLongDistExtra(BigDecimal billM, List<LongRangeStep> steps) {
+            if (billM.compareTo(BigDecimal.ZERO) <= 0) return BigDecimal.ZERO;
+
+            // 当前点所在的公里数
+            BigDecimal extraStart = BigDecimal.ZERO;
+            BigDecimal extraRate = BigDecimal.ZERO;
+
+            // 匹配区间规则
+            for (LongRangeStep step : steps) {
+                if (step.isMatch(billM)) {
+                    extraRate = step.addPrice;
+                    extraStart = step.numStart;
+                    break;
+                }
+            }
+            return billM.subtract(extraStart).multiply(extraRate);
+        }
+
+        private Map<String, BigDecimal> formatMap(Map<String, BigDecimal> map) {
+            return map.entrySet().stream().collect(Collectors.toMap(
+                    Map.Entry::getKey, e -> e.getValue().setScale(2, RoundingMode.HALF_UP), (a, b) -> b, LinkedHashMap::new));
+        }
+    }
+
+    // --- 远途区间模型 ---
+    public static class LongRangeStep {
+        BigDecimal numStart; // 超过公里数
+        BigDecimal numEnd;   // 不足公里数
+        BigDecimal addPrice; // 加收单价
+
+        public LongRangeStep(double start, double end, double price) {
+            this.numStart = BigDecimal.valueOf(start);
+            this.numEnd = BigDecimal.valueOf(end);
+            this.addPrice = BigDecimal.valueOf(price);
+        }
+
+        public boolean isMatch(BigDecimal currentKm) {
+            // [numStart, numEnd)
+            return currentKm.compareTo(numStart) >= 0 && currentKm.compareTo(numEnd) < 0;
+        }
+    }
+
+    public static class PricingConfig {
+        PricingRule holiday;
+        PricingRule peak;
+        PricingRule night;
+        List<PricingRule> normals = new ArrayList<>();
+        List<LongRangeStep> longSteps = new ArrayList<>(); // 修改为区间列表
+
+        public PricingRule findMatchedRule(Date date) {
+            LocalTime t = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
+            if (holiday.isMatch(t)) return holiday;
+            if (peak.isMatch(t)) return peak;
+            if (night.isMatch(t)) return night;
+            for (PricingRule r : normals) if (r.isMatch(t)) return r;
+            return normals.get(0);
+        }
+    }
+
+    public static class PricingRule {
+        String ruleName;
+        LocalTime start, end;
+        BigDecimal baseFare, baseKm, baseMin, perKmRate, perMinRate;
+
+        public PricingRule(String n, String s, String e, double bf, double bk, double bm, double pkr, double pmr) {
+            this.ruleName = n;
+            this.start = LocalTime.parse(s);
+            this.end = LocalTime.parse(e);
+            this.baseFare = BigDecimal.valueOf(bf);
+            this.baseKm = BigDecimal.valueOf(bk);
+            this.baseMin = BigDecimal.valueOf(bm);
+            this.perKmRate = BigDecimal.valueOf(pkr);
+            this.perMinRate = BigDecimal.valueOf(pmr);
+        }
+
+        public boolean isMatch(LocalTime now) {
+            return start.isBefore(end) ? (!now.isBefore(start) && !now.isAfter(end)) : (!now.isBefore(start) || !now.isAfter(end));
+        }
+    }
+
+    public static class GpsPoint {
+        private double distance;
+        private long locatetime;
+        public GpsPoint(double d, long t) { this.distance = d; this.locatetime = t; }
+        public double getDistance() { return distance; }
+        public long getLocatetime() { return locatetime; }
+    }
+
+    private static PricingConfig setupMockConfig() {
+        PricingConfig config = new PricingConfig();
+        config.peak = new PricingRule("高峰期", "14:40", "14:46", 15.0, 3.0, 5.0, 3.0, 0.8);
+        config.night = new PricingRule("夜间行驶", "14:47", "14:48", 18.0, 3.0, 5.0, 3.5, 1.0);
+        config.holiday = new PricingRule("节假日", "18:47", "18:48", 18.0, 3.0, 5.0, 4, 2.0);
+        config.normals.add(new PricingRule("普通时段", "14:46", "14:47", 10.0, 3.0, 5.0, 2.4, 0.5));
+        config.normals.add(new PricingRule("普通时段", "14:48", "17:59", 10.0, 3.0, 5.0, 2.4, 0.5));
+
+        // --- 远途加价区间配置 (关键修改) ---
+        // 1. 超过 num1(12) 不足 num2(20), 加收 num3(1.2)
+        config.longSteps.add(new LongRangeStep(5.0, 6.0, 1.2));
+        // 2. 超过 num4(20) 不足 num5(35), 加收 num6(2.0)
+        config.longSteps.add(new LongRangeStep(6.0, 7.0, 2.0));
+        // 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值
+        config.longSteps.add(new LongRangeStep(7.0, 8.0, 3.5));
+
+        return config;
+    }
+
+    private static List<GpsPoint> createMockGpsPoints(Date startTime) {
+        List<GpsPoint> list = new ArrayList<>();
+        long startTs = startTime.getTime();
+        list.add(new GpsPoint(0, startTs));
+        // 模拟行驶了 20km
+        list.add(new GpsPoint(120, startTs + 2 * 10 * 1000));
+        list.add(new GpsPoint(204, startTs + 2 * 20 * 1000));
+        list.add(new GpsPoint(231, startTs + 2 * 30 * 1000));
+        list.add(new GpsPoint(252, startTs + 2 * 40 * 1000));
+        list.add(new GpsPoint(188, startTs + 2 * 50 * 1000));
+        list.add(new GpsPoint(150, startTs + 2 * 60 * 1000));
+        list.add(new GpsPoint(126, startTs + 2 * 70 * 1000));
+        list.add(new GpsPoint(138, startTs + 2 * 80 * 1000));
+        list.add(new GpsPoint(119, startTs + 2 * 90 * 1000));
+        list.add(new GpsPoint(147, startTs + 2 * 100 * 1000));
+        list.add(new GpsPoint(155, startTs + 2 * 110 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 120 * 1000));
+        list.add(new GpsPoint(172, startTs + 2 * 130 * 1000));
+        list.add(new GpsPoint(202, startTs + 2 * 140 * 1000));
+        list.add(new GpsPoint(162, startTs + 2 * 150 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 160 * 1000));
+        list.add(new GpsPoint(132, startTs + 2 * 170 * 1000));
+        list.add(new GpsPoint(142, startTs + 2 * 180 * 1000));
+        list.add(new GpsPoint(262, startTs + 2 * 190 * 1000));
+        list.add(new GpsPoint(242, startTs + 2 * 200 * 1000));
+        list.add(new GpsPoint(282, startTs + 2 * 210 * 1000));
+        list.add(new GpsPoint(222, startTs + 2 * 220 * 1000));
+        list.add(new GpsPoint(212, startTs + 2 * 230 * 1000));
+        list.add(new GpsPoint(142, startTs + 2 * 240 * 1000));
+        list.add(new GpsPoint(262, startTs + 2 * 250 * 1000));
+        list.add(new GpsPoint(169, startTs + 2 * 260 * 1000));
+        list.add(new GpsPoint(170, startTs + 2 * 270 * 1000));
+        list.add(new GpsPoint(180, startTs + 2 * 280 * 1000));
+        list.add(new GpsPoint(165, startTs + 2 * 290 * 1000));
+        list.add(new GpsPoint(180, startTs + 2 * 300 * 1000));
+        list.add(new GpsPoint(198, startTs + 2 * 310 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 320 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 330 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 340 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 350 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 360 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 370 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 380 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 390 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 400 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 410 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 420 * 1000));
+        list.add(new GpsPoint(182, startTs + 2 * 430 * 1000));
+        return list;
+    }
+}
\ No newline at end of file

--
Gitblit v1.7.1