# 商品推荐功能实施计划 ## 目标 在小程序中实现三类商品推荐场景: 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 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`:在认知雷达图下方添加 `` - 其他维度页(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 analysisResult = parseAnalysisResult(reportId); List dimensionNeeds = extractDimensionNeeds(analysisResult); List 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 weakDims = findWeakDimensions(result); // e.g. ["focusScore", "processingSpeedScore"] // 映射到 dimensionCode:focusScore/processingSpeedScore → "wisdom" // 查询对应维度商品 List products = productRecommendationService.getReportRelatedProducts( "cognitive_assessment", weakDims, userId, 3); // 记录推荐日志 ``` ### 4.3 新增 ProductRecommendationService 方法 ```java public List 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 天 |