xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/automatic/OrderMissionClientFallbackFactory.java
@@ -1,7 +1,7 @@ package com.xiaopeng.api.factory.automatic; import com.xiaopeng.api.dto.automaticPilot.PushOrderDto; import com.xiaopeng.api.feignClient.automatic.OrderMissionClient; import com.xiaopeng.api.feignClient.maintenance.XpChargingRecordClient; import com.xiaopeng.common.core.domain.R; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; @@ -22,7 +22,8 @@ return new OrderMissionClient() { @Override public R<Boolean> pushOrder(PushOrderDto dto) { return null; log.error("推送订单失败,原因: {}", cause.getMessage()); return R.fail(); } }; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/order/XpOrderClientFallbackFactory.java
@@ -82,6 +82,12 @@ log.error("订单出队失败,原因: {}", cause.getMessage()); return R.fail(); } @Override public R<?> updateOrder(XpOrder xpOrder) { log.error("更新订单失败,原因: {}", cause.getMessage()); return R.fail(); } }; } } xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/order/XpOrderClient.java
@@ -91,4 +91,12 @@ @PostMapping("/inner/order/popOrder") R<?> popOrder(@RequestBody PopOrderDto dto); /** * 修改订单信息 * * @param xpOrder 订单信息 */ @PostMapping("/inner/order/updateOrder") R<?> updateOrder(@RequestBody XpOrder xpOrder); } xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrder.java
@@ -207,4 +207,8 @@ @ApiModelProperty(value = "行程结束时间") private LocalDateTime tripEnd; @ApiModelProperty(value = "队列名称") @TableField("queue_name") private String queueName; } xp-robotaxi-operation-boot/pom.xml
@@ -15,12 +15,18 @@ </description> <dependencies> <!--mongodb--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>com.github.stuxuhai</groupId> <artifactId>jpinyin</artifactId> <version>1.1.8</version> xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java
@@ -137,14 +137,13 @@ /** * 批量冻结 */ @PutMapping("/freezeByIds") @GetMapping("/freezeByIds") @ResponseBody @ApiOperation(value = "批量冻结") public R freezeByIds(List<Long> ids) { public R freezeByIds(String ids) { SysUser sysUser = tokenService.getLoginUser().getSysUser(); return xpWhitelistService.freezeByIds(ids, sysUser); List<Long> whiteIds = Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList()); return xpWhitelistService.freezeByIds(whiteIds, sysUser); } @ApiOperation(value = "白名单导入模板下载") xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpElectronicFenceServiceImpl.java
@@ -12,6 +12,7 @@ import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.operation.XpPoint; import com.xiaopeng.api.query.operation.ElectronicPageListQuery; import com.xiaopeng.api.model.operation.XpRegion; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.DateUtils; @@ -25,9 +26,15 @@ import com.xiaopeng.operation.mapper.XpElectronicFenceAreaMapper; import com.xiaopeng.operation.mapper.XpElectronicFenceMapper; import com.xiaopeng.operation.mapper.XpPointMapper; import com.xiaopeng.api.query.operation.ElectronicPageListQuery; import com.xiaopeng.operation.mapper.XpRegionMapper; import com.xiaopeng.operation.service.IXpElectronicFenceService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.operation.service.IXpRegionService; import com.xiaopeng.operation.utils.PolygonOverlapService; import lombok.extern.slf4j.Slf4j; import net.sourceforge.pinyin4j.PinyinHelper; import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; @@ -62,6 +69,8 @@ private MongoTemplate mongoTemplate; @Autowired private XpPointMapper xpPointMapper; @Autowired private XpRegionMapper xpRegionMapper; @Override public PageInfo<XpElectronicFence> pageList(ElectronicPageListQuery query) { @@ -90,7 +99,18 @@ pageInfo.setRecords(list); return pageInfo; } public static String getPinyinInitials(String chinese) { StringBuilder pinyinInitials = new StringBuilder(); for (char c : chinese.toCharArray()) { // 获取拼音数组(多音字会返回多个拼音) String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c); if (pinyinArray != null && pinyinArray.length > 0) { // 取第一个拼音的首字母 pinyinInitials.append(pinyinArray[0].charAt(0)); } } return pinyinInitials.toString(); } @Override public R addElectronic(ElectronicFenceAddDto dto) { try { @@ -142,7 +162,17 @@ } } xpElectronicFence.setCityCode(dto.getCityCode()); // 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收 XpRegion xpRegion = xpRegionMapper.selectOne(new LambdaQueryWrapper<XpRegion>() .eq(XpRegion::getCode, dto.getCityCode())); String name = xpRegion.getName(); this.save(xpElectronicFence); // 根据城市中文名,截取前两位的首字母并且大写 // 截取前两个字符 String prefix = name.substring(0, 2); // 获取拼音首字母并大写 String result = getPinyinInitials(prefix).toUpperCase(); xpElectronicFence.setElectronicNumber(result + "-OP-" + String.format("%04d", xpElectronicFence.getId())); if (dto.getStatus() == 0) { xpElectronicFence.setStatus(0); } else if (dto.getStatus() == 1) { @@ -216,7 +246,7 @@ xpGeoFencingPolygon.setFenceId(String.valueOf(xpElectronicFence.getId())); xpGeoFencingPolygon.setName(xpElectronicFence.getElectronicName()); xpGeoFencingPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId())); xpGeoFencingPolygon.setCityCode("510100"); xpGeoFencingPolygon.setCityCode(xpElectronicFence.getCityCode()); xpGeoFencingPolygon.setOperationStrategy(xpElectronicFence.getOperationStrategy()); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java
@@ -514,12 +514,17 @@ // 尾部 redisService.addToTail(s, orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule()); } xpOrder.setQueueName(s); xpOrderClient.updateOrder(xpOrder); return orderId; } } String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat(); redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule())); xpOrder.setQueueName(key); xpOrderClient.updateOrder(xpOrder); return orderId; // xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -128,6 +128,16 @@ return R.ok(); } /** * 修改订单信息 * * @param xpOrder 订单信息 */ @PostMapping("/inner/order/updateOrder") public R<?> updateOrder(@RequestBody XpOrder xpOrder){ return xpOrderService.updateById(xpOrder)?R.ok():R.fail(); } /** * 新增订单 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -71,6 +71,7 @@ import com.xiaopeng.order.service.IXpOrderEvaluateService; import com.xiaopeng.order.service.IXpOrderReassignService; import com.xiaopeng.order.service.IXpOrderService; import com.xiaopeng.order.utils.TaxiPricingDemoService; import com.xiaopeng.order.utils.holiday.HolidayUtil; import com.xiaopeng.order.vo.OrderDetailVo; import io.swagger.annotations.ApiModelProperty; @@ -771,11 +772,11 @@ // 消息推送 Map<String, Object> params = new HashMap<>(); Map<String, Object> content = new HashMap<>(); content.put("orderId",order.getId()); content.put("orderId", order.getId()); content.put("status", OrderStatusEnum.CANCELED.getCode()); params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc()); params.put("content", com.alibaba.fastjson2.JSONObject.toJSONString(content)); params.put("timestamp", new Date().getTime()+""); params.put("timestamp", new Date().getTime() + ""); String message = JSONObject.toJSONString(params); SendMessageDTO sendMessageDTO = new SendMessageDTO(); sendMessageDTO.setMessage(message); @@ -920,7 +921,41 @@ userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo); } // 计算起步价 // 费用 // 查询订单轨迹 List<OrderTrajectory> trajectories = mongoTemplate.find( new Query(Criteria.where("orderId").is(orderId)) .with(Sort.by(Sort.Direction.ASC, "locatetime")), OrderTrajectory.class ); List<TaxiPricingDemoService.GpsPoint> gpsPoints = new ArrayList<>(); for (OrderTrajectory trajectory : trajectories) { TaxiPricingDemoService.GpsPoint point = new TaxiPricingDemoService.GpsPoint(trajectory.getDistance(),trajectory.getLocatetime() ); gpsPoints.add(point); } TaxiPricingDemoService.PricingConfig config = new TaxiPricingDemoService.PricingConfig(); config.peak = new TaxiPricingDemoService.PricingRule("高峰期", "14:40", "14:46", 15.0, 3.0, 5.0, 3.0, 0.8); config.night = new TaxiPricingDemoService.PricingRule("夜间行驶", "14:47", "14:48", 18.0, 3.0, 5.0, 3.5, 1.0); config.holiday = new TaxiPricingDemoService.PricingRule("节假日", "18:47", "18:48", 18.0, 3.0, 5.0, 4, 2.0); config.normals.add(new TaxiPricingDemoService.PricingRule("普通时段", "14:46", "14:47", 10.0, 3.0, 5.0, 2.4, 0.5)); config.normals.add(new TaxiPricingDemoService.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 TaxiPricingDemoService.LongRangeStep(5.0, 6.0, 1.2)); // 2. 超过 num4(20) 不足 num5(35), 加收 num6(2.0) config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(6.0, 7.0, 2.0)); // 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值 config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(7.0, 8.0, 3.5)); TaxiPricingDemoService.TaxiPricingService service = new TaxiPricingDemoService.TaxiPricingService(); Map<String, BigDecimal> bill = service.calculateOrderPrice(DateUtils.toDate(order.getCreateTime()), order.getMileageKilometers(), gpsPoints, 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 + " 元"); // // 计算实际价格 // // 根据订单所属开通城市id 查询定价 @@ -1276,11 +1311,10 @@ //// ); } private MileageDto mileageType(MileageDto param,JSONArray startingFareJsonArray, Date mileageDate, LocalDateTime begin, String num14, String num16, String num17, String num21, String num22, String num24, String num25, String num30, String num31) { private MileageDto mileageType(MileageDto param, JSONArray startingFareJsonArray, Date mileageDate, LocalDateTime begin, String num14, String num16, String num17, String num21, String num22, String num24, String num25, String num30, String num31) { // 计算出是在哪个普通时间段内 for (Object o : startingFareJsonArray) { JSONObject startingFare = (JSONObject) o; @@ -1295,7 +1329,7 @@ calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1])); // 找到符合条件的直接跳出循环 if (mileageDate.getTime() > calendarStart.getTimeInMillis() && mileageDate.getTime() < calendarEnd.getTimeInMillis()) { param.setMileage(startingFare.getString("num6")) ; param.setMileage(startingFare.getString("num6")); param.setTime(startingFare.getString("num7")); param.setMileageTypeTemp(1); break; @@ -1317,7 +1351,7 @@ if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis() ) { param.setMileage(num16) ; param.setMileage(num16); param.setTime(num17); param.setMileageTypeTemp(2); } @@ -1344,7 +1378,7 @@ if ((date.getTime() > calendar1Start.getTimeInMillis() && date.getTime() < calendar1End.getTimeInMillis()) || (date.getTime() > calendar2Start.getTimeInMillis() && date.getTime() < calendar2End.getTimeInMillis())) { param.setMileage(num24) ; param.setMileage(num24); param.setTime(num25); param.setMileageTypeTemp(3); } @@ -1352,7 +1386,7 @@ boolean holiday = holidayUtil.isHoliday(localDate); if (holiday) { // 判断是否为节假日 起步价修改 param.setMileage(num30) ; param.setMileage(num30); param.setTime(num31); param.setMileageTypeTemp(4); } @@ -1604,7 +1638,7 @@ BeanUtils.copyProperties(order, orderDetailVo); // 查询车辆信息 XpVehicle vehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); if (vehicle!=null){ if (vehicle != null) { orderDetailVo.setVehicleCode(vehicle.getVehicleCode()); orderDetailVo.setPlateNumber(vehicle.getPlateNumber()); orderDetailVo.setVehicleColor(vehicle.getVehicleColor()); @@ -1626,15 +1660,15 @@ OrderTrajectory.class ); orderDetailVo.setTrajectories(trajectories); if (order.getTripBegin()!=null&&order.getTripEnd()!=null){ if (order.getTripBegin() != null && order.getTripEnd() != null) { // 计算两个时间相差多少分钟 long minutesDifference = Duration.between(order.getTripBegin(), order.getTripEnd()).toMinutes(); orderDetailVo.setOrderTime(minutesDifference); }else{ } else { orderDetailVo.setOrderTime(0l); } if (order.getStartMileage()!=null){ orderDetailVo.setTotalMileage(order.getStartMileage()+(order.getMileageKilometers()!=null?order.getMileageKilometers():0D)); if (order.getStartMileage() != null) { orderDetailVo.setTotalMileage(order.getStartMileage() + (order.getMileageKilometers() != null ? order.getMileageKilometers() : 0D)); } return orderDetailVo; @@ -1643,6 +1677,7 @@ /** * 成功操作下发命令后调用 用于弹出redis队列的获取成功的数据 * * @param dto */ @Override @@ -1652,12 +1687,16 @@ XpOrder order = this.getById(dto.getOrderId()); order.setAcceptanceTime(LocalDateTime.now()); order.setVehicleId(dto.getXpVehicleId()); R<XpVehicle> byVehicleVin = xpVehicleClient.findByVehicleVin(dto.getXpVehicleVin()); if (byVehicleVin.getCode()==200){ order.setVehicleId(byVehicleVin.getData().getId()); } order.setStatus(2); // Redis弹出这个队列元素 // value 订单Id_经纬度_状态_用户id_订单编号_距离_时间 String popFirstElement = redisService.popFirstElement(dto.getQueueName()); String popFirstElement = redisService.popFirstElement(order.getQueueName()); XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity(); String[] split = popFirstElement.split("_"); xpGeoQueryEntity.setOrderId(split[0]); xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaxiPricingDemoService.java
@@ -1,5 +1,4 @@ package com.xiaopeng.order.utils; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalTime; @@ -16,11 +15,11 @@ PricingConfig config = setupMockConfig(); Calendar cal = Calendar.getInstance(); cal.set(2023, Calendar.OCTOBER, 10, 16, 50, 0); cal.set(2026, Calendar.MARCH, 13, 14, 40, 15); Date orderTime = cal.getTime(); // 模拟总里程 25.0 km,以触发多级远途加价 double totalMillige = 25.0; double totalMillige = 7.874; // 模拟 GPS 轨迹点 List<GpsPoint> points = createMockGpsPoints(orderTime); @@ -42,7 +41,7 @@ BigDecimal totalMileage = BigDecimal.valueOf(totalMileageKm); PricingRule baseRule = config.findMatchedRule(orderTime); result.put("起步价(" + baseRule.ruleName + ")", baseRule.baseFare); result.put("起步价(" + baseRule.ruleName + ")"+"-"+baseRule.start+"-"+baseRule.end, baseRule.baseFare); if (totalMileage.compareTo(baseRule.baseKm) <= 0) { return formatMap(result); @@ -51,6 +50,15 @@ 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); @@ -59,65 +67,94 @@ 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 (remainBaseM.compareTo(BigDecimal.ZERO) > 0) { if (segM.compareTo(remainBaseM) > 0) { billM = segM.subtract(remainBaseM); remainBaseM = BigDecimal.ZERO; } else { remainBaseM = remainBaseM.subtract(segM); } } else { billM = segM; if (currentTotalM.compareTo(remainBaseM) > 0) { // billM = currentTotalM.subtract(remainBaseM); isOverBaseM = true; } BigDecimal billMs = BigDecimal.ZERO; if (remainBaseMs.compareTo(BigDecimal.ZERO) > 0) { if (segMs.compareTo(remainBaseMs) > 0) { billMs = segMs.subtract(remainBaseMs); remainBaseMs = BigDecimal.ZERO; } else { remainBaseMs = remainBaseMs.subtract(segMs); } } else { billMs = segMs; if (currentTotalMs.compareTo(remainBaseMs) > 0) { // billMs = currentTotalMs.subtract(remainBaseMs); isOverBaseMs = true; } // 1. 基础费率计算 BigDecimal distFee = billM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP).multiply(activeRule.perKmRate); BigDecimal timeFee = billMs.divide(new BigDecimal(60000), 10, RoundingMode.HALF_UP).multiply(activeRule.perMinRate); // 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 longExtra = calculateLongDistExtra(currentTotalM, billM, config.longSteps); BigDecimal totalSeg = distFee.add(timeFee).add(longExtra); result.put(activeRule.ruleName, result.getOrDefault(activeRule.ruleName, BigDecimal.ZERO).add(totalSeg)); // 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 currentTotalM, BigDecimal billM, List<LongRangeStep> steps) { private BigDecimal calculateLongDistExtra(BigDecimal billM, List<LongRangeStep> steps) { if (billM.compareTo(BigDecimal.ZERO) <= 0) return BigDecimal.ZERO; // 当前点所在的公里数 BigDecimal currentKm = currentTotalM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP); BigDecimal extraStart = BigDecimal.ZERO; BigDecimal extraRate = BigDecimal.ZERO; // 匹配区间规则 for (LongRangeStep step : steps) { if (step.isMatch(currentKm)) { if (step.isMatch(billM)) { extraRate = step.addPrice; extraStart = step.numStart; break; } } return billM.divide(new BigDecimal(1000), 10, RoundingMode.HALF_UP).multiply(extraRate); return billM.subtract(extraStart).multiply(extraRate); } private Map<String, BigDecimal> formatMap(Map<String, BigDecimal> map) { @@ -145,16 +182,15 @@ } public static class PricingConfig { PricingRule holiday; PricingRule peak; PricingRule night; List<PricingRule> normals = new ArrayList<>(); List<LongRangeStep> longSteps = new ArrayList<>(); // 修改为区间列表 boolean isHoliday = false; public PricingRule holiday; public PricingRule peak; public PricingRule night; public List<PricingRule> normals = new ArrayList<>(); public List<LongRangeStep> longSteps = new ArrayList<>(); // 修改为区间列表 public PricingRule findMatchedRule(Date date) { LocalTime t = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); if (isHoliday) return holiday; 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; @@ -163,9 +199,9 @@ } public static class PricingRule { String ruleName; LocalTime start, end; BigDecimal baseFare, baseKm, baseMin, perKmRate, perMinRate; public String ruleName; public LocalTime start, end; public 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; @@ -193,17 +229,19 @@ private static PricingConfig setupMockConfig() { PricingConfig config = new PricingConfig(); config.peak = new PricingRule("高峰期", "17:00", "19:00", 15.0, 3.0, 5.0, 3.0, 0.8); config.night = new PricingRule("夜间行驶", "22:00", "05:00", 18.0, 3.0, 5.0, 3.5, 1.0); config.normals.add(new PricingRule("普通时段", "05:00", "17:00", 12.0, 3.0, 5.0, 2.4, 0.5)); 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(12.0, 20.0, 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(20.0, 35.0, 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(35.0, 999999.0, 3.5)); config.longSteps.add(new LongRangeStep(7.0, 8.0, 3.5)); return config; } @@ -213,9 +251,49 @@ long startTs = startTime.getTime(); list.add(new GpsPoint(0, startTs)); // 模拟行驶了 20km list.add(new GpsPoint(12000, startTs + 15 * 60 * 1000)); list.add(new GpsPoint(8000, startTs + 25 * 60 * 1000)); list.add(new GpsPoint(5000, startTs + 35 * 60 * 1000)); 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; } }