فهرست منبع

docs: 健康启航计划实现计划

Sisyphus 3 هفته پیش
والد
کامیت
0aabf8862f
1فایلهای تغییر یافته به همراه1125 افزوده شده و 0 حذف شده
  1. 1125 0
      docs/superpowers/plans/2026-08-22-health-start-plan.md

+ 1125 - 0
docs/superpowers/plans/2026-08-22-health-start-plan.md

@@ -0,0 +1,1125 @@
+# 健康启航计划 实现计划
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 实现新用户付费转化产品"健康启航计划"——199元体验套餐,7天全功能体验+3周闯关任务,CF值激励,续费引导至家庭会员年费。
+
+**架构:** 新增购买订单表+挑战赛进度表;付费墙拦截在报告上传/方案生成接口;前端购买弹窗+续费页+闯关进度页;复用现有会员体系和CF值系统。
+
+**技术栈:** Java 8 + MyBatis-Plus、LangGraph Python (FastAPI)、uni-app Vue 2、Element UI
+
+---
+
+## 文件结构
+
+### 新建文件
+- `cfc-backend/src/main/resources/db/migration/V252__create_purchase_tables.sql`
+- `cfc-backend/src/main/java/com/etotem/cfc/entity/PurchaseOrder.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/entity/ChallengeProgress.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/mapper/PurchaseOrderMapper.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/mapper/ChallengeProgressMapper.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/service/PurchaseService.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/service/impl/PurchaseServiceImpl.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/service/ChallengeService.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/service/impl/ChallengeServiceImpl.java`
+- `cfc-backend/src/main/java/com/etotem/cfc/controller/PurchaseController.java`
+- `cfc-langgraph/app/api/purchase.py`
+- `cfc-frontend/pages/purchase/index.vue` (购买页)
+- `cfc-frontend/pages/purchase/renewal.vue` (续费页)
+- `cfc-frontend/pages/growth/challenge-progress.vue` (闯关进度页)
+- `cfc-web/src/views/admin/PurchaseManagement.vue` (管理端)
+
+### 修改文件
+- `cfc-backend/src/main/resources/schema.sql` — 添加新表定义
+- `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` — 添加迁移
+- `cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java` — 添加付费墙拦截
+- `cfc-backend/src/main/java/com/etotem/cfc/controller/HealthPlanController.java` — 添加付费墙拦截
+- `cfc-frontend/utils/api.js` — 新增购买相关API
+- `cfc-frontend/pages.json` — 注册新页面
+
+---
+
+## 任务分解
+
+### 任务1:数据库迁移 — 创建购买和闯关表
+
+**文件:**
+- 新建:`cfc-backend/src/main/resources/db/migration/V252__create_purchase_tables.sql`
+- 修改:`cfc-backend/src/main/resources/schema.sql`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`
+
+- [ ] **步骤 1:编写迁移SQL**
+
+```sql
+-- 健康启航计划购买订单表
+CREATE TABLE IF NOT EXISTS purchase_orders (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单号',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    amount INT NOT NULL COMMENT '支付金额(分)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/cancelled/refunded',
+    payment_method VARCHAR(20) DEFAULT NULL COMMENT '支付方式: wechat/alipay',
+    transaction_id VARCHAR(64) DEFAULT NULL COMMENT '支付流水号',
+    start_date DATETIME NOT NULL COMMENT '体验开始时间',
+    end_date DATETIME NOT NULL COMMENT '体验结束时间',
+    is_early_renewal TINYINT DEFAULT 0 COMMENT '是否早期续费(7天内)',
+    cf_reward_given INT DEFAULT 0 COMMENT '已发放CF值',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    paid_at DATETIME DEFAULT NULL,
+    INDEX idx_user_id (user_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康启航计划购买订单';
+
+-- 闯关任务进度表
+CREATE TABLE IF NOT EXISTS challenge_progress (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    purchase_id BIGINT NOT NULL COMMENT '购买订单ID',
+    member_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    week_num INT NOT NULL COMMENT '周次: 1/2/3',
+    task_type VARCHAR(50) NOT NULL COMMENT '任务类型: fixed/personalized/streak',
+    task_key VARCHAR(100) NOT NULL COMMENT '任务标识',
+    task_title VARCHAR(255) NOT NULL COMMENT '任务标题',
+    target_value INT DEFAULT 0 COMMENT '目标值',
+    current_value INT DEFAULT 0 COMMENT '当前值',
+    reward_cf INT DEFAULT 0 COMMENT '奖励CF值',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/in_progress/completed',
+    completed_at DATETIME DEFAULT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_purchase_id (purchase_id),
+    INDEX idx_member_id (member_id),
+    UNIQUE KEY uk_member_task (member_id, task_key, week_num)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='闯关任务进度';
+```
+
+- [ ] **步骤 2:同步到 schema.sql**
+
+在 `schema.sql` 末尾追加相同的建表语句。
+
+- [ ] **步骤 3:添加迁移到 DatabaseInitializer**
+
+```java
+// 迁移252: 创建健康启航计划购买和闯关表
+try {
+    jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS purchase_orders (" +
+            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+            "order_no VARCHAR(64) NOT NULL UNIQUE, " +
+            "user_id BIGINT NOT NULL, " +
+            "family_id BIGINT NOT NULL, " +
+            "amount INT NOT NULL, " +
+            "status VARCHAR(20) DEFAULT 'pending', " +
+            "payment_method VARCHAR(20), " +
+            "transaction_id VARCHAR(64), " +
+            "start_date DATETIME NOT NULL, " +
+            "end_date DATETIME NOT NULL, " +
+            "is_early_renewal TINYINT DEFAULT 0, " +
+            "cf_reward_given INT DEFAULT 0, " +
+            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+            "paid_at DATETIME, " +
+            "INDEX idx_user_id (user_id), " +
+            "INDEX idx_family_id (family_id), " +
+            "INDEX idx_order_no (order_no)" +
+            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+    log.info("已创建purchase_orders表");
+} catch (Exception e) {
+    log.warn("创建purchase_orders表可能已存在: {}", e.getMessage());
+}
+
+try {
+    jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS challenge_progress (" +
+            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+            "purchase_id BIGINT NOT NULL, " +
+            "member_id BIGINT NOT NULL, " +
+            "week_num INT NOT NULL, " +
+            "task_type VARCHAR(50) NOT NULL, " +
+            "task_key VARCHAR(100) NOT NULL, " +
+            "task_title VARCHAR(255) NOT NULL, " +
+            "target_value INT DEFAULT 0, " +
+            "current_value INT DEFAULT 0, " +
+            "reward_cf INT DEFAULT 0, " +
+            "status VARCHAR(20) DEFAULT 'pending', " +
+            "completed_at DATETIME, " +
+            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+            "INDEX idx_purchase_id (purchase_id), " +
+            "INDEX idx_member_id (member_id), " +
+            "UNIQUE KEY uk_member_task (member_id, task_key, week_num)" +
+            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+    log.info("已创建challenge_progress表");
+} catch (Exception e) {
+    log.warn("创建challenge_progress表可能已存在: {}", e.getMessage());
+}
+```
+
+- [ ] **步骤 4:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+预期:BUILD SUCCESS
+
+- [ ] **步骤 5:Commit**
+
+```bash
+git add cfc-backend/src/main/resources/db/migration/V252__create_purchase_tables.sql \
+       cfc-backend/src/main/resources/schema.sql \
+       cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
+git commit -m "feat(purchase): 创建健康启航计划购买订单表和闯关进度表"
+```
+
+---
+
+### 任务2:Java实体类和Mapper
+
+**文件:**
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/entity/PurchaseOrder.java`
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/entity/ChallengeProgress.java`
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/mapper/PurchaseOrderMapper.java`
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/mapper/ChallengeProgressMapper.java`
+
+- [ ] **步骤 1:创建 PurchaseOrder 实体**
+
+```java
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("purchase_orders")
+public class PurchaseOrder implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String orderNo;
+    private Long userId;
+    private Long familyId;
+    private Integer amount;
+    private String status;
+    private String paymentMethod;
+    private String transactionId;
+    private Date startDate;
+    private Date endDate;
+    private Integer isEarlyRenewal;
+    private Integer cfRewardGiven;
+    private Date createdAt;
+    private Date paidAt;
+}
+```
+
+- [ ] **步骤 2:创建 ChallengeProgress 实体**
+
+```java
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("challenge_progress")
+public class ChallengeProgress implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long purchaseId;
+    private Long memberId;
+    private Integer weekNum;
+    private String taskType;
+    private String taskKey;
+    private String taskTitle;
+    private Integer targetValue;
+    private Integer currentValue;
+    private Integer rewardCf;
+    private String status;
+    private Date completedAt;
+    private Date createdAt;
+    private Date updatedAt;
+}
+```
+
+- [ ] **步骤 3:创建 Mapper 接口**
+
+```java
+// PurchaseOrderMapper.java
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.PurchaseOrder;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PurchaseOrderMapper extends BaseMapper<PurchaseOrder> {
+}
+
+// ChallengeProgressMapper.java
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.ChallengeProgress;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ChallengeProgressMapper extends BaseMapper<ChallengeProgress> {
+}
+```
+
+- [ ] **步骤 4:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+- [ ] **步骤 5:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/entity/PurchaseOrder.java \
+       cfc-backend/src/main/java/com/etotem/cfc/entity/ChallengeProgress.java \
+       cfc-backend/src/main/java/com/etotem/cfc/mapper/PurchaseOrderMapper.java \
+       cfc-backend/src/main/java/com/etotem/cfc/mapper/ChallengeProgressMapper.java
+git commit -m "feat(purchase): 创建购买订单和闯关进度实体类及Mapper"
+```
+
+---
+
+### 任务3:购买服务 — 核心业务逻辑
+
+**文件:**
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/service/PurchaseService.java`
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/service/impl/PurchaseServiceImpl.java`
+
+- [ ] **步骤 1:创建 Service 接口**
+
+```java
+package com.etotem.cfc.service;
+
+import java.util.Map;
+
+public interface PurchaseService {
+    /** 检查用户是否有有效购买 */
+    Map<String, Object> checkPurchase(Long userId);
+    
+    /** 创建购买订单 */
+    Map<String, Object> createOrder(Long userId, Long familyId);
+    
+    /** 确认支付 */
+    Map<String, Object> confirmPayment(String orderNo, String paymentMethod, String transactionId);
+    
+    /** 获取续费信息 */
+    Map<String, Object> getRenewalInfo(Long userId);
+    
+    /** 处理续费(真实支付) */
+    Map<String, Object> processRenewal(Long userId, Long familyId, Integer amount);
+}
+```
+
+- [ ] **步骤 2:创建实现类**
+
+```java
+package com.etotem.cfc.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
+import com.etotem.cfc.service.PurchaseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+@Service
+public class PurchaseServiceImpl implements PurchaseService {
+
+    @Autowired private PurchaseOrderMapper orderMapper;
+    @Autowired private FamilyMembershipMapper membershipMapper;
+    @Autowired private FamilyMemberMapper memberMapper;
+    @Autowired private PointsLogMapper pointsLogMapper;
+
+    @Override
+    public Map<String, Object> checkPurchase(Long userId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 查询最新购买记录
+        LambdaQueryWrapper<PurchaseOrder> qw = new LambdaQueryWrapper<>();
+        qw.eq(PurchaseOrder::getUserId, userId)
+           .orderByDesc(PurchaseOrder::getCreatedAt)
+           .last("LIMIT 1");
+        PurchaseOrder order = orderMapper.selectOne(qw);
+        
+        if (order == null) {
+            result.put("hasPurchased", false);
+            result.put("daysRemaining", 0);
+            return result;
+        }
+        
+        boolean isActive = order.getStatus().equals("paid") 
+            && order.getEndDate().after(new Date());
+        
+        result.put("hasPurchased", true);
+        result.put("isActive", isActive);
+        result.put("orderId", order.getId());
+        result.put("orderNo", order.getOrderNo());
+        
+        if (isActive) {
+            long daysRemaining = java.time.temporal.ChronoUnit.DAYS.between(
+                LocalDateTime.now(), order.getEndDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
+            result.put("daysRemaining", Math.max(0, daysRemaining));
+            result.put("startDate", order.getStartDate());
+            result.put("endDate", order.getEndDate());
+            result.put("isEarlyRenewal", order.getIsEarlyRenewal());
+        } else {
+            result.put("daysRemaining", 0);
+        }
+        
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> createOrder(Long userId, Long familyId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 生成订单号
+        String orderNo = "HQ" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+        
+        // 创建订单
+        PurchaseOrder order = new PurchaseOrder();
+        order.setOrderNo(orderNo);
+        order.setUserId(userId);
+        order.setFamilyId(familyId);
+        order.setAmount(19900); // 199元
+        order.setStatus("pending");
+        order.setStartDate(new Date());
+        
+        // 7天体验期
+        LocalDateTime endDate = LocalDateTime.now().plusDays(7);
+        order.setEndDate(Date.from(endDate.atZone(java.time.ZoneId.systemDefault()).toInstant()));
+        
+        orderMapper.insert(order);
+        
+        result.put("success", true);
+        result.put("orderNo", orderNo);
+        result.put("amount", 19900);
+        result.put("daysRemaining", 7);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> confirmPayment(String orderNo, String paymentMethod, String transactionId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        LambdaQueryWrapper<PurchaseOrder> qw = new LambdaQueryWrapper<>();
+        qw.eq(PurchaseOrder::getOrderNo, orderNo);
+        PurchaseOrder order = orderMapper.selectOne(qw);
+        
+        if (order == null) {
+            result.put("success", false);
+            result.put("message", "订单不存在");
+            return result;
+        }
+        
+        if (!"pending".equals(order.getStatus())) {
+            result.put("success", false);
+            result.put("message", "订单状态异常");
+            return result;
+        }
+        
+        // 更新订单状态
+        order.setStatus("paid");
+        order.setPaymentMethod(paymentMethod);
+        order.setTransactionId(transactionId);
+        order.setPaidAt(new Date());
+        orderMapper.updateById(order);
+        
+        // 发放CF值奖励(基础奖励)
+        int cfReward = 300; // 基础CF值
+        order.setCfRewardGiven(cfReward);
+        
+        // 发放到家庭成员
+        FamilyMember member = memberMapper.selectById(order.getFamilyId());
+        if (member != null) {
+            member.setSystemPoints((member.getSystemPoints() != null ? member.getSystemPoints() : 0) + cfReward);
+            memberMapper.updateById(member);
+        }
+        
+        result.put("success", true);
+        result.put("orderId", order.getId());
+        result.put("cfReward", cfReward);
+        result.put("daysRemaining", 7);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getRenewalInfo(Long userId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 获取用户CF值余额
+        LambdaQueryWrapper<FamilyMember> qw = new LambdaQueryWrapper<>();
+        qw.select(FamilyMember::getSystemPoints)
+           .eq(FamilyMember::getUserId, userId)
+           .last("LIMIT 1");
+        FamilyMember member = memberMapper.selectOne(qw);
+        
+        int cfBalance = member != null && member.getSystemPoints() != null ? member.getSystemPoints() : 0;
+        
+        // 计算可抵扣金额(1 CF = 0.01元 = 1分)
+        int discount = Math.min(cfBalance, 19900); // 最多抵扣199元
+        
+        result.put("success", true);
+        result.put("cfBalance", cfBalance);
+        result.put("discount", discount);
+        result.put("payAmount", 199900 - discount); // 年费1999元 - 抵扣
+        result.put("discountPercent", cfBalance >= 19900 ? 100 : Math.round((double)discount / 19900 * 100));
+        
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> processRenewal(Long userId, Long familyId, Integer amount) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 检查是否已有购买记录
+        LambdaQueryWrapper<PurchaseOrder> qw = new LambdaQueryWrapper<>();
+        qw.eq(PurchaseOrder::getUserId, userId)
+           .eq(PurchaseOrder::getStatus, "paid")
+           .last("LIMIT 1");
+        PurchaseOrder existingOrder = orderMapper.selectOne(qw);
+        
+        if (existingOrder == null) {
+            result.put("success", false);
+            result.put("message", "无有效购买记录");
+            return result;
+        }
+        
+        // 标记为早期续费
+        existingOrder.setIsEarlyRenewal(1);
+        orderMapper.updateById(existingOrder);
+        
+        // 创建续费订单
+        String orderNo = "HR" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
+        PurchaseOrder renewalOrder = new PurchaseOrder();
+        renewalOrder.setOrderNo(orderNo);
+        renewalOrder.setUserId(userId);
+        renewalOrder.setFamilyId(familyId);
+        renewalOrder.setAmount(amount);
+        renewalOrder.setStatus("paid");
+        renewalOrder.setPaymentMethod("external");
+        renewalOrder.setStartDate(new Date());
+        LocalDateTime endDate = LocalDateTime.now().plusYears(1);
+        renewalOrder.setEndDate(Date.from(endDate.atZone(java.time.ZoneId.systemDefault()).toInstant()));
+        renewalOrder.setIsEarlyRenewal(1);
+        orderMapper.insert(renewalOrder);
+        
+        // 激活家庭会员年费
+        LambdaQueryWrapper<FamilyMembership> memQw = new LambdaQueryWrapper<>();
+        memQw.eq(FamilyMembership::getFamilyId, familyId);
+        FamilyMembership membership = membershipMapper.selectOne(memQw);
+        
+        if (membership == null) {
+            membership = new FamilyMembership();
+            membership.setFamilyId(familyId);
+            membership.setLevelCode("FAMILY");
+            membership.setStartDate(new Date());
+        }
+        membership.setEndDate(Date.from(endDate.atZone(java.time.ZoneId.systemDefault()).toInstant()));
+        membership.setPaymentStatus("paid");
+        membership.setOrderNo(orderNo);
+        membershipMapper.insert(membership);
+        
+        result.put("success", true);
+        result.put("orderNo", orderNo);
+        result.put("membershipValid", true);
+        return result;
+    }
+}
+```
+
+- [ ] **步骤 3:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+- [ ] **步骤 4:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/PurchaseService.java \
+       cfc-backend/src/main/java/com/etotem/cfc/service/impl/PurchaseServiceImpl.java
+git commit -m "feat(purchase): 实现购买服务 — 订单创建、支付确认、续费处理"
+```
+
+---
+
+### 任务4:闯关服务 — 任务管理和进度追踪
+
+**文件:**
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/service/ChallengeService.java`
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/service/impl/ChallengeServiceImpl.java`
+
+- [ ] **步骤 1:创建 Service 接口**
+
+```java
+package com.etotem.cfc.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ChallengeService {
+    /** 初始化闯关任务 */
+    void initChallengeTasks(Long purchaseId, Long memberId);
+    
+    /** 获取用户闯关进度 */
+    Map<String, Object> getChallengeProgress(Long memberId);
+    
+    /** 更新任务进度 */
+    void updateTaskProgress(Long memberId, String taskKey, int currentValue);
+    
+    /** 检查并完成任务 */
+    Map<String, Object> checkAndCompleteTask(Long memberId, String taskKey);
+}
+```
+
+- [ ] **步骤 2:创建实现类**
+
+```java
+package com.etotem.cfc.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
+import com.etotem.cfc.service.ChallengeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class ChallengeServiceImpl implements ChallengeService {
+
+    @Autowired private ChallengeProgressMapper progressMapper;
+    @Autowired private FamilyMemberMapper memberMapper;
+    @Autowired private HealthSleepRecordMapper sleepMapper;
+    @Autowired private HealthExerciseRecordMapper exerciseMapper;
+    @Autowired private HealthMealRecordMapper mealMapper;
+    @Autowired private EmotionCheckinMapper emotionMapper;
+
+    @Override
+    public void initChallengeTasks(Long purchaseId, Long memberId) {
+        // 第1周任务
+        List<Map<String, Object>> week1Tasks = Arrays.asList(
+            CreateMap("first_report_upload", "首次报告上传", 1, 100),
+            CreateMap("streak_3days", "连续3天打卡", 3, 100),
+            CreateMap("upload_1_report", "上传1份报告", 1, 60)
+        );
+        
+        // 第2周任务
+        List<Map<String, Object>> week2Tasks = Arrays.asList(
+            CreateMap("upload_3_reports", "上传3份报告", 3, 100),
+            CreateMap("streak_7days", "连续7天打卡", 7, 100),
+            CreateMap("upload_3_reports_total", "累计上传3份报告", 3, 60)
+        );
+        
+        // 第3周任务
+        List<Map<String, Object>> week3Tasks = Arrays.asList(
+            CreateMap("complete_3_challenges", "完成3个挑战", 3, 100),
+            CreateMap("streak_21days", "连续21天打卡", 21, 100),
+            CreateMap("upload_5_reports", "上传5份报告", 5, 60)
+        );
+        
+        insertTasks(purchaseId, memberId, 1, week1Tasks);
+        insertTasks(purchaseId, memberId, 2, week2Tasks);
+        insertTasks(purchaseId, memberId, 3, week3Tasks);
+    }
+    
+    private Map<String, Object> CreateMap(String key, String title, int target, int reward) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("taskKey", key);
+        map.put("taskTitle", title);
+        map.put("targetValue", target);
+        map.put("rewardCf", reward);
+        return map;
+    }
+    
+    private void insertTasks(Long purchaseId, Long memberId, int weekNum, List<Map<String, Object>> tasks) {
+        for (Map<String, Object> task : tasks) {
+            ChallengeProgress progress = new ChallengeProgress();
+            progress.setPurchaseId(purchaseId);
+            progress.setMemberId(memberId);
+            progress.setWeekNum(weekNum);
+            progress.setTaskType("fixed");
+            progress.setTaskKey((String) task.get("taskKey"));
+            progress.setTaskTitle((String) task.get("taskTitle"));
+            progress.setTargetValue((Integer) task.get("targetValue"));
+            progress.setCurrentValue(0);
+            progress.setRewardCf((Integer) task.get("rewardCf"));
+            progress.setStatus("pending");
+            progressMapper.insert(progress);
+        }
+    }
+
+    @Override
+    public Map<String, Object> getChallengeProgress(Long memberId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 查询所有任务进度
+        LambdaQueryWrapper<ChallengeProgress> qw = new LambdaQueryWrapper<>();
+        qw.eq(ChallengeProgress::getMemberId, memberId)
+           .orderByAsc(ChallengeProgress::getWeekNum);
+        List<ChallengeProgress> progress = progressMapper.selectList(qw);
+        
+        // 按周分组
+        Map<Integer, List<ChallengeProgress>> byWeek = new LinkedHashMap<>();
+        for (ChallengeProgress p : progress) {
+            byWeek.computeIfAbsent(p.getWeekNum(), k -> new ArrayList<>()).add(p);
+        }
+        
+        // 计算总CF奖励和已完成CF
+        int totalReward = 0;
+        int completedReward = 0;
+        int completedCount = 0;
+        
+        for (ChallengeProgress p : progress) {
+            totalReward += p.getRewardCf();
+            if ("completed".equals(p.getStatus())) {
+                completedReward += p.getRewardCf();
+                completedCount++;
+            }
+        }
+        
+        result.put("success", true);
+        result.put("tasks", progress);
+        result.put("byWeek", byWeek);
+        result.put("totalReward", totalReward);
+        result.put("completedReward", completedReward);
+        result.put("completedCount", completedCount);
+        result.put("totalCount", progress.size());
+        result.put("progressPercent", progress.size() > 0 ? Math.round((double)completedCount / progress.size() * 100) : 0);
+        
+        return result;
+    }
+
+    @Override
+    public void updateTaskProgress(Long memberId, String taskKey, int currentValue) {
+        LambdaQueryWrapper<ChallengeProgress> qw = new LambdaQueryWrapper<>();
+        qw.eq(ChallengeProgress::getMemberId, memberId)
+           .eq(ChallengeProgress::getTaskKey, taskKey)
+           .eq(ChallengeProgress::getStatus, "pending");
+        
+        ChallengeProgress progress = progressMapper.selectOne(qw);
+        if (progress != null) {
+            progress.setCurrentValue(currentValue);
+            progressMapper.updateById(progress);
+            
+            // 检查是否完成
+            if (currentValue >= progress.getTargetValue()) {
+                completeTask(progress);
+            }
+        }
+    }
+    
+    private void completeTask(ChallengeProgress progress) {
+        progress.setStatus("completed");
+        progress.setCompletedAt(new java.util.Date());
+        progressMapper.updateById(progress);
+        
+        // 发放CF值
+        FamilyMember member = memberMapper.selectById(progress.getMemberId());
+        if (member != null) {
+            member.setSystemPoints((member.getSystemPoints() != null ? member.getSystemPoints() : 0) + progress.getRewardCf());
+            memberMapper.updateById(member);
+        }
+    }
+
+    @Override
+    public Map<String, Object> checkAndCompleteTask(Long memberId, String taskKey) {
+        // 查询任务
+        LambdaQueryWrapper<ChallengeProgress> qw = new LambdaQueryWrapper<>();
+        qw.eq(ChallengeProgress::getMemberId, memberId)
+           .eq(ChallengeProgress::getTaskKey, taskKey);
+        ChallengeProgress progress = progressMapper.selectOne(qw);
+        
+        if (progress == null) {
+            return Map.of("success", false, "message", "任务不存在");
+        }
+        
+        if ("completed".equals(progress.getStatus())) {
+            return Map.of("success", true, "alreadyCompleted", true);
+        }
+        
+        // 检查当前进度
+        int currentValue = calculateCurrentValue(memberId, taskKey);
+        progress.setCurrentValue(currentValue);
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+        result.put("currentValue", currentValue);
+        result.put("targetValue", progress.getTargetValue());
+        result.put("isCompleted", currentValue >= progress.getTargetValue());
+        
+        if (currentValue >= progress.getTargetValue()) {
+            completeTask(progress);
+            result.put("rewardCf", progress.getRewardCf());
+            result.put("message", "任务完成!获得 " + progress.getRewardCf() + " CF值");
+        }
+        
+        return result;
+    }
+    
+    private int calculateCurrentValue(Long memberId, String taskKey) {
+        // 根据任务类型计算当前值
+        switch (taskKey) {
+            case "first_report_upload":
+                // 查询是否有报告上传记录
+                return 1; // 简化:假设已上传
+            case "streak_3days":
+            case "streak_7days":
+            case "streak_21days":
+                // 查询连续打卡天数
+                return getStreakDays(memberId);
+            case "upload_1_report":
+            case "upload_3_reports":
+            case "upload_5_reports":
+                // 查询报告上传数量
+                return getReportCount(memberId);
+            default:
+                return 0;
+        }
+    }
+    
+    private int getStreakDays(Long memberId) {
+        // 简化实现:返回实际连续打卡天数
+        return 0; // 需要从打卡记录计算
+    }
+    
+    private int getReportCount(Long memberId) {
+        // 简化实现:返回报告数量
+        return 0; // 需要从报告记录计算
+    }
+}
+```
+
+- [ ] **步骤 3:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+- [ ] **步骤 4:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/ChallengeService.java \
+       cfc-backend/src/main/java/com/etotem/cfc/service/impl/ChallengeServiceImpl.java
+git commit -m "feat(purchase): 实现闯关服务 — 任务初始化、进度追踪、CF奖励发放"
+```
+
+---
+
+### 任务5:控制器 — API接口
+
+**文件:**
+- 新建:`cfc-backend/src/main/java/com/etotem/cfc/controller/PurchaseController.java`
+
+- [ ] **步骤 1:创建控制器**
+
+```java
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.service.PurchaseService;
+import com.etotem.cfc.service.ChallengeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/purchase")
+public class PurchaseController {
+
+    @Autowired private PurchaseService purchaseService;
+    @Autowired private ChallengeService challengeService;
+
+    @PostMapping("/check")
+    public Result<Map<String, Object>> checkPurchase(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        Map<String, Object> result = purchaseService.checkPurchase(userId);
+        return Result.success(result);
+    }
+
+    @PostMapping("/create")
+    public Result<Map<String, Object>> createOrder(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        Long familyId = (Long) request.getAttribute("familyId");
+        Map<String, Object> result = purchaseService.createOrder(userId, familyId);
+        return Result.success(result);
+    }
+
+    @PostMapping("/confirm")
+    public Result<Map<String, Object>> confirmPayment(@RequestBody Map<String, Object> params, HttpServletRequest request) {
+        String orderNo = (String) params.get("orderNo");
+        String paymentMethod = (String) params.get("paymentMethod");
+        String transactionId = (String) params.get("transactionId");
+        Map<String, Object> result = purchaseService.confirmPayment(orderNo, paymentMethod, transactionId);
+        return Result.success(result);
+    }
+
+    @PostMapping("/renewal-info")
+    public Result<Map<String, Object>> getRenewalInfo(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        Map<String, Object> result = purchaseService.getRenewalInfo(userId);
+        return Result.success(result);
+    }
+
+    @PostMapping("/renew")
+    public Result<Map<String, Object>> renew(@RequestBody Map<String, Object> params, HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        Long familyId = (Long) request.getAttribute("familyId");
+        Integer amount = (Integer) params.get("amount");
+        Map<String, Object> result = purchaseService.processRenewal(userId, familyId, amount);
+        return Result.success(result);
+    }
+
+    @GetMapping("/challenge/progress")
+    public Result<Map<String, Object>> getChallengeProgress(@RequestParam Long memberId, HttpServletRequest request) {
+        Map<String, Object> result = challengeService.getChallengeProgress(memberId);
+        return Result.success(result);
+    }
+
+    @PostMapping("/challenge/update")
+    public Result<Void> updateChallengeProgress(@RequestBody Map<String, Object> params, HttpServletRequest request) {
+        Long memberId = (Long) params.get("memberId");
+        String taskKey = (String) params.get("taskKey");
+        int currentValue = ((Number) params.get("currentValue")).intValue();
+        challengeService.updateTaskProgress(memberId, taskKey, currentValue);
+        return Result.success(null);
+    }
+}
+```
+
+- [ ] **步骤 2:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+- [ ] **步骤 3:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/controller/PurchaseController.java
+git commit -m "feat(purchase): 添加购买API控制器"
+```
+
+---
+
+### 任务6:付费墙拦截
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java`
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/HealthPlanController.java`
+
+- [ ] **步骤 1:在 HealthReportController 中添加拦截**
+
+找到报告上传方法,在业务逻辑前添加:
+
+```java
+// 付费墙拦截
+@Autowired
+private com.etotem.cfc.service.PurchaseService purchaseService;
+
+// 在 upload 方法开头添加
+Long userId = (Long) request.getAttribute("userId");
+Map<String, Object> purchaseCheck = purchaseService.checkPurchase(userId);
+if (!Boolean.TRUE.equals(purchaseCheck.get("isActive"))) {
+    return Result.error("请先购买健康启航计划体验包");
+}
+```
+
+- [ ] **步骤 2:在 HealthPlanController 中添加拦截**
+
+找到方案生成方法,在业务逻辑前添加相同拦截逻辑。
+
+- [ ] **步骤 3:编译验证**
+
+```bash
+cd cfc-backend && mvn clean compile -q
+```
+
+- [ ] **步骤 4:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java \
+       cfc-backend/src/main/java/com/etotem/cfc/controller/HealthPlanController.java
+git commit -m "feat(purchase): 在报告和方案接口添加付费墙拦截"
+```
+
+---
+
+### 任务7:前端 — API封装和购买页
+
+**文件:**
+- 修改:`cfc-frontend/utils/api.js`
+- 新建:`cfc-frontend/pages/purchase/index.vue`
+
+- [ ] **步骤 1:新增 API 函数**
+
+在 `cfc-frontend/utils/api.js` 末尾追加:
+
+```javascript
+// 健康启航计划
+export function checkPurchase() {
+  return request('/api/purchase/check', 'POST', {})
+}
+
+export function createPurchaseOrder() {
+  return request('/api/purchase/create', 'POST', {})
+}
+
+export function confirmPurchase(data) {
+  return request('/api/purchase/confirm', 'POST', data)
+}
+
+export function getRenewalInfo() {
+  return request('/api/purchase/renewal-info', 'POST', {})
+}
+
+export function renewPurchase(data) {
+  return request('/api/purchase/renew', 'POST', data)
+}
+
+export function getChallengeProgress(memberId) {
+  return request('/api/purchase/challenge/progress?memberId=' + memberId, 'GET', {})
+}
+```
+
+- [ ] **步骤 2:创建购买页**
+
+创建 `cfc-frontend/pages/purchase/index.vue`,包含:
+- 权益对比展示
+- CF奖励计算器
+- 购买按钮(跳转支付)
+- 已有购买的进度展示
+
+- [ ] **步骤 3:Commit**
+
+```bash
+git add cfc-frontend/utils/api.js cfc-frontend/pages/purchase/index.vue
+git commit -m "feat(purchase): 前端购买页和API封装"
+```
+
+---
+
+### 任务8:前端 — 续费页和闯关进度页
+
+**文件:**
+- 新建:`cfc-frontend/pages/purchase/renewal.vue`
+- 新建:`cfc-frontend/pages/growth/challenge-progress.vue`
+
+- [ ] **步骤 1:创建续费页**
+
+展示:
+- 当前CF值余额
+- 可抵扣金额
+- 实付金额
+- 支付按钮(跳转外部支付)
+
+- [ ] **步骤 2:创建闯关进度页**
+
+展示:
+- 3周任务列表
+- 每个任务的完成进度
+- 已获得CF值
+- 连续打卡天数
+
+- [ ] **步骤 3:Commit**
+
+```bash
+git add cfc-frontend/pages/purchase/renewal.vue \
+       cfc-frontend/pages/growth/challenge-progress.vue
+git commit -m "feat(purchase): 续费页和闯关进度页"
+```
+
+---
+
+### 任务9:管理端 — 购买记录管理
+
+**文件:**
+- 新建:`cfc-web/src/views/admin/PurchaseManagement.vue`
+
+- [ ] **步骤 1:创建管理页面**
+
+功能:
+- 购买记录列表
+- 续费记录统计
+- CF值发放管理
+- 用户查询
+
+- [ ] **步骤 2:Commit**
+
+```bash
+git add cfc-web/src/views/admin/PurchaseManagement.vue
+git commit -m "feat(purchase): Web管理端购买记录管理"
+```
+
+---
+
+### 任务10:联调测试与部署
+
+- [ ] **步骤 1:数据库迁移执行**
+
+```bash
+mysql -h 192.168.16.251 -u zxyj -p'zxyj@123' zxyj < cfc-backend/src/main/resources/db/migration/V252__create_purchase_tables.sql
+```
+
+- [ ] **步骤 2:启动后端测试API**
+
+```bash
+cd cfc-backend && mvn spring-boot:run
+# 测试购买、续费、进度查询等接口
+```
+
+- [ ] **步骤 3:小程序联调**
+
+在微信开发者工具中测试:
+- 购买流程
+- 付费墙拦截
+- 续费流程
+- 闯关进度显示
+
+- [ ] **步骤 4:修复bug并提交**
+
+```bash
+git add -A
+git commit -m "fix(purchase): 联调测试修复"
+git push origin cfclub
+```
+
+---
+
+## 注意事项
+
+1. **支付安全**:真实支付需要对接微信支付/支付宝,此处先实现框架,实际支付回调需对接公司支付系统
+2. **并发控制**:购买订单创建需要加分布式锁,防止重复购买
+3. **数据一致性**:CF值发放需要事务保证,避免发放失败
+4. **测试环境**:先在内网测试环境验证完整流程,再上线生产
+
+## 验收标准
+
+- [ ] 新用户首次上传报告时弹出购买提示
+- [ ] 购买成功后7天内可无限使用报告上传和方案生成
+- [ ] 第8天开始功能受限,提示续费
+- [ ] 续费页面正确显示CF值抵扣金额
+- [ ] 闯关任务正确初始化并追踪进度
+- [ ] 完成任务后CF值正确发放
+- [ ] 管理端可查看购买记录和统计数据