# 商品推荐规则引擎设计文档 ## 1. 概述 统一商品推荐的 5 种业务规则,引入统一规则引擎,由配置表 `recommendation_rules` 驱动。现有实现(已购90天排除 + 定时复购提醒)并入新引擎,所有场景(维度页/商城首页/商品详情/下单成功/报告关联)统一走一套计算逻辑,配置在后台管理端。 ## 2. 核心目标 1. **统一入口**:所有推荐场景通过同一个引擎计算,输入输出一致 2. **配置驱动**:规则均由 `recommendation_rules` 表配置,运营可在 Web 管理端增删改,无需改代码 3. **降级兼容**:规则表为空时退化为现有硬编码行为,保证不出现空结果 4. **最小侵入**:现有接口/服务改造仅改调用引擎,核心业务逻辑不改 5. **覆盖全场景**: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 注解示例(仅示意) ```java @Mapper public interface RecommendationRuleMapper { @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND FIND_IN_SET(?, scene_codes)") List selectByScene(String sceneCode); @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? ORDER BY priority DESC") List selectByRuleType(Integer ruleType); @Select("SELECT * FROM recommendation_rules WHERE enabled = 1 AND rule_type = ? AND FIND_IN_SET(?, trigger_product_ids)") List selectByProductIds(Integer ruleType, String productIdsJson); } ``` ## 4. 引擎设计 ### 4.1 入口接口 ```java /** * 推荐上下文 */ 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 excludeProductIds; // 用户自行排除的商品ID(如已选/不喜欢) private Integer limit; // 返回上限,默认6 } ``` ```java /** * 推荐结果项 */ 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/... } ``` ```java /** * 引擎核心方法 */ public interface RecommendationRuleEngine { /** * 计算推荐 * @param ctx 推荐上下文 * @param limit 若覆盖默认limit,可传;否则用 ctx.limit * @return 推荐列表 */ List 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,每项含 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. 容错与降级 1. **规则表为空时**:引擎不抛出异常,直接进入“场景基础候选”阶段(即现有行为),全局已购排除默认 90 天,返回已有候选列表。 2. **规则配置冲突**:同一规则 type 存在多条启用记录时,按 priority 降序处理,较早的生效。运营若需调整顺序,在管理端修改 priority 即可。 3. **trigger_product_id 不存在**:引擎检查 trigger_product_id 是否存在于 products 表中,不存在则跳过该规则,记录 warn 日志。 4. **scene_codes 匹配失败**: - 引擎首先尝试精确匹配 `FIND_IN_SET(sceneCode, scene_codes)` - 若未匹配,再尝试前缀匹配(如 sceneCode 为 "dimension_page",scene_codes 包含 "dimension" 也视为匹配) - 作为最后兜底,若 scene_codes 为空字符串 `""`,视为“所有场景生效” 5. **时间窗口边界**: - time_window_days=0 表示“只计算今日购买” - time_window_days<0 视为异常,引擎取绝对值但标记日志警告 ## 8. 测试要点 1. **规则单独生效**: - 规则1:已购特定商品(180天)从候选排除 - 规则2:30天后出现复购推荐 - 规则3:购买A后推B - 规则4:购买A后不推B - 规则5:购买A+B+C系列后重点推X 2. **优先级排序**: - 验证 priority 从高到低的处理顺序 - 验证同 priority 时,matchScore DESC 3. **scope family/individual**: - family:查 familyId 关联订单 - individual:查 buyerId(userId) 订单 4. **time_window_days 边界**:0、90、365 各自行为 5. **降级测试**: - 删除所有规则记录,验证系统仍能返回推荐(退化为全局已购排除 + 基础候选) - 验证旧定时任务仍正常工作 6. **编译验证**:`mvn clean compile` ## 9. 变更清单 ### 9.1 数据库 ```sql -- 新建表(迁移编号自行在 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. 完成标准 - [ ] 新表 `recommendation_rules` 建表 + schema.sql 同步 - [ ] `RecommendationRuleMapper` + `RecommendationRuleService` 实现 - [ ] `RecommendationRuleEngine.recommend()` 完整实现(5种规则流程) - [ ] `RepurchaseReminderService` 规则2 配置读取改造 - [ ] `ProductRecommendationService` 改为调用 engine - [ ] 管理端 CRUD 页面 + API - [ ] 单元测试:5种规则各自命中/未命中 + priority 排序 + scope family/individual - [ ] 集成测试:mvn clean compile 通过 - [ ] 文档同步:`docs/superpowers/specs/2026-09-17-product-recommendation-rule-engine-design.md`