|
|
@@ -0,0 +1,321 @@
|
|
|
+# 商品推荐规则引擎设计文档
|
|
|
+
|
|
|
+## 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<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 入口接口
|
|
|
+
|
|
|
+```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<Long> 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<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<String,Object> 格式 |
|
|
|
+| 新增 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`
|