4. 票务规则引擎:票价策略、优惠政策、时段折扣的动态配置
好,咱们来聊聊票务规则引擎。说实话,这是整个票务系统里最“烧脑”的部分之一。为什么?因为业务规则太容易变了。今天搞个暑期促销,明天来个周末折扣,后天又可能针对特定人群推出新优惠。你要是把这些规则写死在代码里,那运维同学得天天陪你加班改代码、发版本。
我个人习惯,是把规则和代码彻底分离。规则引擎,说白了就是一个“规则容器”。它不关心业务逻辑怎么算,只负责把规则存起来、匹配上、执行掉。这样,业务人员改个折扣率,就像改个Excel单元格一样简单。
4.1 规则引擎的架构分层
我一般把规则引擎拆成三层。你想想看,这样分层后,每一层只干一件事,出了问题也好定位。
- 规则定义层: 负责规则的存储和版本管理。我用的是关系型数据库 + 缓存。规则存成JSON或YAML格式,方便解析。
- 规则匹配层: 这是核心。根据乘客的行程信息(起点、终点、时间、票种等),找到所有适用的规则。
- 规则执行层: 把匹配到的规则按优先级排序,然后依次计算,最终得出票价。
嗯,这里要注意一点:规则的优先级非常重要。我曾经在项目里遇到过,因为两条优惠规则的优先级没排好,导致乘客同时享受了“学生折扣”和“早鸟折扣”,结果票价算出来是负数……这显然不合理。
4.2 票价策略:基础计费模型
票价策略是规则引擎的“地基”。没有这个,后面的优惠、折扣都无从谈起。常见的计费模型有这么几种:
| 计费模型 | 说明 | 适用场景 |
|---|---|---|
| 里程计费 | 按实际乘坐距离计算,每公里单价固定或分段 | 地铁、城际铁路 |
| 区间计费 | 按站点区间收费,同一区间内价格固定 | 公交、部分地铁 |
| 封顶计费 | 设定单日或单次最高票价,超出部分不再收费 | 日票、月票场景 |
| 混合计费 | 以上几种模型的组合,比如“里程+封顶” | 大多数城市轨道交通 |
举个例子,我参与过的一个项目,用的是“分段里程计费”。0-6公里收2元,6-12公里收3元,以此类推。这个规则在数据库里存成一张表:
{
"fare_rules": [
{"min_km": 0, "max_km": 6, "price": 2.0},
{"min_km": 6, "max_km": 12, "price": 3.0},
{"min_km": 12, "max_km": 20, "price": 4.0},
{"min_km": 20, "max_km": 999, "price": 5.0}
],
"version": "v2.1",
"effective_date": "2024-06-01"
}
你看,这个结构很简单。但实际生产环境里,规则会复杂得多。比如,有的城市会区分“工作日”和“非工作日”的票价,有的会区分“高峰时段”和“平峰时段”。
4.3 优惠政策:多维度叠加与互斥
优惠政策是规则引擎里最“花样百出”的部分。我见过最复杂的场景,是一个乘客同时满足:学生身份、使用储值卡、在非高峰时段乘车。这三条优惠能不能叠加?怎么叠加?
我的做法是,给每条规则打上“叠加类型”标签:
- 可叠加: 多条规则可以同时生效,比如“学生折扣”和“储值卡折扣”。
- 互斥: 多条规则只能选一条生效,比如“早鸟折扣”和“晚鸟折扣”。
- 覆盖: 高优先级规则覆盖低优先级规则,比如“免费乘车”覆盖所有其他折扣。
优惠政策的数据结构,我一般这样设计:
{
"promotion_id": "STU_2024",
"name": "学生暑期优惠",
"type": "percentage", // 折扣类型:百分比、固定金额、免费
"value": 0.8, // 打8折
"conditions": {
"user_type": "student",
"time_range": ["09:00", "17:00"],
"day_type": "weekday"
},
"stack_type": "stackable", // 可叠加
"priority": 10
}
4.4 时段折扣:时间维度的动态配置
时段折扣,说白了就是“不同时间,不同价格”。这个在轨道交通里很常见,比如早高峰贵一点,鼓励错峰出行;晚平峰便宜点,吸引客流。
我建议把时段规则设计成“时间片”的形式。每个时间片可以独立配置折扣率。举个例子:
{
"time_slots": [
{"start": "07:00", "end": "09:00", "discount": 1.0}, // 早高峰,不打折
{"start": "09:00", "end": "17:00", "discount": 0.9}, // 平峰,9折
{"start": "17:00", "end": "19:00", "discount": 1.0}, // 晚高峰,不打折
{"start": "19:00", "end": "23:00", "discount": 0.8} // 夜间,8折
],
"special_dates": [
{"date": "2024-10-01", "discount": 0.7} // 国庆节,7折
]
}
这里有个细节:时间片的边界处理。比如,乘客在08:59进站,09:01出站,该按哪个时段算?我一般按“进站时间”为准,因为进站时间更容易获取,也更容易被乘客理解。
4.5 规则引擎的性能与热加载
规则引擎的性能,直接影响到闸机的通行效率。你想想看,如果每个乘客过闸都要查数据库、算规则,那高峰期闸机前得排多长的队?
我的做法是:规则缓存 + 预计算。把常用的规则加载到内存里,比如Redis。乘客进站时,直接从缓存里取规则计算,毫秒级响应。
另外,规则引擎要支持“热加载”。也就是说,业务人员改了规则后,不需要重启服务,规则就能自动生效。我一般用“版本号+定时刷新”的方式:
// 伪代码示例
public class RuleEngine {
private Map<String, Rule> ruleCache;
private int currentVersion = 0;
public void refreshRules() {
int latestVersion = getLatestVersionFromDB();
if (latestVersion > currentVersion) {
ruleCache = loadRulesFromDB();
currentVersion = latestVersion;
System.out.println("规则已更新,版本号: " + currentVersion);
}
}
}
嗯,这里要提醒一下:热加载虽然方便,但一定要做好“灰度发布”。先在小范围试点,确认没问题了再全量生效。我曾经因为一次规则更新没做灰度,导致某个站点的票价全部算错,运维同学差点没把我拉黑。
4.6 小结
票务规则引擎,说白了就是“把复杂留给自己,把简单留给业务”。通过规则定义、匹配、执行三层架构,配合灵活的优惠政策配置和时段折扣,你可以让业务人员像搭积木一样,快速响应市场变化。
下一章,咱们聊聊“清分结算系统”。这可是票务系统的“财务大脑”,怎么把钱分得清清楚楚、明明白白,里面门道可不少。