PLAN.md 15 KB

商品推荐功能实施计划

目标

在小程序中实现三类商品推荐场景:

  1. 维度页推荐组件:在身/智/心/行/富页面底部展示维度关联商品,按匹配分排序
  2. AI对话推荐:对话过程中根据上下文推荐相关商品/服务
  3. 报告关联推荐:上传健康报告或认知测评后,关联推荐相关商品

第一阶段:基础设施(P0)

1.1 新建数据库表

步骤 1:product_dimension_mapping

路径:cfc-backend/src/main/resources/schema.sqlproducts 表定义之后添加:

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

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

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

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 表新增字段

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(编号递增),使用 ensureColumnjdbcTemplate.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

请求体:

{
  "dimensionCode": "wisdom",
  "familyId": 1,
  "memberId": 5,
  "excludeProductIds": [3, 7],
  "limit": 6
}

响应:

{
  "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个字段:

// 新增:成员维度得分(供 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:] 解析块中新增:

// 新增:从 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) 方法:

// 在 searchProducts() 中新增过滤逻辑:
// 1. 如果 query.userId 或 query.familyId 存在,排除 90 天内已购商品
// 2. 如果 query.dimensionCode 存在,按 match_score 排序时加权

新增字段到 RecommendationQuery DTO:

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.javaanalyze() 方法末尾添加:

// 触发维度推荐
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.javasaveAssessmentResult() 或相关写入方法末尾添加类似逻辑:

// 提取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 方法

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):

@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 记录点击/购买行为

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-remindersGET /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 天