3. 策略模型设计:策略的定义、条件、动作与优先级
好,咱们进入策略引擎的核心地带——策略模型设计。
说白了,策略引擎就是个“如果…那么…”的机器。你给它一堆条件,它帮你匹配,然后执行对应的动作。但实际落地时,这里面的门道可不少。我最早做规则引擎时,以为就是写几个if-else,结果被生产环境教做人。
今天咱们就把策略的四个核心要素拆开揉碎:策略定义、条件、动作、优先级与冲突解决。
3.1 策略的定义:一个完整的策略长什么样?
先看一个最简单的策略定义。我个人习惯用JSON或YAML来描述策略,因为可读性好,也方便动态加载。
{
"strategyId": "S001",
"name": "新用户首单折扣",
"description": "注册7天内且首次下单的用户,享受8折优惠",
"priority": 10,
"conditions": [
{ "field": "user.registrationDays", "operator": "<=", "value": 7 },
{ "field": "order.isFirstOrder", "operator": "==", "value": true }
],
"actions": [
{ "type": "applyDiscount", "params": { "rate": 0.8 } }
],
"status": "active"
}
你看,一个策略包含几个关键字段:
- strategyId:唯一标识,别用自增ID,用UUID或业务编码。我踩过坑,自增ID在分布式环境下容易冲突。
- name / description:给人看的,方便排查。
- priority:优先级,后面细说。
- conditions:条件列表,多个条件之间默认是“与”关系。
- actions:动作列表,按顺序执行。
- status:控制策略是否启用。
version 字段。线上策略迭代快,没版本号你根本不知道哪个策略在生效。
3.2 策略的条件(Condition):如何灵活表达业务规则?
条件,就是策略的“触发器”。它决定了策略在什么场景下被激活。
我见过很多团队把条件写死在代码里,结果每次改规则都要发版。这其实违背了策略引擎的初衷——动态决策。
3.2.1 条件的基本结构
一个条件通常由三部分组成:
- 字段(field):比如用户年龄、订单金额、设备类型。
- 操作符(operator):等于、大于、包含、在范围内等。
- 值(value):比较的目标值。
举个例子:
// 条件:订单金额大于100元
{ "field": "order.amount", "operator": ">", "value": 100 }
// 条件:用户所在城市在列表中
{ "field": "user.city", "operator": "in", "value": ["北京", "上海", "深圳"] }
// 条件:用户标签包含"VIP"
{ "field": "user.tags", "operator": "contains", "value": "VIP" }
3.2.2 复合条件:与、或、非
实际业务中,条件往往是组合的。比如“新用户 且 首单 且 金额大于50”。
我建议用树形结构来表达复合条件:
{
"logic": "AND",
"conditions": [
{ "field": "user.registrationDays", "operator": "<=", "value": 7 },
{
"logic": "OR",
"conditions": [
{ "field": "order.amount", "operator": ">", "value": 100 },
{ "field": "order.itemCount", "operator": ">=", "value": 3 }
]
}
]
}
嗯,这里要注意:不要支持太深的嵌套。我曾经见过一个策略嵌套了7层,维护的人想死的心都有。建议限制在3层以内。
3.3 策略的动作(Action):匹配后做什么?
条件匹配了,就该执行动作了。动作是策略的“输出”。
动作的设计,我总结为三种类型:
| 动作类型 | 说明 | 示例 |
|---|---|---|
| 计算型 | 修改某个值或计算结果 | 打折、积分计算、价格调整 |
| 通知型 | 触发外部通知或消息 | 发送短信、推送、邮件 |
| 流程型 | 改变业务流转方向 | 转人工审核、拒绝订单、标记风控 |
动作的定义也很简单:
{
"actions": [
{ "type": "applyDiscount", "params": { "rate": 0.8, "scope": "order" } },
{ "type": "sendNotification", "params": { "channel": "sms", "template": "welcome" } }
]
}
我个人建议:动作要支持幂等性。为什么?因为策略引擎可能会重试。如果同一个动作被执行两次,结果不能变。比如“发送短信”这种动作,一定要有去重机制。
3.4 策略的优先级与冲突解决
这是策略引擎里最头疼的部分。多个策略同时匹配,到底听谁的?
3.4.1 优先级怎么定?
我见过两种主流做法:
- 数值优先级:给每个策略一个整数,数字越小优先级越高(或相反)。简单粗暴,但容易冲突。
- 层级优先级:比如“全局策略 > 分组策略 > 个人策略”。这种更符合业务直觉。
我推荐混合使用:先按层级过滤,再按数值排序。
// 优先级计算示例
function getEffectivePriority(strategy) {
let base = 0;
if (strategy.level === 'global') base = 1000;
else if (strategy.level === 'group') base = 500;
else if (strategy.level === 'personal') base = 100;
return base + strategy.priority; // priority 范围 0-99
}
3.4.2 冲突解决的三种策略
当两个策略都匹配,且动作冲突时(比如一个说打8折,一个说打9折),怎么办?
- 最高优先级胜出:只执行优先级最高的那个策略。简单,但可能丢失信息。
- 合并动作:把多个策略的动作合并执行。比如折扣取最低价,积分取最高值。这需要定义合并规则。
- 加权投票:每个策略投一票,按权重计算最终结果。适合风控场景。
我在项目中用得最多的是第一种。因为简单、可预测。但如果你做的是风控引擎,建议用加权投票。
3.5 策略模型的核心逻辑图
下面这张图,是我对策略模型设计的一个总结。它展示了从事件输入到策略匹配,再到动作执行的完整链路。
3.6 小结
策略模型设计,说白了就是三件事:
- 定义清楚:策略长什么样,字段怎么设计。
- 条件灵活:支持组合、嵌套,但别太深。
- 冲突可控:优先级和冲突解决策略,提前定好规则。
你想想看,如果这些基础没打好,后面做动态加载、性能优化、可视化编排,全都是空中楼阁。我见过太多团队一上来就搞高大上的可视化界面,结果底层策略模型一塌糊涂,最后全返工。
嗯,先把地基夯实了,后面的事就顺了。
公众号:蓝海资料掘金营,微信deep3321