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; } }