4. 规则模型设计:元数据、版本与优先级
规则模型是整个数据质量体系的骨架。骨架歪了,后面填再多肉也白搭。我这些年见过不少团队,一上来就写规则逻辑,结果规则多了以后,查都查不过来——哪个规则生效?哪个规则过期?规则之间打架怎么办?全乱套。
所以,咱们先把模型设计清楚。说白了,就是给每条规则办一张「身份证」,再管好它的「版本履历」和「执行顺序」。
4.1 规则元数据模型
每条规则,都得有自己的一套元数据。我习惯把它拆成五个核心字段:rule_id、rule_name、rule_type、condition、action。这五个字段,缺一个,规则就跑不顺畅。
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
| rule_id | string | 全局唯一标识,建议用 UUID 或雪花算法生成 | r_20250321_001 |
| rule_name | string | 人类可读的名称,用于日志和告警 | 用户手机号格式校验 |
| rule_type | enum | 规则分类:格式校验、空值检查、范围约束、唯一性等 | format |
| condition | json | 触发条件表达式,支持嵌套逻辑 | {"field":"phone","regex":"^1[3-9]\\d{9}$"} |
| action | json | 命中规则后的处理行为:拒绝、告警、替换、标记 | {"type":"reject","message":"手机号格式错误"} |
核心原则:condition 和 action 必须解耦。condition 只负责「判断」,action 只负责「执行」。我在项目中见过有人把判断逻辑写死在 action 里,结果想改个提示语都得改代码——千万别这么干。
举个例子。假设我们要校验用户注册接口里的手机号:
{
"rule_id": "r_20250321_001",
"rule_name": "手机号格式校验",
"rule_type": "format",
"condition": {
"field": "phone",
"operator": "regex_match",
"value": "^1[3-9]\\d{9}$"
},
"action": {
"type": "reject",
"http_status": 400,
"error_code": "INVALID_PHONE",
"message": "手机号格式不正确,请检查后重试"
}
}
你看,condition 只描述「什么情况下触发」,action 只描述「触发了怎么办」。这样设计,规则引擎就能通用化——不管什么接口,只要套上这套模型,都能跑。
4.2 规则版本管理
规则不是写一次就完事的。业务变了、字段改了、格式调整了,规则就得跟着变。但你不能直接覆盖旧规则——万一新规则有 bug,你连回滚的机会都没有。
我建议用语义化版本号来管理规则版本。格式很简单:major.minor.patch。
- major:规则逻辑发生重大变更,比如从「拒绝」改成「告警」
- minor:新增条件或调整阈值,比如放宽正则表达式
- patch:修复 bug 或优化提示语,不影响判断逻辑
每个版本都存一份快照,包括完整的元数据。我习惯用时间戳 + 版本号作为版本标识:
{
"rule_id": "r_20250321_001",
"version": "2.1.0",
"version_timestamp": "2025-03-21T14:30:00Z",
"rule_name": "手机号格式校验",
"rule_type": "format",
"condition": { ... },
"action": { ... },
"changelog": "放宽对虚拟运营商号段的支持,新增 170、171 号段"
}
小技巧:版本快照建议存到独立的版本表里,别和当前生效的规则混在一起。查询当前规则时只查最新版本,回滚时直接从版本表里捞旧版本覆盖回去。我有个项目就是这么干的,上线两年,回滚过三次,每次都在 30 秒内搞定。
版本管理的另一个好处是灰度发布。你可以先让新规则在测试环境跑几天,确认没问题了再推到生产。万一出问题,切回旧版本就行。
4.3 规则优先级设计
规则多了,冲突是必然的。比如一条规则说「手机号必须 11 位」,另一条说「手机号不能以 0 开头」。两条都命中,先执行哪条?
优先级设计就是为了解决这个问题的。我一般用数字越小优先级越高的方式:
| 优先级 | 规则类型 | 典型规则 |
|---|---|---|
| 1 | 安全校验 | SQL 注入检测、XSS 过滤 |
| 2 | 格式校验 | 手机号、邮箱、身份证格式 |
| 3 | 业务约束 | 金额范围、日期先后顺序 |
| 4 | 数据增强 | 默认值填充、字段格式化 |
为什么要这么排?你想想看,安全校验必须最先执行——如果数据里带了一段恶意脚本,后面的格式校验和业务约束根本没必要跑。格式校验次之,因为格式不对,业务约束也没意义。最后才是数据增强,比如给缺失的字段补默认值。
注意:优先级相同的规则,按 rule_id 的字典序执行。但我不建议依赖这个顺序——如果两条规则有依赖关系,应该显式设置不同的优先级,而不是靠排序碰运气。我曾经踩过这个坑,两条优先级相同的规则,一个先执行替换,一个后执行校验,结果替换后的数据把校验搞崩了。后来我强制要求:有依赖关系的规则,优先级必须差至少 1。
优先级信息直接嵌入规则元数据里:
{
"rule_id": "r_20250321_001",
"priority": 2,
"rule_name": "手机号格式校验",
"condition": { ... },
"action": { ... }
}
规则引擎加载时,先按优先级排序,再按 rule_id 排序。这样执行顺序就确定了,不会出现「这次跑和上次跑结果不一样」的诡异问题。
4.4 整体模型结构图
说了这么多,咱们用一张图把整个模型串起来。这张图展示了规则从定义到执行的完整链路:
从图里能看出来,规则元数据是核心,版本管理和优先级是它的两个「增强属性」。三者一起喂给规则执行引擎,引擎按优先级排序、按版本加载,最后输出执行结果。
4.5 落地建议
模型设计好了,怎么落地?我给出几个实操建议:
- 元数据存数据库,版本存历史表:当前生效的规则放主表,历史版本放版本表。查询性能好,回滚也方便。
- 优先级用数字,别用字符串:数字排序快,而且扩展性好。你想想,如果以后要插一条优先级在 2 和 3 之间的规则,数字 2.5 就能搞定,字符串就麻烦了。
- 版本号自动递增:每次修改规则,系统自动生成新版本号,不允许手动指定。避免人为错误。
- 规则变更走审批流程:尤其是生产环境的规则,改之前最好有人 review 一下。我见过有人把正则写错了,导致所有合法手机号都被拒绝——那场面,啧啧。
我的习惯:每次上线新规则,我都会先在测试环境跑一遍全量历史数据,看看有没有误杀。确认没问题了,再灰度推送到生产。这个流程虽然慢一点,但稳。数据质量这件事,稳比快重要。
好了,规则模型设计这块就聊到这儿。模型搭好了,后面写规则引擎、嵌入 API 层,都是水到渠成的事。