liujie
2026-06-09 c96fbe6b0aeb413a17c429133ee6d81d138144b9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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<String, BigDecimal> calculateOrderFee(
            List<GpsPoint> points,
            PricingRule holidayRule,
            PricingRule peakRule,
            PricingRule nightRule,
            List<PricingRule> normalRules,
            List<LongDistStep> longSteps,
            boolean isHoliday) {
 
        if (points == null || points.size() < 2) return new HashMap<>();
 
        // 结果存放:Key为规则名称,Value为该时段产生的超出金额
        Map<String, BigDecimal> 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<PricingRule> 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<LongDistStep> 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<String, BigDecimal> formatResult(Map<String, BigDecimal> map) {
        Map<String, BigDecimal> 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);
        }
    }
}