# 会员管理系统修复实施计划
> **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:** 修复会员管理系统审计发现的 6 个问题(2 个严重数据问题 + 4 个功能缺口)
**Architecture:** 后端 DDL 补充 → 前端 API 补齐 → 前端页面补全 → 硬编码修复 → 管理端补充
**Tech Stack:** Spring Boot 2.7 + MyBatis-Plus / uni-app Vue 2 小程序 / Vue 2 + Element UI 管理端
**参考文档:**
- `docs/flows/membership-flow.md` — 会员与订阅流程图
- `docs/superpowers/specs/2026-07-09-family-membership-design.md` — 完整设计方案
- `cfc-backend/src/main/java/com/etotem/cfc/entity/MembershipLevel.java` — membership_levels 实体
- `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMembership.java` — family_memberships 实体
- `cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java` — 会员控制器
- `cfc-backend/src/main/java/com/etotem/cfc/controller/subscription/SubscriptionController.java` — 订阅控制器
---
### Task 1: 补充 membership_levels 和 family_memberships 表 DDL
**Files:**
- Modify: `cfc-backend/src/main/resources/schema.sql` — 追加 CREATE TABLE
- Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` — 追加迁移
- [ ] **Step 1: schema.sql 追加 membership_levels 表定义**
在 `member_upgrade_records` 表(行 ~1325)之后,`-- 规划师服务模块表` 之前插入:
```sql
-- 会员等级配置(FREE/FAMILY/PROVIDER)
CREATE TABLE IF NOT EXISTS membership_levels (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
level_code VARCHAR(20) NOT NULL COMMENT '等级编码: FREE/FAMILY/PROVIDER',
level_name VARCHAR(50) NOT NULL COMMENT '等级名称',
level_desc VARCHAR(500) COMMENT '等级描述',
price_monthly INT DEFAULT 0 COMMENT '月费(分)',
price_quarterly INT DEFAULT 0 COMMENT '季费(分)',
price_yearly INT DEFAULT 0 COMMENT '年费(分)',
features TEXT COMMENT '功能权限列表(JSON)',
max_children INT DEFAULT 3 COMMENT '可添加孩子数量',
max_tasks_per_day INT DEFAULT 5 COMMENT '每日任务上限',
ai_review_enabled TINYINT(1) DEFAULT 0 COMMENT 'AI审核',
priority_support TINYINT(1) DEFAULT 0 COMMENT '优先客服',
teacher_consultation TINYINT(1) DEFAULT 0 COMMENT '老师咨询',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_level_code (level_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级配置';
```
- [ ] **Step 2: schema.sql 追加 family_memberships 表定义**
紧跟 membership_levels 之后:
```sql
-- 家庭会员记录
CREATE TABLE IF NOT EXISTS family_memberships (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
family_id BIGINT NOT NULL COMMENT '家庭ID',
level_code VARCHAR(20) NOT NULL COMMENT '会员等级: FREE/BASIC/PROFESSIONAL/ENTERPRISE',
start_date DATETIME NOT NULL COMMENT '开始时间',
end_date DATETIME NOT NULL COMMENT '到期时间',
payment_method VARCHAR(20) COMMENT '支付方式: wechat/alipay/card',
payment_status VARCHAR(20) DEFAULT 'pending' COMMENT '支付状态: pending/paid/expired/refunded',
order_no VARCHAR(64) COMMENT '订单号',
transaction_id VARCHAR(64) COMMENT '交易流水号',
amount INT DEFAULT 0 COMMENT '支付金额(分)',
auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_family_id (family_id),
INDEX idx_level_code (level_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭会员记录';
```
- [ ] **Step 3: DatabaseInitializer 追加迁移 90 和 91**
找到 `runMigrations()` 中最新迁移编号(搜索 `// 迁移` 确认最新号),追加:
```java
// 迁移90: 创建 membership_levels 表(会员等级配置)
try {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS membership_levels (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"level_code VARCHAR(20) NOT NULL COMMENT '等级编码: FREE/FAMILY/PROVIDER', " +
"level_name VARCHAR(50) NOT NULL COMMENT '等级名称', " +
"level_desc VARCHAR(500) COMMENT '等级描述', " +
"price_monthly INT DEFAULT 0 COMMENT '月费(分)', " +
"price_quarterly INT DEFAULT 0 COMMENT '季费(分)', " +
"price_yearly INT DEFAULT 0 COMMENT '年费(分)', " +
"features TEXT COMMENT '功能权限列表(JSON)', " +
"max_children INT DEFAULT 3 COMMENT '可添加孩子数量', " +
"max_tasks_per_day INT DEFAULT 5 COMMENT '每日任务上限', " +
"ai_review_enabled TINYINT(1) DEFAULT 0 COMMENT 'AI审核', " +
"priority_support TINYINT(1) DEFAULT 0 COMMENT '优先客服', " +
"teacher_consultation TINYINT(1) DEFAULT 0 COMMENT '老师咨询', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"UNIQUE KEY uk_level_code (level_code)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级配置'");
log.info("已创建 membership_levels 表");
} catch (Exception e) {
log.warn("创建 membership_levels 表失败: {}", e.getMessage());
}
// 迁移91: 创建 family_memberships 表(家庭会员记录)
try {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_memberships (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"family_id BIGINT NOT NULL COMMENT '家庭ID', " +
"level_code VARCHAR(20) NOT NULL COMMENT '会员等级', " +
"start_date DATETIME NOT NULL COMMENT '开始时间', " +
"end_date DATETIME NOT NULL COMMENT '到期时间', " +
"payment_method VARCHAR(20) COMMENT '支付方式', " +
"payment_status VARCHAR(20) DEFAULT 'pending' COMMENT '支付状态', " +
"order_no VARCHAR(64) COMMENT '订单号', " +
"transaction_id VARCHAR(64) COMMENT '交易流水号', " +
"amount INT DEFAULT 0 COMMENT '支付金额(分)', " +
"auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"INDEX idx_family_id (family_id), " +
"INDEX idx_level_code (level_code)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭会员记录'");
log.info("已创建 family_memberships 表");
} catch (Exception e) {
log.warn("创建 family_memberships 表失败: {}", e.getMessage());
}
```
- [ ] **Step 4: 编译验证**
```bash
cd cfc-backend && mvn clean compile -q
```
- [ ] **Step 5: Commit**
```bash
git add cfc-backend/src/main/resources/schema.sql cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
git commit -m "fix: 补充 membership_levels 和 family_memberships 表 DDL
- schema.sql + DatabaseInitializer 迁移 90/91
- 两个表被实体引用但无 DDL 创建语句,数据库重建时会员系统会不可用"
```
---
### Task 2: 补齐缺少的前端 API 封装
**Files:**
- Modify: `cfc-frontend/utils/api.js`
在 `api.js` 的会员模块区域(约行 287-306,`getMembershipLevels`/`getMyMembership` 附近)追加以下 8 个函数。在订阅模块区域(约行 1750-1755,`getSubscriptionPlans` 附近)追加缺失的 2 个。
- [ ] **Step 1: 追加 membership 模块缺失的 API 封装**
找到 `export const getCurrentLevel` 之后,在 `export const createOrder` 之前插入:
```javascript
// 激活试用会员
export const activateTrial = () => {
return request('/api/membership/trial', 'POST', {})
}
// 获取价格计划
export const getMembershipPlans = () => {
return request('/api/membership/plans', 'POST')
}
// 检查功能权限
export const canUseFeature = (feature) => {
return request('/api/membership/can-use', 'POST', { feature })
}
// 应用会员折扣
export const applyMemberDiscount = (amount) => {
return request('/api/membership/discount', 'POST', { amount })
}
```
- [ ] **Step 2: 追加 subscription 模块缺失的 API 封装**
找到 `export const checkBenefit` 附近,追加:
```javascript
// 我的权益列表(订阅权益使用记录)
export const getMyBenefits = () => {
return request('/api/subscription/benefits/my', 'POST')
}
// 确认订阅(参数式)
export const subscribe = (data) => {
return request('/api/subscription/subscribe', 'POST', data)
}
// 获取推广等级
export const getSubscriptionTier = () => {
return request('/api/subscription/tier', 'POST')
}
```
- [ ] **Step 3: 检查语法完整性**
```bash
node -e "require('./cfc-frontend/utils/api.js')" 2>&1 || true
# 或使用 npm run build:mp-weixin 验证
```
- [ ] **Step 4: Commit**
```bash
git add cfc-frontend/utils/api.js
git commit -m "fix: 补充 11 个缺失的会员/订阅 API 封装
activateTrial / getMembershipPlans / canUseFeature / applyMemberDiscount
getMyBenefits / subscribe / getSubscriptionTier"
```
---
### Task 3: plans.vue 从后端读取价格替代硬编码
**Files:**
- Modify: `cfc-frontend/pages/membership/plans.vue`
当前问题:页面硬编码了 ¥1,314/年和 ¥1,314/月 两个价格,后端 `/api/membership/plans` 已实现但未调用。
- [ ] **Step 1: 修改 plans.vue 的 script 部分**
在 data 中添加 `levels: []` 和 `loading: true`;在 `onLoad` 中调用 `getMembershipPlans`;使用响应式数据替换模板中的硬编码价格。
找到 script 部分类似 `export default { data() { return { ... } } }` 的结构,修改为:
```javascript
import { getMembershipPlans } from '../../utils/api'
export default {
data() {
return {
loading: true,
levels: [],
l0Features: [
'基础健康档案',
'每日任务管理',
'社区互动'
],
l1Features: [],
l2Features: []
}
},
onLoad: function() {
this.loadPlans()
},
methods: {
loadPlans: function() {
var self = this
getMembershipPlans().then(function(res) {
if (res.code === 200 && Array.isArray(res.data)) {
self.levels = res.data
// 从后端数据填充 feature 列表(从 level.features JSON 解析或使用默认值)
var l1 = res.data.find(function(l) { return l.levelCode === 'FAMILY' })
var l2 = res.data.find(function(l) { return l.levelCode === 'PROVIDER' })
if (l1) {
self.l1Features = ['全年会员(¥' + (l1.yearly / 100).toLocaleString() + ')']
}
if (l2) {
self.l2Features = ['高端会员服务']
}
}
self.loading = false
}).catch(function() {
self.loading = false
})
},
// 保留原有 purchasePlan 方法不变
purchasePlan: function(level) {
uni.navigateTo({ url: '/pages/membership/upgrade?level=' + level })
},
formatPrice: function(priceInCents) {
if (!priceInCents) return '0'
return '¥' + (priceInCents / 100).toLocaleString()
}
}
}
```
替换模板中硬编码的 `¥1,314` 为 `{{ formatPrice(planYearlyPrice) }}`。关键修改位置是计划卡片 L1 和 L2 的 `plan-price` 区域。
- [ ] **Step 2: 验证模板正确引用数据**
确认模板中使用 `v-for` 渲染 `levels` 数据,或至少将 L1/L2 价格绑定到从 API 获取的值而非字面量。
- [ ] **Step 3: Commit**
```bash
git add cfc-frontend/pages/membership/plans.vue
git commit -m "fix: plans.vue 从后端 /api/membership/plans 读取价格
替代硬编码的 ¥1,314/年 和 ¥1,314/月"
```
---
### Task 4: 创建订阅管理页面(小程序)
**Files:**
- Modify: `cfc-frontend/pages.json` — 注册 `subscription` 分包
- Create: `cfc-frontend/pages/subscription/index.vue` — 订阅管理首页(状态 + 取消 + 自动续费)
订阅管理页面包含:
- 当前订阅状态卡片(活跃/已过期/已取消 + 到期时间)
- 自动续费开关
- 取消订阅按钮
- 权益列表入口
- [ ] **Step 1: pages.json 注册 subscription 分包**
在 `pages/membership` 分包(约行 678)之后,追加:
```json
{
"root": "pages/subscription",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "订阅管理",
"navigationStyle": "custom"
}
}
]
},
```
- [ ] **Step 2: 创建 `pages/subscription/index.vue`**
组件结构:
```vue
到期时间
{{ formatDate(subscription.expireTime) }}
自动续费
暂无有效订阅
🎁 我的权益
›
```
- [ ] **Step 3: Commit**
```bash
git add cfc-frontend/pages.json cfc-frontend/pages/subscription/index.vue
git commit -m "feat: 创建订阅管理页面(小程序)
pages/subscription/index.vue: 订阅状态卡片、自动续费开关、取消订阅、权益入口"
```
---
### Task 5: 创建管理端订阅管理页面
**Files:**
- Modify: `cfc-web/src/router/index.js` — 添加订阅管理路由
- Create: `cfc-web/src/views/admin/SubscriptionManage.vue` — 订阅列表 + 详情
- Create/Modify: `cfc-web/src/api/subscription.js` — 管理端 API 封装
- [ ] **Step 1: 创建管理端 API 文件 `cfc-web/src/api/subscription.js`**
```javascript
import request from '@/utils/request'
// 订阅列表
export function getSubscriptionList(params) {
return request({
url: '/api/admin/subscription/list',
method: 'post',
data: params
})
}
// 手动取消订阅
export function adminCancelSubscription(data) {
return request({
url: '/api/admin/subscription/cancel',
method: 'post',
data: data
})
}
// 续费
export function adminRenewSubscription(data) {
return request({
url: '/api/admin/subscription/renew',
method: 'post',
data: data
})
}
```
- [ ] **Step 2: 创建 `cfc-web/src/views/admin/SubscriptionManage.vue`**
使用 Element UI 表格展示订阅列表(el-table),包含字段:ID / 家庭ID / 等级 / 状态 / 到期时间 / 自动续费 / 操作(取消/续费)。
- [ ] **Step 3: 添加路由**
在 `router/index.js` 的会员中心路由附近追加:
```javascript
{
path: 'subscription-manage',
name: 'SubscriptionManage',
component: () => import('@/views/admin/SubscriptionManage.vue'),
meta: { title: '订阅管理', perm: 'system:config' }
}
```
- [ ] **Step 4: Commit**
```bash
git add cfc-web/src/api/subscription.js cfc-web/src/views/admin/SubscriptionManage.vue cfc-web/src/router/index.js
git commit -m "feat: 管理端订阅管理页面
SubscriptionManage.vue: 订阅列表/取消/续费
cfc-web/src/api/subscription.js: 管理端 API 封装"
```
---
### Task 6: 小程序权益页对接后端 API
**Files:**
- Modify: `cfc-frontend/pages/membership/benefits.vue`
当前 `benefits.vue` 可能使用静态数据或未调用后端。修改为调用 `getMyBenefits()` 和 `getMySubscription()` 展示权益使用记录。
- [ ] **Step 1: 修改 benefits.vue script**
在 data 中添加 `benefitLogs: []`,`onLoad` 中调用 `getMyBenefits()`,循环渲染权益记录。
```javascript
import { getMyBenefits, getMySubscription } from '../../utils/api'
export default {
data() {
return {
loading: true,
subscription: null,
benefitLogs: []
}
},
onLoad() {
this.loadData()
},
methods: {
loadData() {
var self = this
Promise.all([
getMySubscription(),
getMyBenefits()
]).then(function(results) {
if (results[0].code === 200) {
self.subscription = results[0].data.subscription
}
if (results[1].code === 200 && Array.isArray(results[1].data)) {
self.benefitLogs = results[1].data
}
self.loading = false
}).catch(function() {
self.loading = false
})
}
}
}
```
- [ ] **Step 2: Commit**
```bash
git add cfc-frontend/pages/membership/benefits.vue
git commit -m "fix: benefits.vue 对接后端 /api/subscription/benefits/my
替代静态数据,展示权益使用记录"
```
---
## 执行顺序与依赖
```
Task 1 (DDL) ──→ 无依赖,可先执行
Task 2 (API) ──→ 无依赖,可先执行
Task 3 (plans) ──→ 依赖 Task 2 (getMembershipPlans)
Task 4 (订阅页) ──→ 依赖 Task 2 (getMySubscription/setAutoRenew/cancelSubscription)
Task 5 (管理端) ──→ 无依赖,但需后端 AdminSubscriptionController 已存在(✅ 已有)
Task 6 (权益页) ──→ 依赖 Task 2 (getMyBenefits/getMySubscription)
可并行执行: Task 1 + Task 2 + Task 5
Task 3/4/6 需在 Task 2 之后
```
## 自检清单
- [ ] Task 1 补充的 DDL 字段与实体类 `MembershipLevel.java` / `FamilyMembership.java` 字段一一对应
- [ ] Task 2 的 API 封装路径与后端 `@PostMapping` 路径一致(如 `/api/membership/trial`)
- [ ] Task 3 无硬编码价格残留
- [ ] Task 4 的 subscription 分包已在 pages.json 注册
- [ ] Task 5 的管理端路由在 admin layout 的 sidebar 中有入口
- [ ] Task 6 的 `benefits.vue` 引用 `getMyBenefits` 前已确认函数存在