嵌套文档更合理:单活动专属规则直接存 promotions 文档的 rules 字段;跨活动共享规则则拆至 promo_rules 集合,用 rule_id 引用并加 version 灰度控制。

优惠规则用嵌套文档还是单独集合
直接存嵌套文档更合理,尤其当规则和商品/活动生命周期强绑定时。MongoDB 的 document 天然支持树形结构,把条件树塞进 conditions 字段里,查活动时一并取出,避免多次查询或 $lookup 关联。
但要注意:如果同一套规则被上百个活动复用,或者规则本身频繁更新、需独立审计,那就该拆到 promo_rules 集合里,用 rule_id 引用——否则每次改规则都要批量更新所有活动文档,容易写冲突或漏更新。
- 单活动专属规则 → 嵌套在
promotions文档的rules字段里 - 跨活动共享规则 → 单独集合 + ObjectId 引用,加
version字段做灰度控制 - 规则字段名别叫
expression(易误解为字符串脚本),用condition_tree更准确
条件树结构怎么设计才方便执行和调试
别用纯字符串存表达式(比如 "price > 100 && user.level >= 2"),执行靠 JS eval 或服务端解析,既慢又不安全。应该用可序列化的结构描述逻辑关系:
{
"type": "AND",
"children": [
{
"type": "GT",
"field": "order_amount",
"value": 100
},
{
"type": "IN",
"field": "user.tags",
"value": ["vip", "gold"]
}
]
}
这种结构能直接映射成后端 if 判断链,也方便前端可视化编辑器渲染节点。注意几个坑:
-
field路径要统一用点号分隔(如"user.profile.age"),避免数组下标("items.0.name")导致匹配歧义 - 叶子节点的
value类型必须明确:数字就存 Number,字符串带引号,布尔值用true/false,别混成字符串"true" - 如果支持“当前时间比较”,字段名别硬写
"now",用占位符"$NOW",执行时再替换,防止序列化时出错
表达式里引用动态上下文字段怎么安全取值
规则执行时需要读订单、用户、商品等上下文数据,但不能让表达式任意访问对象属性(比如 user.__proto__.constructor.eval(...))。推荐两种方式:
- 白名单字段投影:执行前只把允许访问的字段扁平化注入一个干净对象,如
{ order_amount: 199, user_level: 3, sku_category: "electronics" },表达式只能读这些 key - 沙箱函数封装:用
get("user.level")或ctx("order.items.0.price")这类受限访问函数,内部做路径校验和类型兜底,不暴露原始对象 - 绝对不要在 MongoDB 查询里用
$where执行用户传入的 JS 表达式——它会全库扫描、无法索引,且有执行任意代码风险
性能和索引怎么配合条件树生效
条件树本身没法建索引,但高频过滤字段可以单独拎出来冗余存储。比如一个满减规则常看 order_amount 和 user.tier,就在活动文档里加两个字段:min_order_amount: 100、required_user_tier: "gold",然后对它们建复合索引:
db.promotions.createIndex({ "min_order_amount": 1, "required_user_tier": 1, "status": 1 })
这样查“当前可用的满减活动”时先走索引快速筛选,再对结果集逐个执行完整条件树判断。容易忽略的是:如果条件树里有 IN 或 REGEX 这类低效操作,得在应用层加缓存或预计算,MongoDB 本身扛不住实时遍历。
树越深、节点越多,执行耗时越不可控;线上建议限制最大深度为 4,单节点条件数不超过 8,超出就报错提示运营配置异常。










