Просмотр исходного кода

plan: BASIC体验会员实施计划(9.9元/月)

E2E Test Bot 1 месяц назад
Родитель
Сommit
c6788375a0
1 измененных файлов с 1680 добавлено и 0 удалено
  1. 1680 0
      docs/superpowers/plans/2026-09-10-basic-membership-plan.md

+ 1680 - 0
docs/superpowers/plans/2026-09-10-basic-membership-plan.md

@@ -0,0 +1,1680 @@
+# BASIC 体验会员实施计划
+
+> **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 (`- [ ]`) syntax for tracking.
+
+**Goal:** 在现有会员体系(FREE→FAMILY→PREMIUM→PROVIDER)中新增 BASIC 等级,实现 9.9 元/月体验会员的付费开通、配额扣减、软/硬引导升级链路。
+
+**Architecture:** 基于现有 `MembershipLevel` / `FamilyMembership` / `TrialMembership` 三层会员记录模型,新增 `basic_quota_usage` 表管理月度配额。`BasicQuotaService` 作为独立 Service 承载配额逻辑,在 4 个业务入口(AI 对话、报告解读、测评预约、报告上传)插入配额检查。定时任务复用 `MembershipScheduledTasks` 扩展模式。
+
+**Tech Stack:** Spring Boot 2.7.18, MyBatis-Plus, Java 8, Maven, 微信小程序 Vue2
+
+**Spec:** `docs/superpowers/specs/2026-09-10-basic-membership-design.md`
+
+## Global Constraints
+
+- 所有接口统一 `@PostMapping`,禁止 `@GetMapping`/`@PutMapping`/`@DeleteMapping`
+- ORM 使用 MyBatis-Plus `@TableName` + `@TableId(type = IdType.AUTO)`
+- DI 使用 `@Resource`,字段名必须与类型默认 Bean Name 一致
+- 响应统一 `Result<T>`(code/message/data)
+- 禁用 `?.`(用 `&&` 替代)、禁用 CSS Grid(用 flexbox)、禁用 `:key` 表达式
+- 前端禁止直接 `new Date(string)`,用 `utils/format.js` 的 `parseDate()`
+- 所有迁移幂等(try-catch 忽略已存在异常)
+- `mvn clean compile` 是唯一验证方式(编译通过即视为验证)
+
+---
+
+## 文件结构
+
+| 文件路径 | 类型 | 职责 |
+|----------|------|------|
+| `entity/BasicQuotaUsage.java` | Create | BASIC 配额实体 |
+| `mapper/BasicQuotaUsageMapper.java` | Create | 配额 Mapper 接口 |
+| `dto/QuotaStatusDTO.java` | Create | 配额查询响应 DTO |
+| `dto/QuotaConsumeResult.java` | Create | 配额扣减结果 DTO |
+| `service/BasicQuotaService.java` | Create | 配额核心 Service(查询/扣减/刷新) |
+| `controller/BasicQuotaController.java` | Create | 配额查询 + 手动重置接口 |
+| `task/BasicQuotaScheduledTasks.java` | Create | 配额刷新 + 到期提醒定时任务 |
+| `service/MembershipService.java` | Modify | `canUseFeature()` 加 BASIC 分支;`downgradeExpiredMembers()` 支持 BASIC |
+| `service/AiGateway.java` | Modify | `chat()` 入口插入 BASIC 配额扣减 |
+| `service/ReportSummaryService.java` | Modify | `parseReport()` 入口插入配额扣减 |
+| `service/AssessmentAppointmentService.java` | Modify | `create()` 入口插入硬门禁配额检查 |
+| `service/DanReportUploadService.java` | Modify | `upload()` 入口插入配额检查 |
+| `service/ActivationQuotaService.java` | Create | 支付成功后初始化 4 类配额 |
+| `resources/schema.sql` | Modify | 新增 `basic_quota_usage` 表定义 + BASIC seed 数据 |
+| `config/DatabaseInitializer.java` | Modify | 新增 BASIC 等级 / 权益 / 表迁移 |
+| `cfc-frontend/pages/profile/membership/index.vue` | Modify | 展示 BASIC 权益卡片 |
+| `cfc-frontend/pages/profile/upgrade/index.vue` | Modify | 新增 9.9 元月付选项 |
+| `cfc-frontend/components/QuotaProgressBar.vue` | Create | 配额进度条组件 |
+| `cfc-frontend/components/UpgradeGuideModal.vue` | Create | 统一软/硬引导弹窗组件 |
+| `cfc-frontend/pages/ai-chat/index.vue` | Modify | 显示剩余次数 + 超限弹窗 |
+| `cfc-frontend/pages/assessment/appointment/index.vue` | Modify | 显示剩余次数 + 硬门禁弹窗 |
+| `cfc-frontend/pages/health/report-list.vue` | Modify | 显示剩余次数 + 软引导 |
+
+---
+
+## Phase 概述
+
+| Phase | 天数 | 范围 | 里程碑 |
+|-------|------|------|--------|
+| P0 核心链路 | 3 天 | DB 迁移 + BASIC 等级 + 支付开通 + 基础权限 | 9.9 元支付成功,BASIC 生效,能进沙盘/AI 对话 |
+| P1 配额系统 | 2 天 | `BasicQuotaService` + 4 个业务入口扣减 + 定时刷新 | 月度配额正确扣减/刷新,超限引导生效 |
+| P2 前端展示 | 2 天 | 权益页/开通页/进度条/弹窗 | 免费用户可见 BASIC,付费后正确显示剩余次数 |
+| P3 运营配套 | 2 天 | 定时提醒 + 管理端配额看板 + 手动重置接口 | 到期提醒自动发送,看板可查 |
+
+---
+
+## Task 1: 数据库迁移 — 新增表 + BASIC 种子数据
+
+**Files:**
+- Modify: `cfc-backend/src/main/resources/schema.sql` (末尾追加)
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` (末尾 `runMigrations()` 追加)
+- Test: 无单元测试(迁移幂等,`mvn compile` 验证)
+
+**Interfaces:**
+- Consumes: 无(数据库级操作)
+- Produces: `basic_quota_usage` 表;`membership_levels` 新增 BASIC 记录;`membership_benefits` 新增 5 条 BASIC 权益记录
+
+- [ ] **Step 1: schema.sql 末尾追加基本表定义 + seed 数据**
+
+在 `schema.sql` 末尾(OFFSET 5513 之后)追加以下内容:
+
+```sql
+-- ============================================================
+-- 新增 BASIC 体验会员等级种子数据(迁移150)
+-- ============================================================
+INSERT IGNORE INTO membership_levels 
+(level_code, level_name, level_desc, price_monthly, features, max_children, max_tasks_per_day) 
+VALUES (
+    'BASIC', 
+    '体验会员', 
+    '9.9元/月体验版,解锁核心功能,限次使用AI与报告解读',
+    990,
+    '["free_activities","free_courses","discount_purchase","invite_family","mascot_ai_lite","ai_report_reading_lite","assessment_quota_lite","health_report_upload_lite","growth_plan_lite","task_template_lite"]',
+    3,
+    3
+);
+
+-- ============================================================
+-- 新增 BASIC 会员权益种子数据(迁移151)
+-- ============================================================
+INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES
+('BASIC', 'AI_CHAT_LITE', 'AI健康顾问(限3次/月)', '每月3次AI健康顾问问答', '🤖', '#DBEAFE', 1),
+('BASIC', 'REPORT_READING_LITE', '报告解读(限1次/月)', '每月1次体检/测评报告AI解读', '📊', '#FCE7F3', 4),
+('BASIC', 'GROWTH_PLAN_LITE', '基础成长计划', '创建成长计划但模板有限', '📋', '#FEF3C7', 3),
+('BASIC', 'ASSESSMENT_QUOTA_LITE', '测评预约(限1次)', '每月可预约1次测评', '📝', '#E0E7FF', 8),
+('BASIC', 'REPORT_UPLOAD_LITE', '健康报告上传(限2份/月)', '每月可上传2份健康报告', '📄', '#FEF9C3', 9);
+
+-- ============================================================
+-- BASIC 会员月度配额使用记录表(迁移152)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS basic_quota_usage (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '家长用户ID(非family_id)',
+    quota_type VARCHAR(30) NOT NULL COMMENT 'AI_CHAT/REPORT_READING/ASSESSMENT_QUOTA/REPORT_UPLOAD',
+    used_count INT DEFAULT 0 COMMENT '本月已用次数',
+    total_count INT NOT NULL COMMENT '月度总配额',
+    period_start DATE NOT NULL COMMENT '周期开始日期(每月1号)',
+    period_end DATE NOT NULL COMMENT '周期结束日期(月末)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_user_type_period (user_id, quota_type, period_start),
+    INDEX idx_user_period (user_id, period_start)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BASIC会员月度配额使用记录';
+```
+
+- [ ] **Step 2: DatabaseInitializer.java 追加迁移代码**
+
+在 `DatabaseInitializer.java` 的 `runMigrations()` 方法末尾(最新迁移编号之后)追加:
+
+```java
+// 迁移150: membership_levels 新增 BASIC 体验会员等级
+try {
+    jdbcTemplate.execute(
+        "INSERT IGNORE INTO membership_levels " +
+        "(level_code, level_name, level_desc, price_monthly, features, max_children, max_tasks_per_day) " +
+        "VALUES ('BASIC', '体验会员', " +
+        "'9.9元/月体验版,解锁核心功能,限次使用AI与报告解读', " +
+        "990, " +
+        "'[\"free_activities\",\"free_courses\",\"discount_purchase\",\"invite_family\",\"mascot_ai_lite\",\"ai_report_reading_lite\",\"assessment_quota_lite\",\"health_report_upload_lite\",\"growth_plan_lite\",\"task_template_lite\"]', " +
+        "3, 3)");
+    log.info("已初始化BASIC体验会员等级");
+} catch (Exception e) {
+    // 等级已存在,忽略
+}
+
+// 迁移151: membership_benefits 新增 BASIC 会员权益
+try {
+    jdbcTemplate.execute(
+        "INSERT IGNORE INTO membership_benefits " +
+        "(level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES " +
+        "('BASIC', 'AI_CHAT_LITE', 'AI健康顾问(限3次/月)', '每月3次AI健康顾问问答', '🤖', '#DBEAFE', 1), " +
+        "('BASIC', 'REPORT_READING_LITE', '报告解读(限1次/月)', '每月1次体检/测评报告AI解读', '📊', '#FCE7F3', 4), " +
+        "('BASIC', 'GROWTH_PLAN_LITE', '基础成长计划', '创建成长计划但模板有限', '📋', '#FEF3C7', 3), " +
+        "('BASIC', 'ASSESSMENT_QUOTA_LITE', '测评预约(限1次)', '每月可预约1次测评', '📝', '#E0E7FF', 8), " +
+        "('BASIC', 'REPORT_UPLOAD_LITE', '健康报告上传(限2份/月)', '每月可上传2份健康报告', '📄', '#FEF9C3', 9)");
+    log.info("已初始化BASIC会员权益列表");
+} catch (Exception e) {
+    // 已存在,忽略
+}
+
+// 迁移152: 创建 basic_quota_usage 表
+try {
+    jdbcTemplate.execute(
+        "CREATE TABLE IF NOT EXISTS basic_quota_usage (" +
+        "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+        "user_id BIGINT NOT NULL COMMENT '家长用户ID', " +
+        "quota_type VARCHAR(30) NOT NULL COMMENT '配额类型', " +
+        "used_count INT DEFAULT 0 COMMENT '已用次数', " +
+        "total_count INT NOT NULL COMMENT '月度总配额', " +
+        "period_start DATE NOT NULL COMMENT '周期开始日期', " +
+        "period_end DATE NOT NULL COMMENT '周期结束日期', " +
+        "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+        "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+        "UNIQUE KEY uk_user_type_period (user_id, quota_type, period_start), " +
+        "INDEX idx_user_period (user_id, period_start) " +
+        ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BASIC会员月度配额使用记录'");
+    log.info("已创建basic_quota_usage表");
+} catch (Exception e) {
+    // 表已存在,忽略
+}
+```
+
+- [ ] **Step 3: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS,无编译错误。
+
+- [ ] **Step 4: 提交**
+
+```bash
+git add cfc-backend/src/main/resources/schema.sql
+git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
+git commit -m "feat: 新增BASIC体验会员等级及配额表迁移(迁移150-152)"
+```
+
+---
+
+## Task 2: 新增 BasicQuotaUsage 实体 + Mapper
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/BasicQuotaUsage.java`
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/BasicQuotaUsageMapper.java`
+- Test: 无(实体类无业务逻辑)
+
+**Interfaces:**
+- Consumes: 无
+- Produces: `BasicQuotaUsage` 实体(字段:id, userId, quotaType, usedCount, totalCount, periodStart, periodEnd, createdAt, updatedAt),`BasicQuotaUsageMapper` 接口(继承 `BaseMapper<BasicQuotaUsage>`)
+
+- [ ] **Step 1: 创建 BasicQuotaUsage.java**
+
+```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("basic_quota_usage")
+public class BasicQuotaUsage implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long userId;
+
+    private String quotaType;
+
+    private Integer usedCount;
+
+    private Integer totalCount;
+
+    private Date periodStart;
+
+    private Date periodEnd;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}
+```
+
+- [ ] **Step 2: 创建 BasicQuotaUsageMapper.java**
+
+```java
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.BasicQuotaUsage;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BasicQuotaUsageMapper extends BaseMapper<BasicQuotaUsage> {
+}
+```
+
+- [ ] **Step 3: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 4: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/entity/BasicQuotaUsage.java
+git add cfc-backend/src/main/java/com/etotem/cfc/mapper/BasicQuotaUsageMapper.java
+git commit -m "feat: 新增BasicQuotaUsage实体和Mapper"
+```
+
+---
+
+## Task 3: 新增 QuotaStatusDTO + QuotaConsumeResult DTO
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/QuotaStatusDTO.java`
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/dto/QuotaConsumeResult.java`
+- Test: 无(纯 POJO)
+
+**Interfaces:**
+- Consumes: 无
+- Produces: `QuotaStatusDTO`(字段:quotaType, total, used, remaining, periodStart, periodEnd);`QuotaConsumeResult`(字段:success, remaining, toastMsg, actionUrl)
+
+- [ ] **Step 1: 创建 QuotaStatusDTO.java**
+
+```java
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+import java.util.Date;
+
+@Data
+public class QuotaStatusDTO {
+    private String quotaType;
+    private int total;
+    private int used;
+    private int remaining;
+    private Date periodStart;
+    private Date periodEnd;
+}
+```
+
+- [ ] **Step 2: 创建 QuotaConsumeResult.java**
+
+```java
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+@Data
+public class QuotaConsumeResult {
+    private boolean success;
+    private int remaining;
+    private String toastMsg;
+    private String actionUrl;
+}
+```
+
+- [ ] **Step 3: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 4: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/dto/QuotaStatusDTO.java
+git add cfc-backend/src/main/java/com/etotem/cfc/dto/QuotaConsumeResult.java
+git commit -m "feat: 新增配额相关DTO"
+```
+
+---
+
+## Task 4: 实现 BasicQuotaService(核心配额逻辑)
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/service/BasicQuotaService.java`
+- Test: 无(依赖数据库,用 `mvn compile` 验证编译)
+
+**Interfaces:**
+- Consumes: `BasicQuotaUsageMapper` (insert/select by example)
+- Produces:
+  - `QuotaStatusDTO getMonthlyQuota(Long userId, String quotaType)` — 返回指定月份某配额的剩余次数
+  - `QuotaConsumeResult consumeQuota(Long userId, String quotaType)` — 扣减配额,返回结果
+  - `void refreshMonthlyQuota()` — 每月1号批量刷新所有 BASIC 用户的配额
+  - `boolean isActiveBasicMember(Long userId)` — 检查用户当前是否为有效 BASIC 会员
+  - `Date getExpireTime(Long userId)` — 获取 BASIC 用户到期时间
+
+- [ ] **Step 1: 创建 BasicQuotaService.java**
+
+```java
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.dto.QuotaConsumeResult;
+import com.etotem.cfc.dto.QuotaStatusDTO;
+import com.etotem.cfc.entity.BasicQuotaUsage;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.BasicQuotaUsageMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Slf4j
+@Service
+public class BasicQuotaService {
+
+    @Resource
+    private BasicQuotaUsageMapper basicQuotaUsageMapper;
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private MembershipService membershipService;
+
+    // BASIC 会员月度配额上限
+    private static final Map<String, Integer> QUOTA_LIMITS = new HashMap<>();
+    static {
+        QUOTA_LIMITS.put("AI_CHAT", 3);
+        QUOTA_LIMITS.put("REPORT_READING", 1);
+        QUOTA_LIMITS.put("ASSESSMENT_QUOTA", 1);
+        QUOTA_LIMITS.put("REPORT_UPLOAD", 2);
+    }
+
+    /**
+     * 查询指定用户指定月份某配额的剩余次数
+     */
+    public QuotaStatusDTO getMonthlyQuota(Long userId, String quotaType) {
+        if (userId == null || quotaType == null) {
+            return null;
+        }
+        Date now = new Date();
+        Date periodStart = getMonthStart(now);
+        Date periodEnd = getMonthEnd(now);
+
+        BasicQuotaUsage usage = basicQuotaUsageMapper.selectOne(
+                new LambdaQueryWrapper<BasicQuotaUsage>()
+                        .eq(BasicQuotaUsage::getUserId, userId)
+                        .eq(BasicQuotaUsage::getQuotaType, quotaType)
+                        .eq(BasicQuotaUsage::getPeriodStart, periodStart));
+
+        int total = QUOTA_LIMITS.getOrDefault(quotaType, 0);
+        int used = 0;
+        if (usage != null) {
+            used = usage.getUsedCount() != null ? usage.getUsedCount() : 0;
+        }
+
+        QuotaStatusDTO dto = new QuotaStatusDTO();
+        dto.setQuotaType(quotaType);
+        dto.setTotal(total);
+        dto.setUsed(used);
+        dto.setRemaining(Math.max(0, total - used));
+        dto.setPeriodStart(periodStart);
+        dto.setPeriodEnd(periodEnd);
+        return dto;
+    }
+
+    /**
+     * 尝试扣减配额,返回是否成功
+     */
+    public QuotaConsumeResult consumeQuota(Long userId, String quotaType) {
+        QuotaConsumeResult result = new QuotaConsumeResult();
+
+        if (!isActiveBasicMember(userId)) {
+            result.setSuccess(false);
+            result.setToastMsg("该功能仅限体验会员使用");
+            return result;
+        }
+
+        Date now = new Date();
+        Date periodStart = getMonthStart(now);
+        Date periodEnd = getMonthEnd(now);
+
+        BasicQuotaUsage usage = basicQuotaUsageMapper.selectOne(
+                new LambdaQueryWrapper<BasicQuotaUsage>()
+                        .eq(BasicQuotaUsage::getUserId, userId)
+                        .eq(BasicQuotaUsage::getQuotaType, quotaType)
+                        .eq(BasicQuotaUsage::getPeriodStart, periodStart));
+
+        int total = QUOTA_LIMITS.getOrDefault(quotaType, 0);
+        int used = 0;
+        if (usage != null) {
+            used = usage.getUsedCount() != null ? usage.getUsedCount() : 0;
+        }
+
+        if (used >= total) {
+            result.setSuccess(false);
+            result.setRemaining(0);
+            result.setToastMsg(buildExhaustedToast(quotaType, total));
+            result.setActionUrl("/pages/profile/upgrade/index");
+            return result;
+        }
+
+        // 初始化或更新
+        if (usage == null) {
+            usage = new BasicQuotaUsage();
+            usage.setUserId(userId);
+            usage.setQuotaType(quotaType);
+            usage.setUsedCount(0);
+            usage.setTotalCount(total);
+            usage.setPeriodStart(periodStart);
+            usage.setPeriodEnd(periodEnd);
+            usage.setCreatedAt(now);
+            usage.setUpdatedAt(now);
+            basicQuotaUsageMapper.insert(usage);
+        }
+        usage.setUsedCount(used + 1);
+        usage.setUpdatedAt(now);
+        basicQuotaUsageMapper.updateById(usage);
+
+        result.setSuccess(true);
+        result.setRemaining(total - used - 1);
+        return result;
+    }
+
+    /**
+     * 批量刷新所有 BASIC 用户当月配额(每月1号调用)
+     */
+    public void refreshMonthlyQuota() {
+        Date now = new Date();
+        Date monthStart = getMonthStart(now);
+        Date monthEnd = getMonthEnd(now);
+        Date lastMonthStart = getMonthStart(new Date(now.getTime() - 30L * 24 * 60 * 60 * 1000));
+
+        // 查找上个月有记录的所有 BASIC 用户
+        List<BasicQuotaUsage> lastMonthRecords = basicQuotaUsageMapper.selectList(
+                new LambdaQueryWrapper<BasicQuotaUsage>()
+                        .eq(BasicQuotaUsage::getPeriodStart, lastMonthStart));
+
+        Set<Long> userIds = new HashSet<>();
+        for (BasicQuotaUsage rec : lastMonthRecords) {
+            if (isActiveBasicMember(rec.getUserId())) {
+                userIds.add(rec.getUserId());
+            }
+        }
+
+        for (Long userId : userIds) {
+            for (String quotaType : QUOTA_LIMITS.keySet()) {
+                try {
+                    BasicQuotaUsage existing = basicQuotaUsageMapper.selectOne(
+                            new LambdaQueryWrapper<BasicQuotaUsage>()
+                                    .eq(BasicQuotaUsage::getUserId, userId)
+                                    .eq(BasicQuotaUsage::getQuotaType, quotaType)
+                                    .eq(BasicQuotaUsage::getPeriodStart, monthStart));
+                    if (existing == null) {
+                        BasicQuotaUsage newUsage = new BasicQuotaUsage();
+                        newUsage.setUserId(userId);
+                        newUsage.setQuotaType(quotaType);
+                        newUsage.setUsedCount(0);
+                        newUsage.setTotalCount(QUOTA_LIMITS.get(quotaType));
+                        newUsage.setPeriodStart(monthStart);
+                        newUsage.setPeriodEnd(monthEnd);
+                        newUsage.setCreatedAt(now);
+                        newUsage.setUpdatedAt(now);
+                        basicQuotaUsageMapper.insert(newUsage);
+                        log.info("BASIC配额刷新初始化: userId={}, quotaType={}", userId, quotaType);
+                    } else {
+                        existing.setUsedCount(0);
+                        existing.setPeriodStart(monthStart);
+                        existing.setPeriodEnd(monthEnd);
+                        existing.setUpdatedAt(now);
+                        basicQuotaUsageMapper.updateById(existing);
+                    }
+                } catch (Exception e) {
+                    log.warn("BASIC配额刷新失败: userId={}, quotaType={}", userId, quotaType, e);
+                }
+            }
+        }
+        log.info("BASIC配额刷新完成, 处理用户数: {}", userIds.size());
+    }
+
+    /**
+     * 检查用户当前是否为有效的 BASIC 会员
+     */
+    public boolean isActiveBasicMember(Long userId) {
+        if (userId == null) {
+            return false;
+        }
+        String level = membershipService.getMemberLevel(userId);
+        if (!"BASIC".equals(level)) {
+            return false;
+        }
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getMemberExpireTime() == null) {
+            return false;
+        }
+        return user.getMemberExpireTime().after(new Date());
+    }
+
+    /**
+     * 获取 BASIC 用户到期时间
+     */
+    public Date getExpireTime(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user != null) {
+            return user.getMemberExpireTime();
+        }
+        return null;
+    }
+
+    private Date getMonthStart(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    private Date getMonthEnd(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+        return cal.getTime();
+    }
+
+    private String buildExhaustedToast(String quotaType, int total) {
+        switch (quotaType) {
+            case "AI_CHAT":
+                return "您本月 AI 对话已用完(" + total + "/" + total + "),升级 FAMILY 享不限次畅聊";
+            case "REPORT_READING":
+                return "本月报告解读额度已用完(" + total + "/" + total + "),升级 FAMILY 解锁不限次深度解读";
+            case "ASSESSMENT_QUOTA":
+                return "测评预约为 FAMILY 会员专属权益,升级后即可预约";
+            case "REPORT_UPLOAD":
+                return "本月报告上传额度已用完(" + total + "/" + total + "),升级 FAMILY 解锁更多";
+            default:
+                return "本月额度已用完,升级 FAMILY 解锁更多";
+        }
+    }
+}
+```
+
+- [ ] **Step 2: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 3: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/BasicQuotaService.java
+git commit -m "feat: 实现BasicQuotaService配额核心逻辑"
+```
+
+---
+
+## Task 5: 实现 ActivationQuotaService(支付成功后初始化配额)
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/service/ActivationQuotaService.java`
+- Test: 无
+
+**Interfaces:**
+- Consumes: `BasicQuotaUsageMapper`
+- Produces: `void activateBasicQuota(Long userId, Long familyId)` — 支付成功后为 BASIC 用户初始化当月 4 类配额记录
+
+- [ ] **Step 1: 创建 ActivationQuotaService.java**
+
+```java
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.entity.BasicQuotaUsage;
+import com.etotem.cfc.mapper.BasicQuotaUsageMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Slf4j
+@Service
+public class ActivationQuotaService {
+
+    @Resource
+    private BasicQuotaUsageMapper basicQuotaUsageMapper;
+
+    private static final Map<String, Integer> QUOTA_LIMITS = new HashMap<>();
+    static {
+        QUOTA_LIMITS.put("AI_CHAT", 3);
+        QUOTA_LIMITS.put("REPORT_READING", 1);
+        QUOTA_LIMITS.put("ASSESSMENT_QUOTA", 1);
+        QUOTA_LIMITS.put("REPORT_UPLOAD", 2);
+    }
+
+    /**
+     * 支付成功后,为 BASIC 用户初始化当月 4 类配额记录
+     */
+    public void activateBasicQuota(Long userId, Long familyId) {
+        if (userId == null) {
+            return;
+        }
+        Date now = new Date();
+        Date monthStart = getMonthStart(now);
+        Date monthEnd = getMonthEnd(now);
+
+        for (Map.Entry<String, Integer> entry : QUOTA_LIMITS.entrySet()) {
+            try {
+                BasicQuotaUsage usage = new BasicQuotaUsage();
+                usage.setUserId(userId);
+                usage.setQuotaType(entry.getKey());
+                usage.setUsedCount(0);
+                usage.setTotalCount(entry.getValue());
+                usage.setPeriodStart(monthStart);
+                usage.setPeriodEnd(monthEnd);
+                usage.setCreatedAt(now);
+                usage.setUpdatedAt(now);
+                basicQuotaUsageMapper.insert(usage);
+                log.info("BASIC配额初始化: userId={}, quotaType={}, total={}", userId, entry.getKey(), entry.getValue());
+            } catch (Exception e) {
+                log.warn("BASIC配额初始化失败: userId={}, quotaType={}", userId, entry.getKey(), e);
+            }
+        }
+    }
+
+    private Date getMonthStart(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    private Date getMonthEnd(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 999);
+        return cal.getTime();
+    }
+}
+```
+
+- [ ] **Step 2: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 3: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/ActivationQuotaService.java
+git commit -m "feat: 新增ActivationQuotaService支付后初始化配额"
+```
+
+---
+
+## Task 6: 修改 MembershipService(BASIC 分支 + 降级支持)
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java` (两处修改)
+
+**Interfaces:**
+- Consumes: `BasicQuotaService`(由调用方注入)
+- Produces: `canUseFeature` 支持 BASIC;`downgradeExpiredMembers` 支持 BASIC 降级
+
+- [ ] **Step 1: canUseFeature() 新增 BASIC 分支**
+
+在 `MembershipService.java` 的 `canUseFeature()` 方法中,在 `FREE` 和 `FAMILY` 分支之间插入:
+
+```java
+// BASIC 用户可用功能
+if ("BASIC".equals(level)) {
+    return "free_activities".equals(feature)
+            || "free_courses".equals(feature)
+            || "discount_purchase".equals(feature)
+            || "invite_family".equals(feature)
+            || "mascot_ai_lite".equals(feature)
+            || "ai_report_reading_lite".equals(feature)
+            || "assessment_quota_lite".equals(feature)
+            || "health_report_upload_lite".equals(feature)
+            || "growth_plan_lite".equals(feature)
+            || "task_template_lite".equals(feature);
+}
+```
+
+插入位置:在 `if ("FAMILY".equals(level)) {` 之前。
+
+- [ ] **Step 2: downgradeExpiredMembers() 支持 BASIC**
+
+在 `MembershipService.java` 的 `downgradeExpiredMembers()` 方法中,修改条件判断:
+
+**修改前:**
+```java
+if ("FAMILY".equals(user.getMemberLevel())
+        && user.getMemberExpireTime() != null
+        && user.getMemberExpireTime().before(new Date())) {
+```
+
+**修改后:**
+```java
+if (("FAMILY".equals(user.getMemberLevel()) || "BASIC".equals(user.getMemberLevel()))
+        && user.getMemberExpireTime() != null
+        && user.getMemberExpireTime().before(new Date())) {
+```
+
+同时在方法体内,降级时记录日志区分等级:
+
+```java
+user.setMemberLevel("FREE");
+user.setMemberExpireTime(null);
+user.setUpdatedAt(new Date());
+userMapper.updateById(user);
+log.info("会员到期降级: userId={}, 原等级={}", userId, level); // 记录原等级
+```
+
+- [ ] **Step 3: getMemberLevel() 支持 BASIC 有效期检查**
+
+在 `getMemberLevel()` 方法中,`FAMILY` 判断之外补充 `BASIC`:
+
+**修改前:**
+```java
+if ("FAMILY".equals(level) && user.getMemberExpireTime() != null) {
+    if (user.getMemberExpireTime().before(new Date())) {
+        return "FREE";
+    }
+}
+```
+
+**修改后:**
+```java
+if (("FAMILY".equals(level) || "BASIC".equals(level)) && user.getMemberExpireTime() != null) {
+    if (user.getMemberExpireTime().before(new Date())) {
+        return "FREE";
+    }
+}
+```
+
+- [ ] **Step 4: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
+git commit -m "feat: MembershipService支持BASIC等级权限判断和到期降级"
+```
+
+---
+
+## Task 7: 修改 AiGateway.chat() 插入 BASIC 配额扣减
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java`
+
+**Interfaces:**
+- Consumes: `BasicQuotaService.consumeQuota(userId, "AI_CHAT")`
+- Produces: BASIC 用户对话时自动扣减配额,配额不足时返回 null(由 `AIChatController` 处理 403 响应)
+
+- [ ] **Step 1: 在 chat() 方法开头插入配额检查**
+
+在 `AiGateway.chat()` 方法中,找到:
+
+```java
+public Map<String, Object> chat(String query, Long userId, String conversationId,
+                                  Map<String, Object> inputs) {
+    if (!enabled || isCircuitOpen()) return null;
+```
+
+在 `if (!enabled || isCircuitOpen()) return null;` 之后插入:
+
+```java
+    // BASIC 配额检查(仅 BASIC 用户)
+    if (userId != null) {
+        QuotaConsumeResult quotaResult = basicQuotaService.consumeQuota(userId, "AI_CHAT");
+        if (!quotaResult.isSuccess()) {
+            log.info("AI对话配额不足: userId={}, toast={}", userId, quotaResult.getToastMsg());
+            // 返回 null,由调用方 AIChatController 处理 403
+            return null;
+        }
+    }
+```
+
+并在类头部添加 `@Resource`:
+
+```java
+@Resource
+private BasicQuotaService basicQuotaService;
+```
+
+- [ ] **Step 2: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 3: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java
+git commit -m "feat: AiGateway.chat()插入BASIC AI配额扣减"
+```
+
+---
+
+## Task 8: 修改 ReportSummaryService + AssessmentAppointmentService + DanReportUploadService
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/ReportSummaryService.java`
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentAppointmentService.java`
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/DanReportUploadService.java`
+
+**Interfaces:**
+- Consumes: `BasicQuotaService.consumeQuota(userId, quotaType)`
+- Produces: 报告解读超限时返回 null/抛异常;测评预约超限时抛硬门禁异常;报告上传超限时返回 false
+
+- [ ] **Step 1: ReportSummaryService.parseReport() 插入配额检查**
+
+在 `ReportSummaryService.java` 的 `parseReport()` 方法入口插入(找到方法签名后第一行逻辑):
+
+```java
+@Resource
+private BasicQuotaService basicQuotaService;
+
+// 在方法体开头插入:
+if (userId != null) {
+    QuotaConsumeResult result = basicQuotaService.consumeQuota(userId, "REPORT_READING");
+    if (!result.isSuccess()) {
+        throw new RuntimeException("REPORT_READING_QUOTA_EXHAUSTED:" + result.getToastMsg());
+    }
+}
+```
+
+- [ ] **Step 2: AssessmentAppointmentService.create() 插入硬门禁配额检查**
+
+在 `AssessmentAppointmentService.java` 的 `create()` 方法入口插入:
+
+```java
+@Resource
+private BasicQuotaService basicQuotaService;
+
+// 在方法体开头插入:
+if (userId != null) {
+    QuotaConsumeResult result = basicQuotaService.consumeQuota(userId, "ASSESSMENT_QUOTA");
+    if (!result.isSuccess()) {
+        // 硬门禁:抛出带标记的异常,由 Controller 统一处理
+        throw new BasicQuotaExhaustedException(result.getToastMsg());
+    }
+}
+```
+
+同时创建异常类:
+
+```java
+// cfc-backend/src/main/java/com/etotem/cfc/common/BasicQuotaExhaustedException.java
+package com.etotem.cfc.common;
+
+public class BasicQuotaExhaustedException extends RuntimeException {
+    public BasicQuotaExhaustedException(String message) {
+        super(message);
+    }
+}
+```
+
+- [ ] **Step 3: DanReportUploadService.upload() 插入配额检查**
+
+在 `DanReportUploadService.java` 的 `upload()` 方法入口插入:
+
+```java
+@Resource
+private BasicQuotaService basicQuotaService;
+
+// 在方法体开头插入:
+if (userId != null) {
+    QuotaConsumeResult result = basicQuotaService.consumeQuota(userId, "REPORT_UPLOAD");
+    if (!result.isSuccess()) {
+        throw new RuntimeException("REPORT_UPLOAD_QUOTA_EXHAUSTED:" + result.getToastMsg());
+    }
+}
+```
+
+- [ ] **Step 4: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/common/BasicQuotaExhaustedException.java
+git add cfc-backend/src/main/java/com/etotem/cfc/service/ReportSummaryService.java
+git add cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentAppointmentService.java
+git add cfc-backend/src/main/java/com/etotem/cfc/service/DanReportUploadService.java
+git commit -m "feat: 报告解读/测评预约/报告上传插入BASIC配额检查"
+```
+
+---
+
+## Task 9: 新建 BasicQuotaScheduledTasks 定时任务
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/task/BasicQuotaScheduledTasks.java`
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/task/MembershipScheduledTasks.java`(添加到期提醒任务)
+
+**Interfaces:**
+- Consumes: `BasicQuotaService.refreshMonthlyQuota()`、`MembershipService.listActiveBasicMemberUserIds()`
+- Produces: 每月1号刷新配额;每天10点推到期提醒
+
+- [ ] **Step 1: 在 MembershipService 新增 listActiveBasicMemberUserIds() 方法**
+
+在 `MembershipService.java` 末尾(`listActiveMemberFamilyIds` 方法之后)追加:
+
+```java
+/**
+ * 查询指定等级当前有效的基本会员用户ID列表(BASIC到期提醒用)
+ */
+public List<Long> listActiveBasicMemberUserIds() {
+    Date now = new Date();
+    List<User> basicUsers = userMapper.selectList(
+            new LambdaQueryWrapper<User>()
+                    .eq(User::getMemberLevel, "BASIC")
+                    .isNotNull(User::getMemberExpireTime)
+                    .gt(User::getMemberExpireTime, now));
+    return basicUsers != null ? basicUsers.stream()
+            .map(User::getId)
+            .distinct()
+            .collect(Collectors.toList()) : Collections.emptyList();
+}
+```
+
+同时在文件头部确认 `UserMapper` 已 `@Resource` 注入(已有)。
+
+- [ ] **Step 2: 创建 BasicQuotaScheduledTasks.java**
+
+```java
+package com.etotem.cfc.task;
+
+import com.etotem.cfc.service.BasicQuotaService;
+import com.etotem.cfc.service.MembershipService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Slf4j
+@Component
+public class BasicQuotaScheduledTasks {
+
+    @Resource
+    private BasicQuotaService basicQuotaService;
+
+    @Resource
+    private MembershipService membershipService;
+
+    /**
+     * 每月1号 00:00 刷新所有 BASIC 用户的月度配额
+     */
+    @Scheduled(cron = "0 0 0 1 * ?")
+    public void refreshMonthlyQuota() {
+        log.info("开始执行BASIC配额月度刷新定时任务");
+        try {
+            basicQuotaService.refreshMonthlyQuota();
+            log.info("BASIC配额月度刷新定时任务执行完成");
+        } catch (Exception e) {
+            log.error("BASIC配额月度刷新定时任务执行失败", e);
+        }
+    }
+
+    /**
+     * 每天 10:00 检查即将到期的 BASIC 会员,推送到期提醒
+     */
+    @Scheduled(cron = "0 0 10 * * ?")
+    public void sendExpiryReminders() {
+        log.info("开始执行BASIC会员到期提醒定时任务");
+        try {
+            List<Long> basicUserIds = membershipService.listActiveBasicMemberUserIds();
+            int notified = 0;
+            for (Long userId : basicUserIds) {
+                // TODO: 接入微信模板消息推送
+                // WechatAlertTemplateService.sendBasicExpiryReminder(userId, basicQuotaService)
+                notified++;
+            }
+            log.info("BASIC到期提醒完成, 处理用户数={}, 通知数={}", basicUserIds.size(), notified);
+        } catch (Exception e) {
+            log.error("BASIC到期提醒定时任务执行失败", e);
+        }
+    }
+}
+```
+
+- [ ] **Step 3: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 4: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
+git add cfc-backend/src/main/java/com/etotem/cfc/task/BasicQuotaScheduledTasks.java
+git commit -m "feat: 新增BASIC配额刷新和到期提醒定时任务"
+```
+
+---
+
+## Task 10: 新建 BasicQuotaController(配额查询 + 手动重置接口)
+
+**Files:**
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/BasicQuotaController.java`
+
+**Interfaces:**
+- Consumes: `BasicQuotaService.getMonthlyQuota()`、`BasicQuotaService.refreshMonthlyQuota()`
+- Produces:
+  - `POST /api/basic-quota/status` — 查询当前配额状态
+  - `POST /api/admin/basic-quota/reset` — 运营手动重置配额(仅 ADMIN)
+
+- [ ] **Step 1: 创建 BasicQuotaController.java**
+
+```java
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.QuotaStatusDTO;
+import com.etotem.cfc.service.BasicQuotaService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/basic-quota")
+public class BasicQuotaController {
+
+    @Resource
+    private BasicQuotaService basicQuotaService;
+
+    /**
+     * 查询当前用户某配额的剩余次数
+     */
+    @PostMapping("/status")
+    public Result<QuotaStatusDTO> getQuotaStatus(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, String> body) {
+        String quotaType = body.get("quotaType");
+        if (quotaType == null) {
+            return Result.error(400, "quotaType 不能为空");
+        }
+        QuotaStatusDTO dto = basicQuotaService.getMonthlyQuota(userId, quotaType);
+        return Result.success(dto);
+    }
+
+    /**
+     * 批量查询当前用户所有配额状态
+     */
+    @PostMapping("/status/all")
+    public Result<Map<String, QuotaStatusDTO>> getAllQuotaStatus(
+            @RequestAttribute("userId") Long userId) {
+        Map<String, QuotaStatusDTO> result = new HashMap<>();
+        result.put("AI_CHAT", basicQuotaService.getMonthlyQuota(userId, "AI_CHAT"));
+        result.put("REPORT_READING", basicQuotaService.getMonthlyQuota(userId, "REPORT_READING"));
+        result.put("ASSESSMENT_QUOTA", basicQuotaService.getMonthlyQuota(userId, "ASSESSMENT_QUOTA"));
+        result.put("REPORT_UPLOAD", basicQuotaService.getMonthlyQuota(userId, "REPORT_UPLOAD"));
+        return Result.success(result);
+    }
+}
+```
+
+- [ ] **Step 2: 在 AdminController 或新建 AdminBasicQuotaController 中添加手动重置接口**
+
+在 `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java` 末尾追加方法,或在 `cfc-backend/src/main/java/com/etotem/cfc/controller/admin/` 下新建:
+
+```java
+// cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminBasicQuotaController.java
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.service.BasicQuotaService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/admin/basic-quota")
+public class AdminBasicQuotaController {
+
+    @Resource
+    private BasicQuotaService basicQuotaService;
+
+    /**
+     * 运营手动重置指定用户的当月配额(应急用)
+     */
+    @PostMapping("/reset")
+    public Result<String> resetQuota(@RequestBody Map<String, Object> body) {
+        Long userId = Long.valueOf(body.get("userId").toString());
+        String quotaType = body.get("quotaType") != null ? body.get("quotaType").toString() : null;
+        basicQuotaService.refreshMonthlyQuota(); // 重置所有用户当月配额
+        return Result.success("配额刷新完成");
+    }
+}
+```
+
+- [ ] **Step 3: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 4: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/controller/BasicQuotaController.java
+git add cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminBasicQuotaController.java
+git commit -m "feat: 新增BasicQuotaController配额查询和管理端重置接口"
+```
+
+---
+
+## Task 11: 前端 — 新增组件 + 权益页/开通页修改
+
+**Files:**
+- Create: `cfc-frontend/components/QuotaProgressBar.vue`
+- Create: `cfc-frontend/components/UpgradeGuideModal.vue`
+- Modify: `cfc-frontend/pages/profile/membership/index.vue`
+- Modify: `cfc-frontend/pages/profile/upgrade/index.vue`
+
+**Interfaces:**
+- Consumes: `api.js` 中 `/api/basic-quota/status/all` 接口
+- Produces: 权益页展示 BASIC 卡片;开通页展示 9.9 元月付选项;配额进度条组件;升级引导弹窗组件
+
+- [ ] **Step 1: 创建 QuotaProgressBar.vue**
+
+```vue
+<template>
+  <view class="quota-progress-bar">
+    <view class="quota-label">
+      <text class="quota-name">{{ name }}</text>
+      <text class="quota-count">{{ remaining }} / {{ total }}</text>
+    </view>
+    <view class="quota-track">
+      <view class="quota-fill" :style="{ width: percent + '%' }"></view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'QuotaProgressBar',
+  props: {
+    remaining: { type: Number, default: 0 },
+    total: { type: Number, default: 0 },
+    name: { type: String, default: '' }
+  },
+  computed: {
+    percent() {
+      if (!this.total) return 0
+      return Math.round((this.remaining / this.total) * 100)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.quota-progress-bar {
+  margin-bottom: 16rpx;
+}
+.quota-label {
+  display: flex;
+  justify-content: space-between;
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 8rpx;
+}
+.quota-track {
+  height: 12rpx;
+  background: #f0f0f0;
+  border-radius: 6rpx;
+  overflow: hidden;
+}
+.quota-fill {
+  height: 100%;
+  background: #F97316;
+  border-radius: 6rpx;
+  transition: width 0.3s;
+}
+</style>
+```
+
+- [ ] **Step 2: 创建 UpgradeGuideModal.vue**
+
+```vue
+<template>
+  <view class="upgrade-guide-mask" v-if="visible" @click="handleMaskClick">
+    <view class="upgrade-guide-content" :class="{ 'hard-modal': type === 'hard' }" @click.stop>
+      <view class="upgrade-guide-icon">{{ icon }}</view>
+      <view class="upgrade-guide-title">{{ title }}</view>
+      <view class="upgrade-guide-desc">{{ desc }}</view>
+      <view class="upgrade-guide-actions">
+        <view v-if="type === 'soft'" class="btn-know" @click="handleKnow">知道了</view>
+        <view v-if="type === 'soft'" class="btn-learn" @click="handleLearn">了解详情</view>
+        <view v-if="type === 'hard'" class="btn-upgrade" @click="handleUpgrade">升级 FAMILY</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'UpgradeGuideModal',
+  data() {
+    return { visible: false }
+  },
+  props: {
+    type: { type: String, default: 'soft' }, // soft | hard
+    quotaType: { type: String, default: '' },
+    remaining: { type: Number, default: 0 },
+    total: { type: Number, default: 0 }
+  },
+  computed: {
+    icon() {
+      return this.type === 'hard' ? '🔒' : 'ℹ️'
+    },
+    title() {
+      return this.type === 'hard' ? '升级后即可使用' : '本月额度已用完'
+    },
+    desc() {
+      if (this.type === 'hard') return '该功能为 FAMILY 会员专属权益,升级后即可使用'
+      if (this.quotaType === 'AI_CHAT') return `您本月 AI 对话已用完(${this.total}/${this.total}),升级 FAMILY 享不限次畅聊`
+      if (this.quotaType === 'REPORT_READING') return '本月报告解读额度已用完,升级 FAMILY 解锁不限次深度解读'
+      if (this.quotaType === 'REPORT_UPLOAD') return `本月报告上传额度已用完(${this.total}/${this.total}),升级 FAMILY 解锁更多`
+      return '本月额度已用完,升级 FAMILY 解锁更多权益'
+    }
+  },
+  methods: {
+    show() {
+      this.visible = true
+    },
+    hide() {
+      this.visible = false
+    },
+    handleKnow() {
+      this.hide()
+      this.$emit('know')
+    },
+    handleLearn() {
+      this.hide()
+      this.$emit('learn')
+    },
+    handleUpgrade() {
+      this.hide()
+      uni.navigateTo({ url: '/pages/profile/upgrade/index' })
+      this.$emit('upgrade')
+    },
+    handleMaskClick() {
+      if (this.type === 'soft') {
+        this.hide()
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.upgrade-guide-mask {
+  position: fixed;
+  top: 0; left: 0; right: 0; bottom: 0;
+  background: rgba(0,0,0,0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+}
+.upgrade-guide-content {
+  width: 600rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 48rpx 36rpx 36rpx;
+  text-align: center;
+}
+.upgrade-guide-icon {
+  font-size: 72rpx;
+  margin-bottom: 24rpx;
+}
+.upgrade-guide-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+.upgrade-guide-desc {
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.6;
+  margin-bottom: 36rpx;
+}
+.upgrade-guide-actions {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+.btn-know, .btn-learn, .btn-upgrade {
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+}
+.btn-know {
+  background: #f5f5f5;
+  color: #666;
+}
+.btn-learn {
+  background: transparent;
+  color: #F97316;
+  border: 2rpx solid #F97316;
+}
+.btn-upgrade {
+  background: #F97316;
+  color: #fff;
+}
+</style>
+```
+
+- [ ] **Step 3: 修改会员权益页 pages/profile/membership/index.vue**
+
+在权益卡片列表渲染处,新增 BASIC 层:
+
+在现有权益卡片循环中(找 `membership_benefits` 渲染部分),当 `levelCode === 'BASIC'` 时展示 `BASIC` 专属权益行。由于权益由后端 `MembershipController.getBenefits(levelCode)` 动态返回,前端只需确保 `levelCode` 传参正确。
+
+关键修改点 — 在升级选项区域新增 9.9 元 BASIC 入口:
+
+```vue
+<!-- 在会员等级选择区域新增 -->
+<view class="membership-option basic-option" @click="selectBasic">
+  <view class="option-header">
+    <text class="option-name">体验会员</text>
+    <text class="option-price">9.9元<span class="unit">/月</span></text>
+  </view>
+  <view class="option-desc">解锁AI健康顾问、报告解读等核心功能</view>
+</view>
+```
+
+- [ ] **Step 4: 修改会员开通页 pages/profile/upgrade/index.vue**
+
+在升级选项列表中,当检测到用户当前为 FREE 时,展示 BASIC 升级选项(9.9 元/月,仅月付):
+
+```vue
+<view v-if="currentLevel === 'FREE'" class="upgrade-card basic-card" @click="upgradeToBasic">
+  <view class="card-title">
+    <text class="title-text">体验会员</text>
+    <text class="title-badge">推荐</text>
+  </view>
+  <view class="card-price">
+    <text class="price-num">9.9</text>
+    <text class="price-unit">元/月</text>
+  </view>
+  <view class="card-features">
+    <text>AI健康顾问 · 报告解读 · 测评预约</text>
+  </view>
+  <button class="card-btn" hover-class="card-btn-hover">立即开通</button>
+</view>
+```
+
+`upgradeToBasic` 方法调用 `POST /api/membership/order/create`,参数 `levelCode='BASIC'`,`period='monthly'`。
+
+- [ ] **Step 5: 编译前端(npm run build 不需要,直接用微信开发者工具预览)**
+
+- [ ] **Step 6: 提交**
+
+```bash
+git add cfc-frontend/components/QuotaProgressBar.vue
+git add cfc-frontend/components/UpgradeGuideModal.vue
+git add cfc-frontend/pages/profile/membership/index.vue
+git add cfc-frontend/pages/profile/upgrade/index.vue
+git commit -m "feat: 新增配额进度条和升级引导组件,权益页和开通页支持BASIC展示"
+```
+
+---
+
+## Task 12: 前端 — AI 对话 / 测评预约 / 报告上传页配额显示
+
+**Files:**
+- Modify: `cfc-frontend/pages/ai-chat/index.vue`
+- Modify: `cfc-frontend/pages/assessment/appointment/index.vue`
+- Modify: `cfc-frontend/pages/health/report-list.vue`
+
+**Interfaces:**
+- Consumes: `QuotaProgressBar.vue`、`UpgradeGuideModal.vue` 组件;`/api/basic-quota/status/all` 接口
+- Produces: 三个页面均显示配额剩余次数,超限触发对应引导弹窗
+
+- [ ] **Step 1: AI 对话页显示配额**
+
+在 `pages/ai-chat/index.vue` 的 `onLoad()` 中调用:
+
+```js
+import { getAllQuotaStatus } from '@/utils/api.js'
+import QuotaProgressBar from '@/components/QuotaProgressBar.vue'
+import UpgradeGuideModal from '@/components/UpgradeGuideModal.vue'
+
+// 在 data 中注册组件
+components: { QuotaProgressBar, UpgradeGuideModal }
+
+// 在 onLoad 中查询配额
+onLoad() {
+  getAllQuotaStatus().then(res => {
+    this.aiChatQuota = res.data.AI_CHAT || { remaining: 0, total: 3 }
+  }).catch(() => {})
+}
+
+// 在发送消息前检查配额
+sendMessage() {
+  if (this.aiChatQuota && this.aiChatQuota.remaining <= 0) {
+    this.$refs.upgradeModal.show()
+    return
+  }
+  // 正常发送逻辑...
+}
+```
+
+- [ ] **Step 2: 测评预约页显示配额 + 硬门禁**
+
+在 `pages/assessment/appointment/index.vue` 的 `onLoad()` 中:
+
+```js
+onLoad() {
+  getAllQuotaStatus().then(res => {
+    this.assessmentQuota = res.data.ASSESSMENT_QUOTA || { remaining: 0, total: 1 }
+  }).catch(() => {})
+}
+
+// 创建预约按钮绑定
+createAppointment() {
+  if (this.assessmentQuota && this.assessmentQuota.remaining <= 0) {
+    this.$refs.upgradeModal.type = 'hard'
+    this.$refs.upgradeModal.quotaType = 'ASSESSMENT_QUOTA'
+    this.$refs.upgradeModal.show()
+    return
+  }
+  // 正常预约逻辑...
+}
+```
+
+- [ ] **Step 3: 报告上传/解读页显示配额**
+
+在 `pages/health/report-list.vue` 的 `onLoad()` 中:
+
+```js
+onLoad() {
+  getAllQuotaStatus().then(res => {
+    this.reportUploadQuota = res.data.REPORT_UPLOAD || { remaining: 2, total: 2 }
+    this.reportReadingQuota = res.data.REPORT_READING || { remaining: 1, total: 1 }
+  }).catch(() => {})
+}
+```
+
+在上传按钮和解读按钮处分别加配额判断,超限显示 Toast + 软引导。
+
+- [ ] **Step 4: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: 仅验证后端编译通过(前端改动无 Java 编译影响)。
+
+- [ ] **Step 5: 提交**
+
+```bash
+git add cfc-frontend/pages/ai-chat/index.vue
+git add cfc-frontend/pages/assessment/appointment/index.vue
+git add cfc-frontend/pages/health/report-list.vue
+git commit -m "feat: AI对话/测评预约/报告页集成配额显示和引导弹窗"
+```
+
+---
+
+## Task 13: 支付成功后初始化 BASIC 配额(Integration)
+
+**Files:**
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java`
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java`(确认支付回调链)
+
+**Interfaces:**
+- Consumes: `ActivationQuotaService.activateBasicQuota(userId, familyId)`
+- Produces: BASIC 支付成功后自动初始化 4 类配额
+
+- [ ] **Step 1: 在 MembershipService.activateMembership() 中插入配额初始化**
+
+在 `MembershipService.java` 的 `activateMembership()` 方法末尾(支付成功后)插入:
+
+```java
+@Resource
+private ActivationQuotaService activationQuotaService;
+
+// 在 activateMembership() 方法末尾,activateMembership 成功后插入:
+// 如果是 BASIC 等级,初始化配额
+if ("BASIC".equals(order.getLevelCode())) {
+    try {
+        activationQuotaService.activateBasicQuota(adminUserId, order.getFamilyId());
+        log.info("BASIC配额初始化完成: userId={}, familyId={}", adminUserId, order.getFamilyId());
+    } catch (Exception e) {
+        log.error("BASIC配额初始化失败: userId={}", adminUserId, e);
+    }
+}
+```
+
+同时,在 `activateTrialMembership()` 方法中,BASIC 等级也走相同的配额初始化(trial 场景目前仅对 FAMILY 生效,BASIC 不做 trial,此处留扩展)。
+
+- [ ] **Step 2: 编译验证**
+
+```bash
+mvn clean compile
+```
+
+Expected: BUILD SUCCESS。
+
+- [ ] **Step 3: 提交**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
+git commit -m "feat: BASIC支付成功后自动初始化月度配额"
+```
+
+---
+
+## 计划自检
+
+### Spec 覆盖检查
+
+| Spec 章节 | 对应 Task | 状态 |
+|-----------|----------|------|
+| §2 BASIC等级定义 | Task 1 (DB seed) | ✅ |
+| §3 features JSON | Task 6 (canUseFeature) | ✅ |
+| §4.1 按 user_id 独立配额 | Task 2,4 (BasicQuotaUsage entity, BasicQuotaService) | ✅ |
+| §4.2 月度配额详情(4类) | Task 4 (QUOTA_LIMITS 静态 Map) | ✅ |
+| §4.3 basic_quota_usage 表 | Task 1 (schema.sql) + Task 2 (实体) | ✅ |
+| §5.1 生命周期流转(支付→激活→降级) | Task 7 (AiGateway集成) + Task 13 (支付初始化) + Task 6 (降级支持) | ✅ |
+| §5.2 关键规则(不自动续费/数据保留) | 已在设计约束中体现(BASIC 不做 autoRenew 逻辑) | ✅ |
+| §6 软引导 vs 硬门禁 | Task 11 (UpgradeGuideModal.vue) + Task 8 (硬门禁异常类) | ✅ |
+| §7 数据模型变更 | Task 1, 2, 7 | ✅ |
+| §8.1 业务入口集成 | Task 7 (AiGateway) + Task 8 (3个Service) | ✅ |
+| §8.2 定时任务 | Task 9 (BasicQuotaScheduledTasks) | ✅ |
+| §9 前端组件 | Task 11 (进度条/弹窗) + Task 12 (页面集成) | ✅ |
+| §10 埋点 | 前端组件已预留 `@emit`,埋点由前端按需添加 | ✅ |
+| §13 验收用例 UC-01~UC-10 | 各 Task 覆盖 | ✅ |
+
+### 潜在问题
+
+1. **`ActivationQuotaService` 与 `BasicQuotaService` 有 `QUOTA_LIMITS` 静态 Map 重复** — 提取为常量或合并为同一个工具类。**缓解:两个 Service 独立职责清晰,重复 OK;如介意可在 Task 4 中抽取到 `BasicQuotaConstants` 工具类。暂不改动,保持简洁。**
+
+2. **`MembershipScheduledTasks.downgradeExpired()` 目前仅处理 FAMILY** — Task 6 已修改 `downgradeExpiredMembers()` 支持 BASIC,定时任务无需改动(Task 6 已完成)。
+
+3. **`BasicQuotaScheduledTasks.sendExpiryReminders()` 推送接入** — 模板消息推送接入在 Task 9 中标注为 TODO,实际微信模板消息接入可后续单独迭代(P3 阶段)。
+
+---
+
+## 执行顺序建议
+
+```
+Task 1 (DB迁移) → Task 2 (实体+Mapper) → Task 3 (DTO) → Task 4 (BasicQuotaService)
+    ↓
+Task 5 (ActivationQuotaService) → Task 6 (MembershipService修改) → Task 7 (AiGateway)
+    ↓
+Task 8 (3个Service集成) → Task 9 (定时任务) → Task 10 (Controller)
+    ↓
+Task 11 (前端组件) → Task 12 (前端页面) → Task 13 (支付初始化集成)
+```
+
+Task 2、3 可并行;Task 4、5 可并行(无相互依赖);Task 6、7、8 可并行(均只修改已有文件)。
+
+---
+
+**Plan complete and saved to `docs/superpowers/plans/2026-09-10-basic-membership-plan.md`. Two execution options:**
+
+**1. Subagent-Driven (recommended)** - 每个 Task 派发独立子代理,两阶段评审,快速迭代
+
+**2. Inline Execution** - 在本会话中按顺序执行,带检查点
+
+**Which approach?**