Files
zhiyin/docs/PAYMENT-REDESIGN.md
T

300 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 产品改造方案:付费体系重构 + 数字人面试
> 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`
单一配置源,所有定价和套餐内容后台可编辑:
```json
{
"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`,不再散落在各处。
```typescript
// 配额服务
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`:增加 `interviewCredits`
- `optimize`:增加 `resumeOptimizeCredits`
- `download`:标记 `resume.paidDownload=true`
## 数字人面试架构
### 整体流程
```
用户选择岗位 → 配额检查 → AI 出题 → 回答
→ AI 评分+下一题 → ... → 完成 → 报告 → 头像回顾视频(可选)
```
每次 AI 回复时,同步调用 TTS 生成音频,返回给前端:
```typescript
// 面试回答响应
{
text: "请介绍一下你的项目经验",
audioUrl: "/tts/cache/abc123.mp3", // edge-tts 生成
durationMs: 2800, // 音频时长
animationTimings: [], // 可选:逐字时间戳
}
```
### TTS 服务
`backend/src/modules/tts/tts.service.ts`
```typescript
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 命令示例:
```bash
edge-tts --voice zh-CN-XiaoxiaoNeural --text "你好" --write-media /tmp/tts-cache/abc.mp3
```
### 前端数字人组件
`zhiyin-app/src/components/digital-human.vue`
```
┌─────────────────────┐
│ ┌───────┐ │
│ │ 头像 │ │ ← 静态 PNG + CSS呼吸/眨眼
│ │ (嘴型) │ │ ← Canvas 覆盖嘴部区域
│ └───────┘ │
│ "请介绍你的项目" │ ← 字幕气泡
│ │
│ [▼ 音频波形] │ ← 可选
└─────────────────────┘
```
嘴型驱动:
1. `AudioContext.createMediaElementSource(audioEl)``AnalyserNode`
2. 每帧读取 `analyser.getByteTimeDomainData(dataArray)`
3. 计算 RMS → 映射到嘴型开合度 (0~1)
4. 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`)
1. 读取 resume.contentmarkdown/HTML
2. 套用模板(含姓名、岗位、日期等)
3. `puppeteer.launch()``page.setContent(html)``page.pdf({ format: 'A4' })`
4. 返回 PDF buffer
5. 可选:同时生成 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 不过期