package com.xiaopeng.order.utils; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalTime; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 出租车计费引擎服务 */ public class TaxiPricingService { public static void main(String[] args) { } // 1. 定义规则模型 public static class PricingRule { String ruleName; // 时段名称(如:高峰期、深夜档、普通时段A) String ruleType; // 类型:NORMAL, PEAK, NIGHT, HOLIDAY LocalTime start; LocalTime end; BigDecimal perKmRate; // 每公里价格 BigDecimal perMinRate; // 每分钟价格 // 起步价相关(仅用于订单开始时的首个规则判定) BigDecimal baseFare; BigDecimal baseKm; BigDecimal baseMin; public PricingRule(String name, String type, String start, String end, double kmRate, double minRate, double bf, double bk, double bm) { this.ruleName = name; this.ruleType = type; this.start = LocalTime.parse(start); this.end = LocalTime.parse(end); this.perKmRate = BigDecimal.valueOf(kmRate); this.perMinRate = BigDecimal.valueOf(minRate); this.baseFare = BigDecimal.valueOf(bf); this.baseKm = BigDecimal.valueOf(bk); this.baseMin = BigDecimal.valueOf(bm); } // 跨天匹配核心算法 public boolean contains(LocalTime now) { if (start.isBefore(end)) { return !now.isBefore(start) && !now.isAfter(end); } else { // 处理跨天,如 22:00 - 05:00 return !now.isBefore(start) || !now.isAfter(end); } } } /** * 计算服务 */ public Map calculateOrderFee( List points, PricingRule holidayRule, PricingRule peakRule, PricingRule nightRule, List normalRules, List longSteps, boolean isHoliday) { if (points == null || points.size() < 2) return new HashMap<>(); // 结果存放:Key为规则名称,Value为该时段产生的超出金额 Map report = new HashMap<>(); // 1. 初始化起步参数 (以第一条定位的时间点决定起步规则) GpsPoint startPoint = points.get(0); PricingRule initialRule = findMatchedRule(startPoint, isHoliday, holidayRule, peakRule, nightRule, normalRules); report.put("起步价(" + initialRule.ruleName + ")", initialRule.baseFare); // 剩余可抵扣起步额度 BigDecimal remainBaseMeters = initialRule.baseKm.multiply(new BigDecimal("1000")); BigDecimal remainBaseMillis = initialRule.baseMin.multiply(new BigDecimal("60000")); BigDecimal totalDistanceMeters = BigDecimal.ZERO; // 2. 循环处理每一段定位轨迹 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()); totalDistanceMeters = totalDistanceMeters.add(segM); // 匹配当前定位点所属的规则 PricingRule activeRule = findMatchedRule(curr, isHoliday, holidayRule, peakRule, nightRule, normalRules); String key = activeRule.ruleName; // A. 扣除起步里程后的计费里程 BigDecimal billableM = BigDecimal.ZERO; if (remainBaseMeters.compareTo(BigDecimal.ZERO) > 0) { if (segM.compareTo(remainBaseMeters) > 0) { billableM = segM.subtract(remainBaseMeters); remainBaseMeters = BigDecimal.ZERO; } else { remainBaseMeters = remainBaseMeters.subtract(segM); } } else { billableM = segM; } // B. 扣除起步时长后的计费时长 BigDecimal billableMs = BigDecimal.ZERO; if (remainBaseMillis.compareTo(BigDecimal.ZERO) > 0) { if (segMs.compareTo(remainBaseMillis) > 0) { billableMs = segMs.subtract(remainBaseMillis); remainBaseMillis = BigDecimal.ZERO; } else { remainBaseMillis = remainBaseMillis.subtract(segMs); } } else { billableMs = segMs; } // C. 计算当前段金额 = (里程费) + (时长费) + (远途费) BigDecimal distFee = billableM.divide(new BigDecimal("1000"), 10, RoundingMode.HALF_UP).multiply(activeRule.perKmRate); BigDecimal timeFee = billableMs.divide(new BigDecimal("60000"), 10, RoundingMode.HALF_UP).multiply(activeRule.perMinRate); BigDecimal extraLongFee = calculateLongDistExtra(totalDistanceMeters, billableM, longSteps); BigDecimal segTotal = distFee.add(timeFee).add(extraLongFee); // D. 归纳到对应规则 report.put(key, report.getOrDefault(key, BigDecimal.ZERO).add(segTotal)); } // 3. 格式化输出:保留两位小数 return formatResult(report); } /** * 优先级判定逻辑:节假日 > 高峰期 > 夜间 > 普通1 > 普通2... */ private PricingRule findMatchedRule(GpsPoint p, boolean isHoliday, PricingRule h, PricingRule pk, PricingRule n, List normals) { if (isHoliday) return h; // 将毫秒转为当地时间进行匹配 LocalTime time = new java.sql.Time(p.getLocatetime()).toLocalTime(); if (pk.contains(time)) return pk; if (n.contains(time)) return n; for (PricingRule rule : normals) { if (rule.contains(time)) return rule; } return normals.get(0); // 兜底逻辑 } private BigDecimal calculateLongDistExtra(BigDecimal totalM, BigDecimal segM, List steps) { if (segM.compareTo(BigDecimal.ZERO) <= 0) return BigDecimal.ZERO; BigDecimal totalKm = totalM.divide(new BigDecimal("1000"), 10, RoundingMode.HALF_UP); BigDecimal rate = BigDecimal.ZERO; for (LongDistStep step : steps) { if (totalKm.compareTo(step.kmThreshold) > 0) { rate = step.addPrice; break; } } return segM.divide(new BigDecimal("1000"), 10, RoundingMode.HALF_UP).multiply(rate); } private Map formatResult(Map map) { Map formatted = new HashMap<>(); map.forEach((k, v) -> formatted.put(k, v.setScale(2, RoundingMode.HALF_UP))); return formatted; } // 远途阶梯配置类 public static class LongDistStep { BigDecimal kmThreshold; BigDecimal addPrice; public LongDistStep(double km, double price) { this.kmThreshold = BigDecimal.valueOf(km); this.addPrice = BigDecimal.valueOf(price); } } }