|
@@ -0,0 +1,415 @@
|
|
|
|
|
+# 供应商体系差距修复 — 实施计划
|
|
|
|
|
+
|
|
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
|
|
|
|
+
|
|
|
|
|
+**Goal:** 修复供应商体系分析中发现的3个可实施差距:维度商品推荐端点缺失、复购提醒端点+定时任务缺失。
|
|
|
|
|
+
|
|
|
|
|
+**Architecture:** 在现有 `RecommendationController` + `RecommendationService` 上扩展,新增 `RepurchaseReminderScheduledTask` 定时任务类。
|
|
|
|
|
+
|
|
|
|
|
+**Tech Stack:** Spring Boot 2.7.18 / MyBatis-Plus / Java 8 / uni-app Vue 2
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 差距来源
|
|
|
|
|
+
|
|
|
|
|
+基于 `docs/superpowers/specs/2026-07-12-dimension-features-energy-map.md` 和 `docs/product-recommendation/PLAN.md` 的设计要求,对照实际代码发现:
|
|
|
|
|
+
|
|
|
|
|
+| 差距 | 设计文档要求 | 实际状态 |
|
|
|
|
|
+|------|-------------|---------|
|
|
|
|
|
+| `/api/recommend/dimension-products` | 维度页按 `dimensionCode` 查询匹配商品,按 `matchScore` 排序 | 前端 `getDimensionProducts()` 调用 → 后端无此端点 → 500 错误 |
|
|
|
|
|
+| `/api/recommend/repurchase-reminders` | 查询用户的复购提醒列表 | 前端 `getRepurchaseReminders()` 调用 → 后端无此端点 |
|
|
|
|
|
+| 复购定时调度 | "系统每日检查" → 生成复购提醒 | `repurchase_reminder_*` 表已建但无 `@Scheduled` 任务 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 1: 维度商品推荐端点 + Service 逻辑
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/ai/RecommendationController.java`
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java`
|
|
|
|
|
+- Inject: `cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductDimensionMappingMapper.java` (already exists, unused)
|
|
|
|
|
+
|
|
|
|
|
+### Step 0: RecommendationResult DTO 新增字段
|
|
|
|
|
+
|
|
|
|
|
+在 `RecommendationResult.java` 的 `private String source;` 之后新增:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+private Integer matchScore; // 维度匹配度 0-100 (dimension-product)
|
|
|
|
|
+private String matchReason; // 匹配原因 (dimension-product)
|
|
|
|
|
+private String reason; // 推荐理由 (repurchase/ai_chat/report)
|
|
|
|
|
+private Long productId; // 关联商品ID (repurchase-reminder)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 1: RecommendationService 新增维度商品查询方法
|
|
|
|
|
+
|
|
|
|
|
+在 `RecommendationService.java` 末尾添加两个方法:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+@Resource
|
|
|
|
|
+private ProductDimensionMappingMapper productDimensionMappingMapper;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 按维度查询关联商品(按 matchScore 降序)
|
|
|
|
|
+ */
|
|
|
|
|
+public List<RecommendationResult> getDimensionProducts(String dimensionCode, Long familyId, Integer limit) {
|
|
|
|
|
+ List<ProductDimensionMapping> mappings = productDimensionMappingMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<ProductDimensionMapping>()
|
|
|
|
|
+ .eq(ProductDimensionMapping::getDimensionCode, dimensionCode)
|
|
|
|
|
+ .eq(ProductDimensionMapping::getEnabled, 1)
|
|
|
|
|
+ .orderByDesc(ProductDimensionMapping::getMatchScore)
|
|
|
|
|
+ .last(limit != null ? "LIMIT " + Math.min(limit, 20) : "LIMIT 20")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ List<RecommendationResult> results = new ArrayList<>();
|
|
|
|
|
+ for (ProductDimensionMapping m : mappings) {
|
|
|
|
|
+ Product product = productMapper.selectById(m.getProductId());
|
|
|
|
|
+ if (product == null || !"上架".equals(product.getStatus()) || product.getStock() <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ RecommendationResult r = new RecommendationResult();
|
|
|
|
|
+ r.setType("product");
|
|
|
|
|
+ r.setId(product.getId());
|
|
|
|
|
+ r.setName(product.getName());
|
|
|
|
|
+ r.setDescription(product.getIntro() != null ? product.getIntro() : product.getDescription());
|
|
|
|
|
+ r.setCoverImage(product.getCoverImage());
|
|
|
|
|
+ r.setPrice(product.getPrice());
|
|
|
|
|
+ r.setUrl("/pages/shop/detail?id=" + product.getId());
|
|
|
|
|
+ r.setMatchScore(m.getMatchScore());
|
|
|
|
|
+ r.setMatchReason(m.getMatchReason());
|
|
|
|
|
+ r.setSource("dimension");
|
|
|
|
|
+ results.add(r);
|
|
|
|
|
+ }
|
|
|
|
|
+ return results;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 查询用户复购提醒列表
|
|
|
|
|
+ */
|
|
|
|
|
+public List<RecommendationResult> getRepurchaseReminders(Long userId) {
|
|
|
|
|
+ List<RepurchaseReminderRecord> reminders = repurchaseReminderRecordMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<RepurchaseReminderRecord>()
|
|
|
|
|
+ .eq(RepurchaseReminderRecord::getUserId, userId)
|
|
|
|
|
+ .eq(RepurchaseReminderRecord::getPurchased, 0)
|
|
|
|
|
+ .orderByDesc(RepurchaseReminderRecord::getSentAt)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ List<RecommendationResult> results = new ArrayList<>();
|
|
|
|
|
+ for (RepurchaseReminderRecord r : reminders) {
|
|
|
|
|
+ Product product = productMapper.selectById(r.getProductId());
|
|
|
|
|
+ if (product == null || !"上架".equals(product.getStatus())) continue;
|
|
|
|
|
+ RecommendationResult result = new RecommendationResult();
|
|
|
|
|
+ result.setType("repurchase");
|
|
|
|
|
+ result.setId(r.getId());
|
|
|
|
|
+ result.setProductId(product.getId());
|
|
|
|
|
+ result.setName(product.getName());
|
|
|
|
|
+ result.setCoverImage(product.getCoverImage());
|
|
|
|
|
+ result.setPrice(product.getPrice());
|
|
|
|
|
+ result.setUrl("/pages/shop/detail?id=" + product.getId());
|
|
|
|
|
+ result.setReason("复购提醒");
|
|
|
|
|
+ result.setSource("repurchase");
|
|
|
|
|
+ results.add(result);
|
|
|
|
|
+ }
|
|
|
|
|
+ return results;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 记录复购提醒点击
|
|
|
|
|
+ */
|
|
|
|
|
+public void markRepurchaseClicked(Long reminderId) {
|
|
|
|
|
+ RepurchaseReminderRecord record = repurchaseReminderRecordMapper.selectById(reminderId);
|
|
|
|
|
+ if (record != null) {
|
|
|
|
|
+ record.setClicked(1);
|
|
|
|
|
+ repurchaseReminderRecordMapper.updateById(record);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+需要的 import 和 DI 注入在类顶部新增:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import com.etotem.cfc.entity.ProductDimensionMapping;
|
|
|
|
|
+import com.etotem.cfc.entity.RepurchaseReminderRecord;
|
|
|
|
|
+import com.etotem.cfc.mapper.ProductDimensionMappingMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.RepurchaseReminderRecordMapper;
|
|
|
|
|
+
|
|
|
|
|
+// 在 @Resource 区域新增:
|
|
|
|
|
+@Resource
|
|
|
|
|
+private ProductDimensionMappingMapper productDimensionMappingMapper;
|
|
|
|
|
+
|
|
|
|
|
+@Resource
|
|
|
|
|
+private RepurchaseReminderRecordMapper repurchaseReminderRecordMapper;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 2: RecommendationController 新增三个端点
|
|
|
|
|
+
|
|
|
|
|
+在 `RecommendationController.java` 末尾(`}` 之前)新增:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+@Operation(summary = "按维度查询推荐商品")
|
|
|
|
|
+@PostMapping("/dimension-products")
|
|
|
|
|
+public Result<List<RecommendationResult>> dimensionProducts(@RequestBody Map<String, Object> params) {
|
|
|
|
|
+ String dimensionCode = (String) params.get("dimensionCode");
|
|
|
|
|
+ Long familyId = params.get("familyId") instanceof Number
|
|
|
|
|
+ ? ((Number) params.get("familyId")).longValue() : null;
|
|
|
|
|
+ Integer limit = params.get("limit") instanceof Number
|
|
|
|
|
+ ? ((Number) params.get("limit")).intValue() : 20;
|
|
|
|
|
+
|
|
|
|
|
+ List<RecommendationResult> results = recommendationService.getDimensionProducts(
|
|
|
|
|
+ dimensionCode, familyId, limit);
|
|
|
|
|
+ return Result.success(results);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Operation(summary = "查询复购提醒列表")
|
|
|
|
|
+@PostMapping("/repurchase-reminders")
|
|
|
|
|
+public Result<List<RecommendationResult>> repurchaseReminders(HttpServletRequest request) {
|
|
|
|
|
+ Long userId = (Long) request.getAttribute("userId");
|
|
|
|
|
+ List<RecommendationResult> results = recommendationService.getRepurchaseReminders(userId);
|
|
|
|
|
+ return Result.success(results);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Operation(summary = "复购提醒点击记录")
|
|
|
|
|
+@PostMapping("/repurchase-reminder/{id}/click")
|
|
|
|
|
+public Result<Boolean> repurchaseClick(@PathVariable Long id) {
|
|
|
|
|
+ recommendationService.markRepurchaseClicked(id);
|
|
|
|
|
+ return Result.success(true);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+需要的 import:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import org.springframework.web.bind.annotation.PathVariable;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**注意**: `/repurchase-reminders` 端点的前端 `api.js` 中定义为 GET 方法,但项目规范统一使用 `@PostMapping`。修改前端 `api.js` 同步为 POST:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+// cfc-frontend/utils/api.js 第 1740-1741 行
|
|
|
|
|
+export const getRepurchaseReminders = (params) => {
|
|
|
|
|
+ return request('/api/recommend/repurchase-reminders', 'POST', params)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 3: 编译验证
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+cd cfc-backend && mvn clean compile
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+预期:BUILD SUCCESS,无新增编译错误
|
|
|
|
|
+
|
|
|
|
|
+### Step 4: 提交
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+git add ... && git commit -m "feat: 维度商品推荐 + 复购提醒端点实现"
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 2: 复购提醒定时任务
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/task/RepurchaseReminderScheduledTask.java`
|
|
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/RecommendationService.java` (新增生成提醒方法)
|
|
|
|
|
+
|
|
|
|
|
+### Step 1: RecommendationService 新增生成复购提醒方法
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 每日定时任务:为符合条件的用户生成复购提醒
|
|
|
|
|
+ *
|
|
|
|
|
+ * 逻辑:查询最近N天内有购买记录的商品,按 repurchase_reminder_config 的
|
|
|
|
|
+ * reminder_days 配置判断是否需要提醒,生成提醒记录。
|
|
|
|
|
+ */
|
|
|
|
|
+public void generateDailyRepurchaseReminders() {
|
|
|
|
|
+ // 1. 获取所有启用的复购提醒配置
|
|
|
|
|
+ List<RepurchaseReminderConfig> configs = repurchaseReminderConfigMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<RepurchaseReminderConfig>()
|
|
|
|
|
+ .eq(RepurchaseReminderConfig::getEnabled, 1)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (configs.isEmpty()) return;
|
|
|
|
|
+
|
|
|
|
|
+ for (RepurchaseReminderConfig config : configs) {
|
|
|
|
|
+ int reminderDays = config.getReminderDays() != null ? config.getReminderDays() : 30;
|
|
|
|
|
+ Date since = new Date(System.currentTimeMillis() - reminderDays * 24L * 60 * 60 * 1000);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询该类目下已送达且超过提醒天数的商品订单
|
|
|
|
|
+ List<ProductOrder> orders = productOrderMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<ProductOrder>()
|
|
|
|
|
+ .eq(ProductOrder::getStatus, "received")
|
|
|
|
|
+ .ge(ProductOrder::getCreatedAt, since)
|
|
|
|
|
+ .last("LIMIT 500")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ for (ProductOrder order : orders) {
|
|
|
|
|
+ // 3. 检查对应商品是否匹配提醒配置类目
|
|
|
|
|
+ Product product = productMapper.selectById(order.getProductId());
|
|
|
|
|
+ if (product == null) continue;
|
|
|
|
|
+ if (config.getProductCategory() != null && !config.getProductCategory().isEmpty()
|
|
|
|
|
+ && !config.getProductCategory().equals(product.getCategoryId() != null
|
|
|
|
|
+ ? String.valueOf(product.getCategoryId()) : "")) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 检查是否已生成过提醒(去重)
|
|
|
|
|
+ int existingCount = repurchaseReminderRecordMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<RepurchaseReminderRecord>()
|
|
|
|
|
+ .eq(RepurchaseReminderRecord::getUserId, order.getBuyerId())
|
|
|
|
|
+ .eq(RepurchaseReminderRecord::getProductId, order.getProductId())
|
|
|
|
|
+ .eq(RepurchaseReminderRecord::getPurchased, 0)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (existingCount > 0) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 生成提醒记录
|
|
|
|
|
+ RepurchaseReminderRecord record = new RepurchaseReminderRecord();
|
|
|
|
|
+ record.setUserId(order.getBuyerId());
|
|
|
|
|
+ record.setProductId(order.getProductId());
|
|
|
|
|
+ record.setOrderId(order.getId());
|
|
|
|
|
+ record.setReminderDays(reminderDays);
|
|
|
|
|
+ record.setSentAt(new Date());
|
|
|
|
|
+ record.setClicked(0);
|
|
|
|
|
+ record.setPurchased(0);
|
|
|
|
|
+ repurchaseReminderRecordMapper.insert(record);
|
|
|
|
|
+ log.info("生成复购提醒: userId={}, productId={}, orderId={}",
|
|
|
|
|
+ order.getBuyerId(), order.getProductId(), order.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+需要的 import:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
|
|
+import com.etotem.cfc.entity.RepurchaseReminderConfig;
|
|
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.RepurchaseReminderConfigMapper;
|
|
|
|
|
+
|
|
|
|
|
+// 在 @Resource 区域新增:
|
|
|
|
|
+@Resource
|
|
|
|
|
+private RepurchaseReminderConfigMapper repurchaseReminderConfigMapper;
|
|
|
|
|
+
|
|
|
|
|
+@Resource
|
|
|
|
|
+private ProductOrderMapper productOrderMapper;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 2: 创建 RepurchaseReminderScheduledTask
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+package com.etotem.cfc.task;
|
|
|
|
|
+
|
|
|
|
|
+import com.etotem.cfc.service.RecommendationService;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Component
|
|
|
|
|
+public class RepurchaseReminderScheduledTask {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private RecommendationService recommendationService;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 每日凌晨2点执行复购提醒生成
|
|
|
|
|
+ */
|
|
|
|
|
+ @Scheduled(cron = "0 0 2 * * ?")
|
|
|
|
|
+ public void generateReminders() {
|
|
|
|
|
+ log.info("===== RepurchaseReminderScheduledTask 开始执行 =====");
|
|
|
|
|
+ try {
|
|
|
|
|
+ recommendationService.generateDailyRepurchaseReminders();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("复购提醒定时任务执行失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("===== RepurchaseReminderScheduledTask 执行完成 =====");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 3: 编译验证
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+cd cfc-backend && mvn clean compile
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+预期:BUILD SUCCESS
|
|
|
|
|
+
|
|
|
|
|
+### Step 4: 提交
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+git add ... && git commit -m "feat: 复购提醒定时任务 (每日2点)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 3: 前端 api.js GET→POST 修正
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `cfc-frontend/utils/api.js:1740-1741`
|
|
|
|
|
+
|
|
|
|
|
+### Step 1: 修改请求方法
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+// 修改前:
|
|
|
|
|
+export const getRepurchaseReminders = (params) => {
|
|
|
|
|
+ return request('/api/recommend/repurchase-reminders', 'GET', params)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 修改后:
|
|
|
|
|
+export const getRepurchaseReminders = (params) => {
|
|
|
|
|
+ return request('/api/recommend/repurchase-reminders', 'POST', params)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+同时确认 `clickRepurchaseReminder` 已正确调用。`DimensionProductList.vue` 第31行已 import,但第71-78行的 `goProduct()` 方法中注释掉了点击追踪调用。解除注释:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+// DimensionProductList.vue goProduct() 方法修改:
|
|
|
|
|
+goProduct: function(product) {
|
|
|
|
|
+ if (!product || !product.id) return
|
|
|
|
|
+ // 调用点击追踪
|
|
|
|
|
+ clickRepurchaseReminder(product.id).catch(function() {})
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/discover/product-detail/product-detail?id=' + product.id + '&from=dimension'
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Step 2: 提交
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+git add ... && git commit -m "fix: 复购提醒API GET→POST + 点击追踪调用"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Task 4: DAN 测评商城化 Phase 2-5 计划框架
|
|
|
|
|
+
|
|
|
|
|
+**范围过大,本次不实施**。创建独立计划文档标记待办:
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `docs/superpowers/plans/2026-07-13-dan-assessment-phase2-5.md`
|
|
|
|
|
+
|
|
|
|
|
+内容:引用 `docs/assessment-market-plan.md` 的 Phase 2-5 设计,拆分为独立的计划文档。
|
|
|
|
|
+
|
|
|
|
|
+按 `assessment-market-plan.md` 的 5 阶段依赖:
|
|
|
|
|
+1. 阶段二 (P0): 测评额度表 + 采购执行解耦 → `AssessmentQuotaService`
|
|
|
|
|
+2. 阶段三 (P1): 报告双通道 (规划师录入 + 家长上传 + API获取)
|
|
|
|
|
+3. 阶段四 (P1): 方案生成引擎 (AI + 维度配置)
|
|
|
|
|
+4. 阶段五 (P1): 审核→确认→执行闭环
|
|
|
|
|
+
|
|
|
|
|
+在计划文档中标记为"待启动",后续根据业务优先级决定实施顺序。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 验证标准
|
|
|
|
|
+
|
|
|
|
|
+| 验证项 | 方法 |
|
|
|
|
|
+|--------|------|
|
|
|
|
|
+| `/api/recommend/dimension-products` 可用 | POST `{"dimensionCode":"body","limit":6}` → 返回按 matchScore 排序的商品列表 |
|
|
|
|
|
+| `/api/recommend/repurchase-reminders` 可用 | POST JWT认证 → 返回用户复购提醒列表 |
|
|
|
|
|
+| 复购定时任务可触发 | 启动应用 → 观察日志 "RepurchaseReminderScheduledTask 开始执行" |
|
|
|
|
|
+| `mvn clean compile` BUILD SUCCESS | 无新增编译错误 |
|