无关风月
2026-02-13 ac52ff126bf9ca4eb7018ae1252b39fa6f91a2bb
bug修改,计算费用工具类
17个文件已添加
819 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/VehicleBaseRespDto.java
New file
@@ -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;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/operation/ReassignOrderDTO.java
New file
@@ -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;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/OrderMissionClientFallbackFactory.java
New file
@@ -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;
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automaticPilot/XpuVimClientFallbackFactory.java
New file
@@ -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;
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/ReassignOrderClientFallbackFactory.java
New file
@@ -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());
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/OrderMissionClient.java
New file
@@ -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);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/VmpOpenServiceClient.java
New file
@@ -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);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/automaticPilot/XpuVinClient.java
New file
@@ -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);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/ReassignOrderClient.java
New file
@@ -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);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderDetail.java
New file
@@ -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;
}
xp-robotaxi-automaticPilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/XpuVinController.java
New file
@@ -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);
    }
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/ReassignOrderController.java
New file
@@ -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();
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderDetailMapper.java
New file
@@ -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> {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderDetailService.java
New file
@@ -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> {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderDetailServiceImpl.java
New file
@@ -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 {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/GpsPoint.java
New file
@@ -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
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/test/TaxiPricingDemoService.java
New file
@@ -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;
    }
}