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 — 订阅控制器Files:
cfc-backend/src/main/resources/schema.sql — 追加 CREATE TABLEModify: cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java — 追加迁移
[ ] Step 1: schema.sql 追加 membership_levels 表定义
在 member_upgrade_records 表(行 ~1325)之后,-- 规划师服务模块表 之前插入:
-- 会员等级配置(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='会员等级配置';
紧跟 membership_levels 之后:
-- 家庭会员记录
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='家庭会员记录';
找到 runMigrations() 中最新迁移编号(搜索 // 迁移 确认最新号),追加:
// 迁移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: 编译验证
cd cfc-backend && mvn clean compile -q
[ ] Step 5: Commit
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 创建语句,数据库重建时会员系统会不可用"
Files:
cfc-frontend/utils/api.js在 api.js 的会员模块区域(约行 287-306,getMembershipLevels/getMyMembership 附近)追加以下 8 个函数。在订阅模块区域(约行 1750-1755,getSubscriptionPlans 附近)追加缺失的 2 个。
找到 export const getCurrentLevel 之后,在 export const createOrder 之前插入:
// 激活试用会员
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 })
}
找到 export const checkBenefit 附近,追加:
// 我的权益列表(订阅权益使用记录)
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: 检查语法完整性
node -e "require('./cfc-frontend/utils/api.js')" 2>&1 || true
# 或使用 npm run build:mp-weixin 验证
[ ] Step 4: Commit
git add cfc-frontend/utils/api.js
git commit -m "fix: 补充 11 个缺失的会员/订阅 API 封装
activateTrial / getMembershipPlans / canUseFeature / applyMemberDiscount
getMyBenefits / subscribe / getSubscriptionTier"
Files:
cfc-frontend/pages/membership/plans.vue当前问题:页面硬编码了 ¥1,314/年和 ¥1,314/月 两个价格,后端 /api/membership/plans 已实现但未调用。
在 data 中添加 levels: [] 和 loading: true;在 onLoad 中调用 getMembershipPlans;使用响应式数据替换模板中的硬编码价格。
找到 script 部分类似 export default { data() { return { ... } } } 的结构,修改为:
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 区域。
确认模板中使用 v-for 渲染 levels 数据,或至少将 L1/L2 价格绑定到从 API 获取的值而非字面量。
[ ] Step 3: Commit
git add cfc-frontend/pages/membership/plans.vue
git commit -m "fix: plans.vue 从后端 /api/membership/plans 读取价格
替代硬编码的 ¥1,314/年 和 ¥1,314/月"
Files:
cfc-frontend/pages.json — 注册 subscription 分包cfc-frontend/pages/subscription/index.vue — 订阅管理首页(状态 + 取消 + 自动续费)订阅管理页面包含:
权益列表入口
[ ] Step 1: pages.json 注册 subscription 分包
在 pages/membership 分包(约行 678)之后,追加:
{
"root": "pages/subscription",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "订阅管理",
"navigationStyle": "custom"
}
}
]
},
pages/subscription/index.vue组件结构:
<template>
<view class="sub-container">
<!-- 自定义导航栏 -->
<view class="custom-nav">
<view class="nav-header">
<view class="nav-back" @click="goBack"><text class="nav-back-icon">‹</text></view>
<text class="nav-title">订阅管理</text>
<view class="nav-placeholder"></view>
</view>
</view>
<scroll-view class="sub-list" scroll-y refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="loadData">
<!-- 当前订阅状态 -->
<view class="status-card" v-if="subscription">
<view class="status-header">
<text class="status-level">{{ subscription.level === 'L2' ? '💎 久久一生' : '❤️ 一生一世' }}</text>
<text :class="['status-badge', subActive ? 'active' : 'inactive']">{{ subActive ? '生效中' : '已失效' }}</text>
</view>
<view class="status-body">
<view class="status-row">
<text class="status-label">到期时间</text>
<text class="status-value">{{ formatDate(subscription.expireTime) }}</text>
</view>
<view class="status-row">
<text class="status-label">自动续费</text>
<switch :checked="subscription.autoRenew === 1" @change="onAutoRenewChange" />
</view>
</view>
<button class="btn-cancel" @click="onCancel" v-if="subActive">取消订阅</button>
</view>
<view class="no-sub" v-else>
<text class="no-sub-text">暂无有效订阅</text>
<button class="btn-go" @click="goPlans">查看会员计划</button>
</view>
<!-- 我的权益入口 -->
<view class="benefit-entry" @click="goBenefits">
<text class="benefit-title">🎁 我的权益</text>
<text class="benefit-arrow">›</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { getMySubscription, setAutoRenew, cancelSubscription } from '../../utils/api'
export default {
data() {
return {
refreshing: false,
subscription: null,
subActive: false
}
},
onLoad() { this.loadData() },
methods: {
loadData() {
var self = this
getMySubscription().then(function(res) {
if (res.code === 200 && res.data) {
self.subscription = res.data.subscription || null
self.subActive = res.data.hasActiveSubscription || false
}
self.refreshing = false
}).catch(function() {
self.refreshing = false
})
},
onAutoRenewChange(e) {
var self = this
setAutoRenew({ enabled: e.detail.value ? 1 : 0 }).then(function(res) {
if (res.code === 200) {
uni.showToast({ title: e.detail.value ? '已开启' : '已关闭', icon: 'success' })
self.loadData()
}
})
},
onCancel() {
var self = this
uni.showModal({
title: '确认取消',
content: '取消后订阅到期将不再续费,确定要取消吗?',
success: function(r) {
if (r.confirm) {
cancelSubscription({}).then(function(res) {
if (res.code === 200) {
uni.showToast({ title: '已取消', icon: 'success' })
self.loadData()
}
})
}
}
})
},
goBenefits() { uni.navigateTo({ url: '/pages/membership/benefits' }) },
goPlans() { uni.navigateTo({ url: '/pages/membership/plans' }) },
goBack() { uni.navigateBack() },
formatDate(d) { return d ? d.slice(0, 10) : '' }
}
}
</script>
<style scoped>
.sub-container { min-height: 100vh; background: #FFF7ED; }
.custom-nav { padding-top: var(--status-bar-height); background: #fff; }
.nav-header { display: flex; align-items: center; height: 88rpx; }
.nav-back { width: 88rpx; height: 88rpx; display: flex; align-items: center; justify-content: center; }
.nav-back-icon { font-size: 48rpx; color: #333; }
.nav-title { flex: 1; text-align: center; font-size: 34rpx; font-weight: 600; color: #333; }
.nav-placeholder { width: 88rpx; }
.sub-list { height: calc(100vh - var(--status-bar-height) - 88rpx); padding: 20rpx; }
.status-card { background: #fff; border-radius: 20rpx; padding: 30rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
.status-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24rpx; }
.status-level { font-size: 32rpx; font-weight: 600; color: #333; }
.status-badge { font-size: 24rpx; padding: 4rpx 16rpx; border-radius: 20rpx; }
.status-badge.active { background: #E8F5E9; color: #2E7D32; }
.status-badge.inactive { background: #FBE9E7; color: #BF360C; }
.status-body { display: flex; flex-direction: column; gap: 16rpx; margin-bottom: 24rpx; }
.status-row { display: flex; justify-content: space-between; align-items: center; }
.status-label { font-size: 28rpx; color: #666; }
.status-value { font-size: 28rpx; color: #333; }
.btn-cancel { width: 100%; height: 80rpx; border-radius: 40rpx; background: #fff; color: #FF4444; border: 2rpx solid #FF4444; font-size: 28rpx; display: flex; align-items: center; justify-content: center; }
.btn-cancel:active { opacity: 0.8; }
.no-sub { background: #fff; border-radius: 20rpx; padding: 60rpx 30rpx; text-align: center; }
.no-sub-text { font-size: 28rpx; color: #999; margin-bottom: 24rpx; display: block; }
.btn-go { background: #F97316; color: #fff; border: none; border-radius: 40rpx; padding: 20rpx 40rpx; font-size: 28rpx; }
.benefit-entry { margin-top: 20rpx; background: #fff; border-radius: 16rpx; padding: 30rpx; display: flex; justify-content: space-between; align-items: center; }
.benefit-title { font-size: 28rpx; font-weight: 500; color: #333; }
.benefit-arrow { font-size: 36rpx; color: #ccc; }
</style>
[ ] Step 3: Commit
git add cfc-frontend/pages.json cfc-frontend/pages/subscription/index.vue
git commit -m "feat: 创建订阅管理页面(小程序)
pages/subscription/index.vue: 订阅状态卡片、自动续费开关、取消订阅、权益入口"
Files:
cfc-web/src/router/index.js — 添加订阅管理路由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
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 / 等级 / 状态 / 到期时间 / 自动续费 / 操作(取消/续费)。
在 router/index.js 的会员中心路由附近追加:
{
path: 'subscription-manage',
name: 'SubscriptionManage',
component: () => import('@/views/admin/SubscriptionManage.vue'),
meta: { title: '订阅管理', perm: 'system:config' }
}
[ ] Step 4: Commit
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 封装"
Files:
cfc-frontend/pages/membership/benefits.vue当前 benefits.vue 可能使用静态数据或未调用后端。修改为调用 getMyBenefits() 和 getMySubscription() 展示权益使用记录。
在 data 中添加 benefitLogs: [],onLoad 中调用 getMyBenefits(),循环渲染权益记录。
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
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 之后
MembershipLevel.java / FamilyMembership.java 字段一一对应@PostMapping 路径一致(如 /api/membership/trial)benefits.vue 引用 getMyBenefits 前已确认函数存在