|
|
@@ -0,0 +1,420 @@
|
|
|
+# 商品推荐功能实施计划
|
|
|
+
|
|
|
+## 目标
|
|
|
+
|
|
|
+在小程序中实现三类商品推荐场景:
|
|
|
+
|
|
|
+1. **维度页推荐组件**:在身/智/心/行/富页面底部展示维度关联商品,按匹配分排序
|
|
|
+2. **AI对话推荐**:对话过程中根据上下文推荐相关商品/服务
|
|
|
+3. **报告关联推荐**:上传健康报告或认知测评后,关联推荐相关商品
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 第一阶段:基础设施(P0)
|
|
|
+
|
|
|
+### 1.1 新建数据库表
|
|
|
+
|
|
|
+**步骤 1:`product_dimension_mapping` 表**
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/resources/schema.sql`
|
|
|
+在 `products` 表定义之后添加:
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE product_dimension_mapping (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ product_id BIGINT NOT NULL COMMENT '商品ID',
|
|
|
+ dimension_code VARCHAR(32) NOT NULL COMMENT '维度: body/wisdom/mind/action/wealth',
|
|
|
+ match_score INT DEFAULT 100 COMMENT '匹配度 0-100',
|
|
|
+ match_reason VARCHAR(200) COMMENT '匹配原因,如"专注力提升"',
|
|
|
+ tags VARCHAR(500) COMMENT '推荐标签 JSON',
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME,
|
|
|
+ updated_at DATETIME,
|
|
|
+ INDEX idx_product (product_id),
|
|
|
+ INDEX idx_dimension (dimension_code)
|
|
|
+) ENGINE=InnoDB COMMENT='商品维度关联表';
|
|
|
+```
|
|
|
+
|
|
|
+**步骤 2:`product_recommendation_log` 表**
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE product_recommendation_log (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ user_id BIGINT,
|
|
|
+ family_id BIGINT,
|
|
|
+ product_id BIGINT NOT NULL,
|
|
|
+ scene VARCHAR(32) NOT NULL COMMENT 'dimension_page/ai_chat/report_upload/repurchase',
|
|
|
+ reason VARCHAR(200),
|
|
|
+ match_score INT,
|
|
|
+ was_clicked TINYINT DEFAULT 0,
|
|
|
+ was_purchased TINYINT DEFAULT 0,
|
|
|
+ clicked_at DATETIME,
|
|
|
+ purchased_at DATETIME,
|
|
|
+ created_at DATETIME,
|
|
|
+ INDEX idx_user_scene (user_id, scene),
|
|
|
+ INDEX idx_product_purchased (product_id, was_purchased)
|
|
|
+) ENGINE=InnoDB COMMENT='推荐曝光日志';
|
|
|
+```
|
|
|
+
|
|
|
+**步骤 3:`repurchase_reminder_record` 表**
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE repurchase_reminder_record (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ user_id BIGINT NOT NULL,
|
|
|
+ product_id BIGINT NOT NULL,
|
|
|
+ order_id BIGINT COMMENT '关联订单ID',
|
|
|
+ reminder_days INT DEFAULT 30,
|
|
|
+ sent_at DATETIME,
|
|
|
+ clicked TINYINT DEFAULT 0,
|
|
|
+ purchased TINYINT DEFAULT 0,
|
|
|
+ INDEX idx_user_pending (user_id, purchased)
|
|
|
+) ENGINE=InnoDB COMMENT='复购提醒发送记录';
|
|
|
+```
|
|
|
+
|
|
|
+**步骤 4:`repurchase_reminder_config` 表**
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE repurchase_reminder_config (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ product_category VARCHAR(100),
|
|
|
+ product_id BIGINT COMMENT '特定商品ID(优先于category)',
|
|
|
+ reminder_days INT DEFAULT 30,
|
|
|
+ reminder_template VARCHAR(500) COMMENT '提醒话术模板',
|
|
|
+ max_reminders INT DEFAULT 3,
|
|
|
+ enabled TINYINT DEFAULT 1,
|
|
|
+ created_at DATETIME
|
|
|
+) ENGINE=InnoDB COMMENT='复购提醒配置表';
|
|
|
+```
|
|
|
+
|
|
|
+**步骤 5:`products` 表新增字段**
|
|
|
+
|
|
|
+```sql
|
|
|
+ALTER TABLE products ADD COLUMN recommendation_tags VARCHAR(500) COMMENT '推荐标签 JSON';
|
|
|
+ALTER TABLE products ADD COLUMN purchase_count_threshold INT DEFAULT 0;
|
|
|
+ALTER TABLE products ADD COLUMN repurchase_interval_days INT DEFAULT 30;
|
|
|
+```
|
|
|
+
|
|
|
+### 1.2 数据库迁移脚本
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`
|
|
|
+
|
|
|
+在 `runMigrations()` 方法末尾添加迁移N(编号递增),使用 `ensureColumn` 和 `jdbcTemplate.execute` 创建新表。
|
|
|
+
|
|
|
+### 1.3 创建实体类
|
|
|
+
|
|
|
+| 类 | 路径 | 说明 |
|
|
|
+|---|------|------|
|
|
|
+| `ProductDimensionMapping` | `entity/ProductDimensionMapping.java` | 商品-维度关联 |
|
|
|
+| `ProductRecommendationLog` | `entity/ProductRecommendationLog.java` | 推荐曝光日志 |
|
|
|
+| `RepurchaseReminderRecord` | `entity/RepurchaseReminderRecord.java` | 复购提醒记录 |
|
|
|
+| `RepurchaseReminderConfig` | `entity/RepurchaseReminderConfig.java` | 复购提醒配置 |
|
|
|
+
|
|
|
+### 1.4 创建 Mapper
|
|
|
+
|
|
|
+| 类 | 路径 | 说明 |
|
|
|
+|---|------|------|
|
|
|
+| `ProductDimensionMappingMapper` | `mapper/ProductDimensionMappingMapper.java` | 继承 BaseMapper |
|
|
|
+| `ProductRecommendationLogMapper` | `mapper/ProductRecommendationLogMapper.java` | 继承 BaseMapper |
|
|
|
+| `RepurchaseReminderRecordMapper` | `mapper/RepurchaseReminderRecordMapper.java` | 继承 BaseMapper |
|
|
|
+| `RepurchaseReminderConfigMapper` | `mapper/RepurchaseReminderConfigMapper.java` | 继承 BaseMapper |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 第二阶段:维度页推荐(P0)
|
|
|
+
|
|
|
+### 2.1 后端接口
|
|
|
+
|
|
|
+**接口:`POST /api/recommend/dimension-products`**
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/controller/recommendation/ProductRecommendationController.java`
|
|
|
+
|
|
|
+**请求体:**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "dimensionCode": "wisdom",
|
|
|
+ "familyId": 1,
|
|
|
+ "memberId": 5,
|
|
|
+ "excludeProductIds": [3, 7],
|
|
|
+ "limit": 6
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**响应:**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "id": 10,
|
|
|
+ "name": "认知能力测评套餐",
|
|
|
+ "coverImage": "https://...",
|
|
|
+ "price": 29900,
|
|
|
+ "memberPrice": 19900,
|
|
|
+ "reason": "专注力得分偏低,推荐优先提升",
|
|
|
+ "matchScore": 85,
|
|
|
+ "productType": "assessment",
|
|
|
+ "url": "/pages/shop/detail?id=10"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 推荐算法(`ProductRecommendationService`)
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/service/ProductRecommendationService.java`
|
|
|
+
|
|
|
+**算法逻辑:**
|
|
|
+
|
|
|
+```
|
|
|
+1. 查询所有上架且有库存的商品(status='上架', stock > 0)
|
|
|
+2. 匹配维度:
|
|
|
+ a. Product.domain == dimensionCode(权重1.0)
|
|
|
+ b. 或 product_dimension_mapping.dimension_code == dimensionCode(权重1.5,从mapping表读取)
|
|
|
+3. 查询 members 的 five_dimension_scores,按 dimension_code 排序
|
|
|
+ → 得分低的维度 → 对应商品推荐权重 × 1.2
|
|
|
+4. 查询 ProductOrder,确认排除已购商品(buyerId 或 familyId 在 90 天内购买过)
|
|
|
+5. 按 match_score = 基础分 × 维度缺口加权 × 已购惩罚 排序
|
|
|
+6. 取前 limit 条返回
|
|
|
+```
|
|
|
+
|
|
|
+**新增方法:**
|
|
|
+- `getDimensionRecommendations(String dimensionCode, Long familyId, Long memberId, List<Long> excludeProductIds, int limit)`
|
|
|
+- `getPurchasedProductIds(Long userId, Long familyId, int daysAgo)`
|
|
|
+- `getMemberDimensionScores(Long familyId, Long memberId)`
|
|
|
+
|
|
|
+### 2.3 前端组件
|
|
|
+
|
|
|
+路径:`cfc-frontend/components/DimensionProductList.vue`
|
|
|
+
|
|
|
+**功能:**
|
|
|
+- Props: `dimensionCode`, `familyId`, `memberScores`, `excludeProductIds`, `limit`
|
|
|
+- 加载时调用 `POST /api/recommend/dimension-products`
|
|
|
+- 显示:商品封面图、名称、价格、推荐理由、匹配分 badge
|
|
|
+- 点击跳转商品详情页
|
|
|
+
|
|
|
+**嵌入位置:**
|
|
|
+- `cfc-frontend/pages/wisdom/index.vue`:在认知雷达图下方添加 `<DimensionProductList dimensionCode="wisdom" ... />`
|
|
|
+- 其他维度页(body/mind/action/wealth)同步添加
|
|
|
+
|
|
|
+### 2.4 推荐日志写入
|
|
|
+
|
|
|
+每次返回推荐结果前,写入 `product_recommendation_log`(scene=`dimension_page`),记录 product_id / user_id / match_score / created_at。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 第三阶段:AI对话推荐(P1)
|
|
|
+
|
|
|
+### 3.1 扩展 FamilyContextService
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java`
|
|
|
+
|
|
|
+**修改 `buildContext(Long userId)` 方法:**
|
|
|
+
|
|
|
+在返回的 inputs Map 中新增3个字段:
|
|
|
+
|
|
|
+```java
|
|
|
+// 新增:成员维度得分(供 Dify 理解家庭短板)
|
|
|
+inputs.put("dimensionScores", buildDimensionScoresContext(userId));
|
|
|
+
|
|
|
+// 新增:最近认知测评摘要
|
|
|
+inputs.put("recentCognitiveResult", buildCognitiveContext(userId));
|
|
|
+
|
|
|
+// 新增:已购买商品标签(避免重复推荐)
|
|
|
+inputs.put("purchasedProductTags", buildPurchasedTagsContext(userId));
|
|
|
+```
|
|
|
+
|
|
|
+**新增私有方法:**
|
|
|
+- `buildDimensionScoresContext(Long userId)` → 查询 `five_dimension_scores` 返回 `[{dimension, score, memberName}]`
|
|
|
+- `buildCognitiveContext(Long userId)` → 查询 `dan_assessment_results` 最新一条,返回 `{weakDimensions: [...], overallScore}`
|
|
|
+- `buildPurchasedTagsContext(Long userId)` → 查询 `product_orders` 中用户已购商品的 `recommendation_tags`
|
|
|
+
|
|
|
+### 3.2 修改 AIChatController 解析逻辑
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java`
|
|
|
+
|
|
|
+**扩展 `[RECOMMEND]` 解析:**
|
|
|
+
|
|
|
+在 `sendNutritionMessage()` 的 `[RECOMMEND:]` 解析块中新增:
|
|
|
+
|
|
|
+```java
|
|
|
+// 新增:从 RecommendationQuery 中取 dimensionCode 和 userId,过滤已购
|
|
|
+if (tags != null && !tags.isEmpty()) {
|
|
|
+ RecommendationQuery rq = new RecommendationQuery();
|
|
|
+ rq.setNutritionTags(tags);
|
|
|
+ rq.setTypes(types != null && !types.isEmpty() ? types : null);
|
|
|
+ rq.setLimit(limit);
|
|
|
+ rq.setUserId(userId); // 新增:传入userId用于过滤已购
|
|
|
+ rq.setFamilyId(familyId); // 新增:传入familyId用于过滤已购
|
|
|
+ recommendations = recommendationService.search(rq);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3.3 扩展 RecommendationService
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java`
|
|
|
+
|
|
|
+**修改 `search(RecommendationQuery query)` 方法:**
|
|
|
+
|
|
|
+```java
|
|
|
+// 在 searchProducts() 中新增过滤逻辑:
|
|
|
+// 1. 如果 query.userId 或 query.familyId 存在,排除 90 天内已购商品
|
|
|
+// 2. 如果 query.dimensionCode 存在,按 match_score 排序时加权
|
|
|
+```
|
|
|
+
|
|
|
+**新增字段到 `RecommendationQuery` DTO:**
|
|
|
+```java
|
|
|
+private Long userId;
|
|
|
+private Long familyId;
|
|
|
+private String dimensionCode; // 用于维度加权
|
|
|
+```
|
|
|
+
|
|
|
+### 3.4 扩展前端聊天页展示
|
|
|
+
|
|
|
+路径:`cfc-frontend/pages/ai/chat.vue`
|
|
|
+
|
|
|
+在现有的 recommendation 卡片展示逻辑中,新增:
|
|
|
+- 推荐理由展示(从返回结果的 `reason` 字段读取)
|
|
|
+- 已购商品标记(接口返回时已过滤,前端无需额外处理)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 第四阶段:报告关联推荐(P1)
|
|
|
+
|
|
|
+### 4.1 健康报告上传后触发
|
|
|
+
|
|
|
+**触发点:** `HealthReportService.analyze(reportId)` 执行完成后
|
|
|
+
|
|
|
+在 `cfc-backend/src/main/java/com/etotem/cfc/service/HealthAnalysisService.java` 的 `analyze()` 方法末尾添加:
|
|
|
+
|
|
|
+```java
|
|
|
+// 触发维度推荐
|
|
|
+try {
|
|
|
+ Map<String, Object> analysisResult = parseAnalysisResult(reportId);
|
|
|
+ List<String> dimensionNeeds = extractDimensionNeeds(analysisResult);
|
|
|
+ List<RecommendationResult> products =
|
|
|
+ productRecommendationService.getReportRelatedProducts(
|
|
|
+ "health_report", analysisResult, userId, 3);
|
|
|
+ // 记录推荐日志,scene = 'report_upload'
|
|
|
+ for (RecommendationResult r : products) {
|
|
|
+ productRecommendationLogService.log(userId, r, "report_upload",
|
|
|
+ "健康报告分析触发:" + String.join(",", dimensionNeeds));
|
|
|
+ }
|
|
|
+} catch (Exception e) {
|
|
|
+ log.warn("报告关联推荐生成失败: {}", e.getMessage());
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 认知测评上传后触发
|
|
|
+
|
|
|
+**触发点:** `DanAssessmentResult` 写入完成(source=parent_upload)
|
|
|
+
|
|
|
+在 `cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java` 的 `saveAssessmentResult()` 或相关写入方法末尾添加类似逻辑:
|
|
|
+
|
|
|
+```java
|
|
|
+// 提取6维得分中最低的2个维度
|
|
|
+List<String> weakDims = findWeakDimensions(result); // e.g. ["focusScore", "processingSpeedScore"]
|
|
|
+// 映射到 dimensionCode:focusScore/processingSpeedScore → "wisdom"
|
|
|
+// 查询对应维度商品
|
|
|
+List<RecommendationResult> products =
|
|
|
+ productRecommendationService.getReportRelatedProducts(
|
|
|
+ "cognitive_assessment", weakDims, userId, 3);
|
|
|
+// 记录推荐日志
|
|
|
+```
|
|
|
+
|
|
|
+### 4.3 新增 ProductRecommendationService 方法
|
|
|
+
|
|
|
+```java
|
|
|
+public List<RecommendationResult> getReportRelatedProducts(
|
|
|
+ String reportType, Object analysis, Long userId, int limit) {
|
|
|
+ // reportType = "health_report" 或 "cognitive_assessment"
|
|
|
+ // 根据 reportType 提取关联维度码和标签
|
|
|
+ // 调用 search() 时设置 dimensionCode 和已购过滤
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 第五阶段:复购提醒(P2)
|
|
|
+
|
|
|
+### 5.1 定时任务
|
|
|
+
|
|
|
+路径:`cfc-backend/src/main/java/com/etotem/cfc/service/RepurchaseReminderService.java`
|
|
|
+
|
|
|
+**定时扫描(每天 09:00):**
|
|
|
+
|
|
|
+```java
|
|
|
+@Scheduled(cron = "0 0 9 * * ?")
|
|
|
+public void scanAndCreateReminders() {
|
|
|
+ // 1. 查询过去 30~60 天内有已支付订单的用户
|
|
|
+ // 2. 对每个订单商品,匹配 repurchase_reminder_config
|
|
|
+ // 3. 检查是否已发送过 reminder 且未过期
|
|
|
+ // 4. 创建 repurchase_reminder_record(sent_at = now)
|
|
|
+ // 5. 发送小程序订阅消息(调用现有消息通知机制)
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 记录点击/购买行为
|
|
|
+
|
|
|
+```java
|
|
|
+public void onReminderClicked(Long reminderId) { ... }
|
|
|
+public void onReminderPurchased(Long reminderId, Long orderId) { ... }
|
|
|
+```
|
|
|
+
|
|
|
+### 5.3 前端复购提醒组件
|
|
|
+
|
|
|
+路径:`cfc-frontend/components/RepurchaseReminder.vue`
|
|
|
+
|
|
|
+- 在首页或消息 Tab 展示待处理复购提醒卡片
|
|
|
+- 点击跳商品详情页(携带 `from=repurchase` 参数)
|
|
|
+- 前端 API:`POST /api/recommend/repurchase-reminders` → `GET /api/recommend/repurchase-reminders`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 验证步骤
|
|
|
+
|
|
|
+| 步骤 | 操作 | 预期结果 |
|
|
|
+|------|------|---------|
|
|
|
+| 1 | `mvn clean compile` | 编译通过,无错误 |
|
|
|
+| 2 | `curl -X POST /api/recommend/dimension-products` | 返回维度关联商品列表 |
|
|
|
+| 3 | 小程序打开智页 | 底部显示推荐商品(DimensionProductList) |
|
|
|
+| 4 | 上传健康报告 | 推荐日志写入,scene=report_upload |
|
|
|
+| 5 | AI营养对话触发 [RECOMMEND] | 返回过滤已购后的商品 |
|
|
|
+| 6 | 查看 `product_recommendation_log` 表 | 有 dimension_page 和 report_upload 记录 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件清单
|
|
|
+
|
|
|
+| 操作 | 文件路径 |
|
|
|
+|------|---------|
|
|
|
+| 新增表 | `resources/schema.sql` 中 4 个 CREATE TABLE + ALTER TABLE |
|
|
|
+| 新增迁移 | `config/DatabaseInitializer.java` runMigrations() |
|
|
|
+| 新增实体 ×4 | `entity/ProductDimensionMapping.java` 等 |
|
|
|
+| 新增 Mapper ×4 | `mapper/ProductDimensionMappingMapper.java` 等 |
|
|
|
+| 新增 Service | `service/ProductRecommendationService.java` |
|
|
|
+| 新增 Service | `service/RepurchaseReminderService.java` |
|
|
|
+| 新增 Controller | `controller/recommendation/ProductRecommendationController.java` |
|
|
|
+| 修改 Service | `service/FamilyContextService.java` — buildContext() |
|
|
|
+| 修改 DTO | `dto/RecommendationQuery.java` — 新增 userId/familyId/dimensionCode |
|
|
|
+| 修改 Service | `service/RecommendationService.java` — 过滤已购 |
|
|
|
+| 修改 Controller | `controller/ai/AIChatController.java` — 传入 userId/familyId |
|
|
|
+| 修改 Service | `service/HealthAnalysisService.java` — 报告上传触发推荐 |
|
|
|
+| 修改 Service | `service/CognitiveService.java` — 测评上传触发推荐 |
|
|
|
+| 修改 Service | `service/ProductRecommendationLogService.java`(新建) |
|
|
|
+| 新增前端组件 | `components/DimensionProductList.vue` |
|
|
|
+| 新增前端组件 | `components/RepurchaseReminder.vue` |
|
|
|
+| 修改前端页面 | `pages/wisdom/index.vue` 等 — 嵌入 DimensionProductList |
|
|
|
+| 修改前端页面 | `pages/ai/chat.vue` — 推荐理由展示 |
|
|
|
+| 修改前端 API | `utils/api.js` — 新增推荐相关接口 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 风险与依赖
|
|
|
+
|
|
|
+| 风险 | 缓解 |
|
|
|
+|------|------|
|
|
|
+| Dify 幻觉推荐 | 后端兜底过滤(status=上架,stock>0),仅在 nutrition/send 接口触发 |
|
|
|
+| 冷启动(mapping 表空) | 初期用 `Product.domain` 隐式匹配;mapping 表由运营后台手动标注或导入 |
|
|
|
+| 推荐效果未验证 | `product_recommendation_log` 记录曝光,后续可做转化率统计 |
|
|
|
+| 复购周期判断不准 | `repurchase_interval_days` 可按商品类别配置,默认 30 天 |
|