商品推荐规则引擎设计文档
1. 概述
统一商品推荐的 5 种业务规则,引入统一规则引擎,由配置表 recommendation_rules 驱动。现有实现(已购90天排除 + 定时复购提醒)并入新引擎,所有场景(维度页/商城首页/商品详情/下单成功/报告关联)统一走一套计算逻辑,配置在后台管理端。
2. 核心目标
- 统一入口:所有推荐场景通过同一个引擎计算,输入输出一致
- 配置驱动:规则均由
recommendation_rules 表配置,运营可在 Web 管理端增删改,无需改代码
- 降级兼容:规则表为空时退化为现有硬编码行为,保证不出现空结果
- 最小侵入:现有接口/服务改造仅改调用引擎,核心业务逻辑不改
- 覆盖全场景:5 种规则全覆盖,且易扩展新规则
3. 数据模型
3.1 新表 recommendation_rules
| 字段 |
类型 |
主键 |
说明 |
id |
BIGINT |
PK |
主键,AUTO_INCREMENT |
rule_type |
TINYINT |
— |
规则类型 1-5 |
rule_name |
VARCHAR(100) |
— |
运营可读名称 |
trigger_product_id |
BIGINT |
— |
单商品触发(规则1/2/3/4) |
trigger_product_ids |
VARCHAR(1000) |
— |
多商品触发 JSON 数组(规则5,如 "[1,2,3]") |
target_product_id |
BIGINT |
— |
目标商品ID(被推荐/排除/重点推) |
priority |
INT |
— |
优先级分,越大越先处理 |
enabled |
TINYINT |
— |
启用 1/0 |
time_window_days |
INT |
— |
购买回溯窗口(天),默认 null=不限 |
delay_days |
INT |
— |
复购延迟天数(规则2) |
scope |
VARCHAR(20) |
— |
family/individual |
scene_codes |
VARCHAR(500) |
— |
生效场景 JSON 数组,如 "[\"dimension_page\",\"product_detail\"]" |
created_at |
DATETIME |
— |
创建时间 |
updated_at |
DATETIME |
— |
更新时间 |
3.2 Index 建议
idx_rules_enabled(enabled, rule_type)
idx_rules_scene(scene_codes, enabled)
idx_rules_priority(priority, enabled)
3.3 MyBatis-Plus Mapper 注解示例(仅示意)
@Mapper
public interface RecommendationRuleMapper {
@Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND FIND_IN_SET(?, scene_codes)")
List<RecommendationRule> selectByScene(String sceneCode);
@Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? ORDER BY priority DESC")
List<RecommendationRule> selectByRuleType(Integer ruleType);
@Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? AND FIND_IN_SET(?, trigger_product_ids)")
List<RecommendationRule> selectByProductIds(Integer ruleType, String productIdsJson);
}
4. 引擎设计
4.1 入口接口
/**
* 推荐上下文
*/
public class RecommendationContext {
private Long userId; // 登录用户ID
private Long familyId; // 家庭ID(scene 为 family 时必填)
private Long memberId; // 成员ID(scope=individual 时)
private String scene; // 生效场景代码:dimension_page, mall_home, product_detail, after_order, report_related
private Long currentProductId; // 商品详情页/下单页的当前商品ID
private List<Long> excludeProductIds; // 用户自行排除的商品ID(如已选/不喜欢)
private Integer limit; // 返回上限,默认6
}
/**
* 推荐结果项
*/
public class RecommendedProduct {
private Long productId;
private String name;
private String coverImage;
private Integer price; // 分
private String reason; // 推荐理由
private Double matchScore; // 匹配分
private Integer ruleId; // 触发该结果的规则ID
private Integer ruleType; // 规则类型 1-5
private Integer priority; // 该规则优先级
private String source; // 来源标识:engine/campaign/...
}
/**
* 引擎核心方法
*/
public interface RecommendationRuleEngine {
/**
* 计算推荐
* @param ctx 推荐上下文
* @param limit 若覆盖默认limit,可传;否则用 ctx.limit
* @return 推荐列表
*/
List<RecommendedProduct> recommend(RecommendationContext ctx, Integer limit);
}
4.2 处理流程(按 priority 降序遍历规则)
1. 加载规则
- 根据 scene 匹配 `scene_codes`(精确匹配 JSON 中的字符串)
- enabled=1,按 priority DESC 排序
2. 查购买记录
- scope=family:查 family_members 关联的订单(ProductOrder 中 familyId 匹配)
- scope=individual:查 buyerId(userId) 的订单
- 状态过滤:status in ("已支付", "completed")
- 时间窗口:time_window_days 为 null 或 返回的时间差 <= time_window_days
若为 null,默认使用全局配置 default_purchased_exclude_days(默认 90)
3. 构建候选池(场景基础候选)
- dimension_page → 维度匹配商品(已有 ProductRecommendationService 逻辑)
- mall_home → 全量上架在售商品(product status=on_shelf, stock>0)
- product_detail → 从规则3/5 中命中的 targetProductId
- after_order → 从规则3/5 中命中的 targetProductId
- report_related → 报告提取的弱维度商品(已有 extractDimensionNeedsFromHealth/FromCognitive)
4. 应用规则(按 priority 从高到低遍历已加载的规则)
4.1 规则4(购买A不推B)
- 条件:已购买 trigger_product_id
- 动作:从候选池移除 target_product_id
- 若未命中则跳过
4.2 规则1(已购不推)
- 条件:已购买 trigger_product_id(或全局默认已购窗口内任意商品)
- 动作:从候选池移除 target_product_id
- 若未命中则跳过(已购默认由 step2/3 在全局层面完成,规则1只作特定覆盖)
4.3 规则3(购买A推B)
- 条件:已购买 trigger_product_id
- 动作:将 target_product_id 加入候选池 + reason="根据您的购买记录推荐"
- 若候选池已有该productId,则不重复添加,仅更新 reason
4.4 规则5(系列重点推)
- 条件:已购买 trigger_product_ids 中的 ALL 个商品
- 动作:将 target_product_id 加入候选池 + priority 加 10(提升排序位置)
4.5 规则2(定时复购)
- 条件:已购买 trigger_product_id 且(当前日期 - 购买日期) >= delay_days
- 动作:将 target_product_id 加入候选池 + reason="该商品您已购买,可考虑复购"
- 建议 delay_days 默认值:30
5. 结果排序 & 截断
- 按 (priority DESC, matchScore DESC) 排序
- 截断 limit(默认6)
6. 返回结果
- List<RecommendedProduct>,每项含 productId, name, coverImage, price, reason, matchScore, ruleId, ruleType, priority, source
4.3 规则语义表
| 规则 |
rule_type |
触发 |
动作 |
关键字段 |
| 1 已购不推 |
1 |
购买 trigger_product(或全局已购窗口内任意) |
从候选排除 target_product |
trigger_product_id, target_product_id, time_window_days |
| 2 定时复购 |
2 |
购买 trigger_product 且距今>=delay_days |
target_product 加入候选(复购) |
delay_days, target_product_id |
| 3 购买A推B |
3 |
购买 trigger_product(A) |
target_product(B) 加入候选 |
trigger_product_id, target_product_id |
| 4 购买A不推B |
4 |
购买 trigger_product(A) |
从候选排除 target_product(B) |
trigger_product_id, target_product_id |
| 5 系列重点推 |
5 |
购买 trigger_product_ids 全部 |
target_product 加入候选 + boost |
trigger_product_ids, target_product_id |
4.4 全局默认已购排除
- 系统级配置
default_purchased_exclude_days(默认 90 天),存入 application.yml 或系统参数表
- 引擎在 step2 查购买记录阶段,scope=family 时查 family 成员订单,scope=individual 时查 buyerId 订单
- 无论规则1是否配置,所有候选商品在进入 step4 前都已过滤掉回溯窗口内的已购商品(作为兜底)
- 规则1 仅作“特定商品覆盖”:如 trigger_product_id=123,time_window_days=180,则仅将商品123 的购买记录从候选排除(放宽或收窄默认 90 天)
5. 现有实现迁移
| 现有代码/组件 |
迁移动作 |
ProductRecommendationService.getDimensionRecommendations |
改为调用 engine.recommend(ctx.scene="dimension_page"),ctx.limit=6 |
getPurchasedProductIds(familyId, 90) 硬编码 90 天 |
移除该方法,改为在引擎内读 default_purchased_exclude_days |
RepurchaseReminderService.scanAndCreateReminders @Scheduled |
保留(不做改动,但内部配置读取改为从规则表读;rule_type=2 的 config 从 recommendation_rules 读,旧 repurchase_reminder_config 表数据迁移),外部定时任务仍可继续生成 reminder 记录 |
repurchase_reminder_config 表 |
保留(点击/购买追踪表),不做结构变更,仅在新引擎规则2 中通过 rule_type 匹配 |
ContentRecommendService.getPurchasedProductIds |
复用引擎的全局已购排除能力 |
| API Controller(维度页推荐) |
不改接口签名,内部改调用 engine.recommend,返回结果自动映射为 Map 格式
|
| 新增 API 接口(可选) |
若前端需要更多场景,可在 POST /api/recommend/* 下新增,内部同样调用 engine.recommend |
6. 管理端(cfc-web)
6.1 页面:商品推荐规则
- 列表:分页 + rule_type / enabled / priority 筛选 + 排序
- 新建/编辑:
- rule_type:1-5 下拉
- rule_name:文本
- trigger_product_id / trigger_product_ids:商品选择器(支持搜索、多选)
- target_product_id:目标商品选择器
- priority:数字输入(整数,默认 10)
- enabled:开关
- time_window_days:整数(天,默认 90)
- delay_days:整数(天,默认 30,仅规则2)
- scope:single/family 单选
- scene_codes:多行文本/标签选择器(可选:dimension_page, mall_home, product_detail, after_order, report_related)
6.2 管理端 API
GET /api/admin/recommendation-rules - 分页列表
POST /api/admin/recommendation-rules - 新建
PUT /api/admin/recommendation-rules/{id} - 编辑
DELETE /api/admin/recommendation-rules/{id} - 物理删除(慎用,建议设 enabled=0 停用)
POST /api/admin/recommendation-rules/batch-enable - 批量启用/停用
7. 容错与降级
规则表为空时:引擎不抛出异常,直接进入“场景基础候选”阶段(即现有行为),全局已购排除默认 90 天,返回已有候选列表。
规则配置冲突:同一规则 type 存在多条启用记录时,按 priority 降序处理,较早的生效。运营若需调整顺序,在管理端修改 priority 即可。
trigger_product_id 不存在:引擎检查 trigger_product_id 是否存在于 products 表中,不存在则跳过该规则,记录 warn 日志。
scene_codes 匹配失败:
- 引擎首先尝试精确匹配
FIND_IN_SET(sceneCode, scene_codes)
- 若未匹配,再尝试前缀匹配(如 sceneCode 为 "dimension_page",scene_codes 包含 "dimension" 也视为匹配)
- 作为最后兜底,若 scene_codes 为空字符串
"",视为“所有场景生效”
时间窗口边界:
- time_window_days=0 表示“只计算今日购买”
- time_window_days<0 视为异常,引擎取绝对值但标记日志警告
8. 测试要点
规则单独生效:
- 规则1:已购特定商品(180天)从候选排除
- 规则2:30天后出现复购推荐
- 规则3:购买A后推B
- 规则4:购买A后不推B
- 规则5:购买A+B+C系列后重点推X
优先级排序:
- 验证 priority 从高到低的处理顺序
- 验证同 priority 时,matchScore DESC
scope family/individual:
- family:查 familyId 关联订单
- individual:查 buyerId(userId) 订单
time_window_days 边界:0、90、365 各自行为
降级测试:
- 删除所有规则记录,验证系统仍能返回推荐(退化为全局已购排除 + 基础候选)
- 验证旧定时任务仍正常工作
编译验证:mvn clean compile
9. 变更清单
9.1 数据库
-- 新建表(迁移编号自行在 DatabaseInitializer 末尾递增)
CREATE TABLE recommendation_rules (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
rule_type TINYINT NOT NULL COMMENT '1-5',
rule_name VARCHAR(100) COMMENT '运营名称',
trigger_product_id BIGINT COMMENT '触发商品ID',
trigger_product_ids VARCHAR(1000) COMMENT '触发商品ID JSON 数组',
target_product_id BIGINT COMMENT '目标商品ID',
priority INT DEFAULT 10 COMMENT '优先级,越大越优',
enabled TINYINT DEFAULT 1 COMMENT '启用 1/0',
time_window_days INT COMMENT '回溯窗口(天)',
delay_days INT COMMENT '复购延迟天数',
scope VARCHAR(20) COMMENT 'family/individual',
scene_codes VARCHAR(500) COMMENT '生效场景 JSON 数组',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 建议索引
CREATE INDEX idx_rules_enabled ON recommendation_rules(enabled);
CREATE INDEX idx_rules_scene ON recommendation_rules(scene_codes);
CREATE INDEX idx_rules_priority ON recommendation_rules(priority);
9.2 Java 代码变更
- 新增
entity/RecommendationRule.java
- 新增
mapper/RecommendationRuleMapper.java
- 新增
service/RecommendationRuleService.java(CRUD + 规则校验)
- 新增
service/RecommendationRuleEngine.java(核心计算逻辑)
- 新增
controller/recommendation/RecommendationRuleAdminController.java(管理端 API)
- 修改
service/ProductRecommendationService.java → 改为调用 engine.recommend
- 修改
service/RepurchaseReminderService.java → rule_type=2 配置读取改为从 recommendation_rules
- 删除/废弃
repurchase_reminder_config 业务逻辑(保留表结构)
9.3 API 变更(后端)
- 维度页推荐:
POST /api/recommend/dimension-products 不变,内部实现改调用 engine
- 新增(如需要):
POST /api/recommend/mall-home、POST /api/recommend/product-detail、POST /api/recommend/after-order、POST /api/recommend/report-related
9.4 Web管理端
- 新增页面:
/admin/recommendation-rules(Vue 2 + Element UI,遵循项目现有设计规范)
- 权限:role=admin 或 role=teacher
10. 完成标准