9.8 KiB
9.8 KiB
产品改造方案:付费体系重构 + 数字人面试
2026-06-11 · v1.0
概述
将现有一刀切的 remaining 免费额度模式,改为按产品线独立计费 + 会员套餐含额度 + 数据库配置化的灵活付费体系。
数据库模型变更
User Schema 新增字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
interviewCredits |
number | 1 | AI 数字人面试可用次数(含首次免费) |
resumeOptimizeCredits |
number | 0 | 简历优化可用次数 |
resumeDownloadCredits |
number | 0 | 简历下载可用次数 |
freeOptimizeUsed |
number | 0 | 已使用免费优化次数(上限 3) |
旧的 remaining 字段保留,仅限老用户兼容,新逻辑不再使用。
Resume Schema 新增字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
version |
number | 1 | 版本号,每次 AI 改写 +1 |
contentHash |
string | '' | 内容 MD5,用于判断是否改版 |
paidDownload |
boolean | false | 是否已购买该版本的下载权 |
contentHash 在 create 和 optimize 后自动更新。
PaymentOrder Schema 新增字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type |
string | 'membership' | membership / interview / optimize / download |
metadata |
Mixed | {} | 产品粒度信息(如 { resumeId, version }) |
定价配置(SiteConfig key=pricing)
单一配置源,所有定价和套餐内容后台可编辑:
{
"interview": {
"firstFree": true,
"pricePerSession": 500,
"creditsPerPurchase": 1
},
"resumeOptimize": {
"freeLimit": 3,
"pricePerOptimize": 300,
"creditsPerPurchase": 1
},
"resumeDownload": {
"pricePerDownload": 200,
"creditsPerPurchase": 1
},
"plans": {
"growth": {
"price": 1990,
"durationDays": 30,
"credits": {
"interview": 999,
"resumeOptimize": 20,
"resumeDownload": 10
},
"features": [
"免费版全部权益",
"AI 数字人面试无限次",
"详细面试报告(四维评分)",
"进步轨迹雷达图 + 打卡",
"每日一题推送",
"参考回答思路",
"公司真题库"
]
},
"sprint": {
"price": 4990,
"durationDays": 30,
"credits": {
"interview": 999,
"resumeOptimize": 50,
"resumeDownload": 30
},
"features": [
"成长版全部权益",
"AI 语音分析(语气词/语速检测)",
"技能缺口分析报告",
"学习路径推荐",
"真人导师 1v1 点评(每月 1 次)",
"简历精修(每月 1 次)",
"内推优先"
]
}
}
}
配额检查逻辑
所有配额检查收敛到统一的 QuotaGuard 或 QuotaService,不再散落在各处。
// 配额服务
class QuotaService {
checkInterview(user): // user.interviewCredits >= 1
checkOptimize(user): // user.resumeOptimizeCredits > 0
// || user.freeOptimizeUsed < freeLimit
checkDownload(user, resume): // resume.paidDownload
// || user.resumeDownloadCredits > 0
deductInterview(user)
deductOptimize(user)
deductDownload(user, resume)
}
各类用户行为对应的检查
| 行为 | 检查条件 | 扣减项 |
|---|---|---|
| 开始 AI 面试 | interviewCredits >= 1 |
interviewCredits -= 1 |
| AI 诊断/优化简历 | resumeOptimizeCredits > 0 || freeOptimizeUsed < 3 |
付费优先扣,免费次之 |
| 下载简历 PDF | resume.paidDownload || resumeDownloadCredits > 0 |
标记 resume.paidDownload |
支付流程扩展
现有:套餐购买(不变)
POST /payment/create { plan: 'growth' | 'sprint' }
→ 创建 Membership 订单(type='membership')
→ WeChat Pay
→ 回调/activate → 注入 plan 对应的全部 credits
新增:按次购买
POST /payment/create-product { type, resumeId?, count? }
→ 从 pricing config 读取单价
→ 创建对应 type 订单
→ WeChat Pay
→ 回调 → 根据 type 增加对应用户 credits / 标记下载
支持的产品:
interview:增加interviewCreditsoptimize:增加resumeOptimizeCreditsdownload:标记resume.paidDownload=true
数字人面试架构
整体流程
用户选择岗位 → 配额检查 → AI 出题 → 回答
→ AI 评分+下一题 → ... → 完成 → 报告 → 头像回顾视频(可选)
每次 AI 回复时,同步调用 TTS 生成音频,返回给前端:
// 面试回答响应
{
text: "请介绍一下你的项目经验",
audioUrl: "/tts/cache/abc123.mp3", // edge-tts 生成
durationMs: 2800, // 音频时长
animationTimings: [], // 可选:逐字时间戳
}
TTS 服务
backend/src/modules/tts/tts.service.ts
class TtsService {
async synthesize(text: string): Promise<{ audioPath: string; durationMs: number }> {
// 1. 按 text MD5 检查缓存
// 2. 未命中 → child_process.exec('edge-tts ...')
// 3. 保存到 /tmp/tts-cache/ 或 certs 同目录
// 4. 返回路径和时长
}
}
edge-tts 命令示例:
edge-tts --voice zh-CN-XiaoxiaoNeural --text "你好" --write-media /tmp/tts-cache/abc.mp3
前端数字人组件
zhiyin-app/src/components/digital-human.vue
┌─────────────────────┐
│ ┌───────┐ │
│ │ 头像 │ │ ← 静态 PNG + CSS呼吸/眨眼
│ │ (嘴型) │ │ ← Canvas 覆盖嘴部区域
│ └───────┘ │
│ "请介绍你的项目" │ ← 字幕气泡
│ │
│ [▼ 音频波形] │ ← 可选
└─────────────────────┘
嘴型驱动:
AudioContext.createMediaElementSource(audioEl)→AnalyserNode- 每帧读取
analyser.getByteTimeDomainData(dataArray) - 计算 RMS → 映射到嘴型开合度 (0~1)
- Canvas 绘制椭圆(开)或线(闭)
新接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /interview/create-avatar |
创建数字人面试(含配额检查) |
| POST | /interview/:id/answer-avatar |
回答+AI 回复(含 TTS audioUrl) |
| GET | /tts/:hash |
获取 TTS 音频文件 |
简历下载流程图
用户查看优化结果
│
├─ 点击「下载 PDF」
│ │
│ ├─ resume.paidDownload === true → 直接生成 → 返回 PDF
│ │
│ └─ resume.paidDownload === false
│ │
│ ├─ user.resumeDownloadCredits > 0
│ │ → 扣 1 credit → 标记 paidDownload → 生成 PDF
│ │
│ └─ 无 credits
│ → 弹出支付窗口(¥X/次)
│ → WeChat Pay → 标记 paidDownload → 生成 PDF
│
└─ 再次优化(内容改变)
→ contentHash 变化 → 新 resume 记录(version++)
→ paidDownload = false
→ 需重新付费下载
Puppeteer PDF 生成 (resume-pdf.service.ts):
- 读取 resume.content(markdown/HTML)
- 套用模板(含姓名、岗位、日期等)
puppeteer.launch()→page.setContent(html)→page.pdf({ format: 'A4' })- 返回 PDF buffer
- 可选:同时生成 Word(用 HTML → mammoth 或 libreoffice)
管理后台配置
在现有 admin.vue 的 Config Tab 中增加:
┌─────────────────────────────────────────────┐
│ 定价配置 [保存] │
│ │
│ AI 面试单价 ¥ [ 5 ] /次 │
│ 简历优化单价 ¥ [ 3 ] /次 │
│ 简历下载单价 ¥ [ 2 ] /次 │
│ 免费优化次数 [ 3 ] 次 │
│ 首次面试免费 [✓] │
│ │
│ ─── 成长版 ─── │
│ 价格 ¥ [ 19.9 ] /月 │
│ 面试额度 [ 999 ] 次 │
│ 优化额度 [ 20 ] 次 │
│ 下载额度 [ 10 ] 次 │
│ 功能列表 [编辑] │
│ │
│ ─── 冲刺版 ─── │
│ ... │
└─────────────────────────────────────────────┘
实施优先级
| 阶段 | 内容 | 文件涉及 |
|---|---|---|
| P0-1 | 定价配置化 + User 新字段 + 统一配额检查 | user.schema, SiteConfig, QuotaService |
| P0-2 | 简历下载付费 + Puppeteer PDF | resume.schema, resume.service, resume.controller, ResumePdfService |
| P0-3 | 支付扩展(按次购买) | payment-order.schema, payment.controller |
| P1-1 | TTS 服务 | tts.service, tts.controller, tts.module |
| P1-2 | 数字人面试前端组件 | digital-human.vue, interview.vue |
| P1-3 | 面试接口改版 + 配额接入 | interview.service, interview.controller |
| P2 | Admin 定价管理界面 | admin.vue, admin.controller |
兼容性注意事项
remaining字段保留,老用户数据不受影响- 新用户注册默认
interviewCredits=1(首次免费) - 会员激活时注入 plan 定义的 credits
- 过期降级时不清零已购买的 credits,仅阻止会员专属 privileges
- 已购买的单次 credits 不过期