# Phase 1 用户故事与验收标准
> 基于 `phase1-product-plan.md` 与 `1.0产品对齐方案.md`,每个用户故事包含:标题、描述、验收标准、优先级
---
## EPIC 1:命盘生成与展示(P0)
### US-1.1 生日输入与咨询发起
**作为** 数字能量师/C端用户
**我希望** 输入姓名和出生年月日,发起一次命盘咨询
**以便** 获取命盘展示和AI解读服务
**验收标准(输入表单):**
1. 输入页面包含:**被咨询者姓名**(选填,即命盘主人姓名)、出生年份(4位数字)、月份(1-12)、日期(1-31)
2. 月份和日期支持数字键盘输入,也有下拉选择器
3. 验证:年份必须为 1900-当前年份、月份 1-12、日期根据月份合法性验证
4. 输入无效时显示具体错误提示(如"请输入正确的出生日期")
5. "想了解的问题" 输入框限制 100 字以内(输入时实时显示剩余字数)
6. 点击"开始咨询"后,调用后端 `POST /api/consultation/start` 发起咨询
7. **已登录用户自动填入自己生日**(用户注册时已填 `birth_date`),但允许修改(能量师可输入客户生日)
**验收标准(咨询会话管理):**
7. 后端按照 **userId + birthday** 唯一确定一条咨询记录(同用户+同生日始终返回同一条记录)
8. 同一用户对同一生日重复点击"开始咨询",后端直接返回已有的咨询记录 + 完整聊天历史,**不创建新记录**;前端 toast 提示"已找到您之前的咨询记录"
9. 输入的生日是**全新**生日时,前端弹出确认对话框:"此生日将开启全新的命盘咨询,确认吗?"
10. 用户确认后,后端计算完整的数字命盘(24个位置),创建新咨询记录,并返回结果
11. 用户取消确认,停留在首页,不跳转、不创建
12. 点击"开始咨询"到命盘渲染完成,总耗时不超过 3 秒
13. 命盘计算全部由后端 `CalculatorService` 完成,前端只负责展示(`calculator.js` 保留为降级兜底,不作为主路径)
14. 命盘页面 URL 可分享
### US-1.2 三角形命盘可视化展示
**作为** 数字能量师
**我希望** 看到一个清晰、美观的数字三角形,每个位置都标有数字
**以便** 直接用于客户讲解
**验收标准:**
1. 页面展示完整的24个数字位置(8个底部输入位 A-H + 7个三角内部位 I-O + 9个外部三组位 P-X)
2. 内部三角形布局严格遵循(新字母命名):
```
O
/ \
M N
/ \ / \
I J K L
```
3. 底部输入框布局:日(2位)| 月(2位)| 年(4位),从左到右排列(已改为"日月年"顺序)
4. 外部三组以T形线段连接三角内部对应位置:
- 左侧组(R, P, Q)对应 21-40 岁,由 I, J, M 计算
- 右侧组(U, S, T)对应 61+ 岁,由 K, L, N 计算
- 顶部组(X, V, W)对应 41-60 岁,由 M, N, O 计算
5. 外部三组使用独立的计算值,非内部数字的直接重复
6. 每个数字使用单独的圆形/方形卡片展示,字体清晰易读
7. 五区使用不同背景色区分(左区/中区/右区/父源/母源/主性格)
8. 数字颜色:1-9 每个数字有独立颜色(参考能量数字配色)
9. 卓越数(11/22/33)用特殊标记突出
10. 底部显示姓名和出生日期信息
### US-1.3 命盘数字位置命名与计算规则(对照表)
**作为** 开发团队
**我希望** 有一份完整的位置命名与计算规则对照表作为开发基准
**以便** 前后端计算逻辑一致,避免歧义
#### 1. 完整字母命名表(A-X)
##### 输入层(8位)—— 生日原始数字
| 字母 | 来源 | 示例 1990-01-15 | 说明 |
|------|------|----------------|------|
| A | 年份千位 | 1 | 年[0] |
| B | 年份百位 | 9 | 年[1] |
| C | 年份十位 | 9 | 年[2] |
| D | 年份个位 | 0 | 年[3] |
| E | 月份十位 | 0 | 月[0] |
| F | 月份个位 | 1 | 月[1] |
| G | 日期十位 | 1 | 日[0] |
| H | 日期个位 | 5 | 日[1] |
##### 内部计算层(7位)
| 字母 | 公式 | 中文名 | 旧名对照 | 视觉层级 |
|------|------|-------|---------|---------|
| I | `reduce(E+F)` | 月能量 | 旧 F | 底部左1 |
| J | `reduce(G+H)` | 日能量 | 旧 G | 底部左2 |
| K | `reduce(A+B)` | 年前半 | 旧 H | 底部右1 |
| L | `reduce(C+D)` | 年后半 | 旧 I | 底部右2 |
| M | `reduce(I+J)` | 青年综合数 | 不变 | 中层左 |
| N | `reduce(K+L)` | 晚年综合数 | 不变 | 中层右 |
| O | `reduce(M+N)` | **主性格数** | 不变 | 顶层 |
##### 外部三组(9位)
| 字母 | 公式 | 中文名 | 年龄区间 | 所属组 |
|------|------|-------|---------|-------|
| P | `reduce(I+M)` | 左侧左子(left-L) | 21-40 | 左侧 |
| Q | `reduce(J+M)` | 左侧右子(left-R) | 21-40 | 左侧 |
| R | `reduce(P+Q)` | **左侧主数(left)** | 21-40 | 左侧 |
| S | `reduce(K+N)` | 右侧左子(right-L) | 61+ | 右侧 |
| T | `reduce(L+N)` | 右侧右子(right-R) | 61+ | 右侧 |
| U | `reduce(S+T)` | **右侧主数(right)** | 61+ | 右侧 |
| V | `reduce(M+O)` | 顶部左子(top-L) | 41-60 | 顶部 |
| W | `reduce(N+O)` | 顶部右子(top-R) | 41-60 | 顶部 |
| X | `reduce(V+W)` | **顶部主数(top)** | 41-60 | 顶部 |
#### 2. 视觉布局示意
```
X
↙ ↘
V W
↙ ↘
M ───────────── N
↙ ↘ ↙ ↘
P Q S T
↙ ↘ ↙ ↘
I ─────────── J K ─────────── L
R (21-40) U (61+)
底部输入: 日 G H | 月 E F | 年 A B C D
```
#### 3. 外部三组年龄映射
| 组 | 位置 | 年龄区间 | 关联内部位 |
|----|------|---------|----------|
| 左侧组 (R, P, Q) | 三角形左外侧 | **21-40岁** | 基于 I, J, M |
| 顶部组 (X, V, W) | 三角形顶外侧 | **41-60岁** | 基于 M, N, O |
| 右侧组 (U, S, T) | 三角形右外侧 | **61+岁** | 基于 K, L, N |
#### 4. 计算精度说明
- `reduce(N)` 定义:各位数字相加归约至个位,但 11/22/33 保留为卓越数
- 所有 `+` 操作符均先执行普通加法,再调用 `reduce()` 归约
- 外部三组**必须使用独立的归约计算**,不得直接引用内部数字值
#### 5. 后端 API 返回结构(`POST /api/consultation/start` 响应示例)
```json
{
"recordId": 42,
"isNew": false,
"chartData": {
"positions": {
"I": 1, "J": 2, "K": 3, "L": 4,
"M": 5, "N": 6, "O": 7,
"P": 8, "Q": 9, "R": 1,
"S": 2, "T": 3, "U": 4,
"V": 5, "W": 6, "X": 7
},
"mainCharacter": 7,
"isMasterNumber": false
},
"messages": []
}
```
---
### US-1.4 数字点击查看含义
**作为** 数字能量师
**我希望** 点击三角形中的每个数字可以查看该位置的含义和能量解读
**以便** 快速向客户解释命盘
**验收标准(交互):**
1. 点击三角形中任一数字卡片(A-X 任一位置),从屏幕底部弹出该位置的详细面板
2. 点击效果分为两层:
- 轻点数字卡片 → 弹出解释面板
- 面板弹出时,数字卡片高亮状态(边框发光 + 放大 1.1 倍)
3. 打开新面板时自动关闭前一个面板
4. 关闭方式:点击面板外部遮罩区 / 点击关闭图标 / 再次点击同一数字
5. 关闭面板后高亮状态取消
**验收标准(面板内容):**
6. 面板标题区域显示位置名称(如"主性格")、字母编号(如 O)、对应数字
7. 面板内容按 Tab 切换展示:
- **Tab 1 - 含义**:该位置的核心能量含义(如"主性格:代表一个人与生俱来的天赋和性格特质")
- **Tab 2 - 特征**:该数字在该位置的具体性格/运势描述(如"O=7 代表分析力强、追求真理")
- **Tab 3 - 建议**:该位置该数字的注意事项和提升建议
8. 面板底部显示数字的能量强弱 / 吉凶指示(如"吉数 ★★★★☆")
9. 若该位置数字与相邻位置形成特定组合,面板顶部提示"该数字与 X 形成 XX 组合",可点击查看组合含义
10. 面板内容中的"组合"和"特征"描述,后续可通过管理后台配置
**验收标准(交互细节):**
11. 面板高度不超过屏幕 60%,内容可滚动
12. 面板弹出动画:从底部平滑滑入(300ms ease-out)
13. 连续快速点击不同数字时,面板内容直接替换,不重复弹入动画
14. 面板支持手势下滑关闭(drag-to-dismiss)
---
## EPIC 2:分享与导出(P0)
### US-2.1 命盘分享图片
**作为** 数字能量师
**我希望** 将命盘生成为一张精美的图片,分享到微信或保存到相册
**以便** 发给客户或在朋友圈展示
**验收标准(入口与权限):**
1. 分享按钮位于命盘展示页底部,始终可见
2. 点击分享按钮,底部弹出分享方式选择菜单:
- "保存到相册"
- "分享给微信好友"
- "分享到朋友圈"
3. 免费用户每日限制分享 1 次,点击后显示剩余次数(如"今日还剩 1 次")
4. 免费用户用完今日次数后,按钮置灰,提示"升级能量师享无限次分享"
5. 已付费用户不限次数,不显示剩余次数
**验收标准(分享卡片生成):**
6. 分享卡片在客户端生成(使用 canvas 绘图),不依赖后端
7. 卡片内容包含:
- 顶部:品牌 Logo + "数字能量命理分析" 标题
- 中部:三角命盘图(颜色和样式与 app 内一致)
- 底部:用户姓名 + 出生日期 + 生成日期
- 右下角:小字水印(平台名称/二维码)
8. 卡片设计使用样式指南中的品牌色系统(主色 #B8860B / 金色系)
9. 横向卡片比例 4:3,宽度适配主流手机屏幕(建议 1080px 基准)
**验收标准(保存与分享):**
10. 保存到相册:调用 `uni.saveImageToPhotosAlbum`,保存成功后 toast 提示"已保存到相册"
11. 分享给微信好友:调用 `uni.share`(或小程序原生转发),携带卡片图片和默认文案"看看你的数字能量命盘"
12. 分享到朋友圈:调用小程序端朋友圈分享 API(走官方渠道)
13. 分享后返回 app 时,若为免费用户则扣除当日次数,显示更新后的剩余次数
**验收标准(限制与风控):**
14. 免费用户每日分享**到微信/朋友圈**的次数在服务端记录,防止客户端篡改
15. 分享次数限制仅对"分享到微信/朋友圈"生效;**保存到相册**不限制次数(客户端canvas生成无法强控)
16. 每日 0 点重置次数,后端接口 `POST /api/user/share-quota` 返回当日剩余次数
17. 分享卡片不得包含用户微信号、手机号等敏感信息
### US-2.2 命盘PDF导出
**作为** 数字能量师
**我希望** 将命盘导出为 PDF 文件,可以直接打印或微信发送
**以便** 客户获得正式的纸质/电子版报告
**验收标准(权限与入口):**
1. "导出 PDF" 按钮位于命盘展示页顶部操作栏(仅已付费用户可见)
2. 未付费用户点击不可见或置灰 + 提示"升级能量师可导出 PDF 报告"
3. 点击后出现加载指示器(loading + 进度百分比),防止重复点击
4. 生成过程不超过 5 秒,超过 5 秒则显示"生成较慢,请稍候…"
**验收标准(PDF 内容与排版):**
5. PDF 采用 A4 竖版(210mm × 297mm),页边距上下 15mm、左右 12mm
6. 第一页内容按以下布局:
- 头部:品牌 Logo + "数字能量命理分析报告" 标题(居中)
- 副标题:生成日期 + 报告编号(格式:YYYYMMDD-XXXXX)
- 用户信息区:姓名 / 出生日期 / 主性格数字
- 三角命盘图:占页面 40% 高度,清晰可辨各位置数字
- 表格区:24 个位置的字母编号、数字值、位置名称三列展示
- 左中右三区标注:左侧(21-40 岁)/ 顶部(41-60 岁)/ 右侧(61+ 岁)
7. PDF 使用品牌色系(金色 #B8860B 为标题色,深棕色 #4A3728 为正文字体)
8. PDF 支持中文显示,字体嵌入(或使用系统黑体/宋体)
9. 如有命盘批注内容(US-7.1),在 PDF 第二页以附录形式呈现
**验收标准(导出流程):**
10. PDF 由后端生成(通过 iText 或 Apache PDFBox 库),前端仅发起请求
11. 前端调用 `POST /api/export/pdf` 传入 `chartId`,后端返回 PDF 文件流
12. 前端接收文件流后,使用 `uni.openDocument` 打开微信文件预览
13. 微信文件预览界面支持:转发给好友 / 保存到本地 / 发送到电脑
14. 导出记录保存在数据库 `export_logs` 表,便于统计
**验收标准(限制):**
15. 已付费用户导出 PDF **不限制次数**
16. 同一命盘重复导出不重新生成,直接返回已缓存的文件(缓存有效期 7 天)
---
## EPIC 3:AI 解读(P0)
### US-3.1 AI 解读展示
**作为** 数字能量师
**我希望** 在命盘生成后,看到由AI自动生成的完整文字解读
**以便** 直接发给客户或稍作润色后使用,节省自己查资料写解读的时间
**验收标准:**
1. 命盘展示页底部有"查看AI解读"按钮,点击跳转到AI解读页
2. 解读内容通过 **Dify Workflow**(工作流)生成,技术架构详见**附录B**
3. 前端调用后端 `POST /api/chart/interpret`,后端转发至 Dify Workflow API(`POST /v1/workflows/run`)
4. 首次加载等待时间 < 10 秒(含 Dify Workflow 执行时间);超过 8 秒显示"AI 正在深度分析中,请稍候…"
5. 加载过程中显示骨架屏或loading动画,避免用户以为卡死
6. 解读内容至少包含以下章节:
- **主性格解读**:顶端数字的核心特质、性格描述、代表人物
- **左区(21-40岁)**:早年运势、成长环境(对应 P/Q/R 位置)
- **顶部(41-60岁)**:中年事业、人际关系(对应 V/W/X 位置)
- **右区(61+岁)**:晚年成就、财运趋势(对应 S/T/U 位置)
7. 每个章节独立卡片展示,可折叠展开
8. 解读内容基于命盘的实际数字,同一数字对不同命盘解读不同(位置差异)
9. 页面底部显示"本解读由AI生成,仅供参考"的免责声明
10. Dify Workflow 输出解析:后端收到 Dify 返回的 `data.outputs` JSON 后,原样或稍作格式化后返回前端
### US-3.2 AI 解读的免费/付费控制
**作为** 平台运营者
**我希望** 控制AI解读功能的免费与付费边界
**以便** 激励用户订阅C端年费或能量师年费
**验收标准:**
1. 未付费用户点击"查看AI解读"时,仅显示**主性格概要**(1段文字)
2. 未付费用户每日可查看主性格概要 1 次(**此配额与 US-3.4 的 3 轮 AI 问答独立计数,互不消耗**)
3. 五区完整解读仅在已付费后可用(C端¥131或能量师¥1,314+)
4. 解读页底部显示"¥131 开通完整解读"引导卡片(免费用户可见)
5. 已付费用户可无限次查看完整解读
6. 每次解读请求记录到数据库,用于成本控制和限流
### US-3.4 AI 交互问答(免费/付费控制)
**作为** 平台运营者
**我希望** 控制AI问答互动功能的免费次数,且限定在同一个命盘上
**以便** 防止用户通过切换生日绕过每日限制,同时保证体验清晰可预期
**验收标准(配额控制):**
1. 未付费用户每日可进行 3 轮 AI 问答互动(每日配额全局统一,不按命盘拆分)
2. 此 3 轮互动**绑定到当前咨询的命盘上**,用户不能通过创建多个生日命盘来获得额外免费次数
3. 已付费用户不受次数限制
4. 用户在命盘页顶部看到剩余次数提示:"今日 {{usedChats}}/{{maxChats}} 轮"
5. 当用户在已用满3次的命盘上新建**另一个生日**的咨询时:
- 前端弹出确认对话框
- 对话框提示:"此生日将开启全新的命盘咨询,今日剩余互动次数仍为 0 次,确定吗?"
- 用户确认后创建新咨询,次数不重置
6. 超出每日次数后,AI 输入框和快捷问题按钮隐藏,显示引导卡片:"今日 AI 解读次数已用完,开通会员享无限次"
7. 每日次数在 `lastQuotaDate` 跨日时自动归零
**验收标准(Dify Chatflow 集成):**
8. 问答交互通过 **Dify Chatflow**(对话工作流)实现,详见**附录B**
9. 前端调用 `POST /api/chat/send` 发送用户消息,传入 `{ chartId, message }`
10. 后端 `ChatService` 将消息转发至 Dify Chatflow API(`POST /v1/chat-messages`)
11. Chatflow 保持多轮对话上下文(通过 Dify 的 `conversation_id`),无需后端自行维护会话历史
12. 后端在首次向 Dify 发送消息时,将命盘 24 个数字(A-X)作为 Chatflow 的 `inputs` 传入,后续轮次无需重复传入
13. Dify Chatflow 返回的答案中,如果包含数字能量学专业术语,后端不做二次处理,直接透传
14. 后端每次问答调用前检查用户配额(未付费用户当日≤3轮),超限则不调用 Dify,直接返回错误码
### US-3.3 解读内容缓存
**作为** 系统
**我希望** 同一命盘的重复解读请求不重复调用LLM API
**以便** 控制成本,避免同一命盘每次查看都消耗API费用
**验收标准:**
1. 首次生成解读后,将解读内容与命盘ID绑定存储到数据库
2. 后续同一命盘再次查看解读时,直接从数据库读取,不调用LLM
3. 用户可在AI解读页右上角菜单中点击"重新生成解读",覆盖旧的缓存内容(重新调用 Dify Workflow)
4. 解读缓存永久保留,不自动过期
5. 重新生成时弹出确认框:"重新生成将覆盖已有解读内容,确定吗?"
---
## EPIC 4:付费与订阅(P0)
### US-4.1 能量师付费订阅
**作为** 数字能量师
**我希望** 看到清晰的定价方案并进行年费支付
**以便** 获得AI完整解读、分销推广等全部功能
**验收标准:**
1. 付费入口:未付费用户在首页/命盘页/解读页/分享时均会看到升级引导
2. 付费页展示:
- 功能对比表(普通用户 vs 能量师)
- 价格:种子价 ¥1,314 / 标准价 ¥1,986(通过 `POST /api/pricing/current` 获取)
- 种子价提示:根据 `seedReason` 展示不同文案
- `eligible`:"仅剩 XX 个种子名额" + "有效期至 YYYY-MM-DD"
- `not_founder_code` / `no_referrer`:"种子价仅限创始人邀请用户"
- `expired`:"种子价活动已结束"
- `quota_full`:"种子名额已满"
- "立即开通"支付按钮
3. 通过微信支付完成订阅
4. 支付成功后,用户状态即时更新(`vipEndTime += 365天`)
5. 支付成功后,自动生成专属6位推广码(如无已有)
6. 支付成功后,自动跳转到分销面板(或引导页)
7. 支付失败显示友好提示
### US-4.2 种子价自动判断
**作为** 平台
**我希望** 系统自动判断用户是否享受种子价
**以便** 运营策略自动化,无需人工干预
**种子会员定义:**
种子会员 = 通过扫描**创始人码**注册并付费成为能量师的用户。创始人码 = **没有推荐人的能量师**(`invitedBy IS NULL` 且 `vipType='practitioner'`)的推广码。
> 即:用户 U 的直接推荐人 A 满足 `A.invitedBy IS NULL AND A.vipType='practitioner'` 时,U 扫的是创始人码。
**验收标准:**
1. 用户在支付页看到的价格由后端接口 `POST /api/pricing/current` 返回,前端不做判断
2. `isSeedPrice = true` 需**同时满足三重条件**:
- **条件① 注册渠道**:用户的直接推荐人(`invitedBy`)是创始人(`invitedBy IS NULL AND vipType='practitioner'`)的自然注册用户(无推荐人,`invitedBy IS NULL`)**不享受种子价**
- **条件② 有效期**:当前时间 ≤ `sys_config.pricing.practitioner.seed_period_end`(种子价有效期截止时间)
- **条件③ 名额**:已通过创始人码付费的能量师记录数 < `sys_config.pricing.practitioner.seed_limit`(默认 300)
3. 三个条件任一不满足 → `isSeedPrice = false`,返回标准价
4. 种子价名额计数方式:统计 `orders` 表中 `product_type='practitioner'` AND `status='paid'` AND `is_seed_price=true` 的记录数
5. 订单创建时将种子价状态锁定(`is_seed_price` 写入订单,不因后续条件变化而改变)
6. 有效期过期后,即使名额未满,所有新用户看到标准价
7. 名额满后,即使仍在有效期内,所有新用户看到标准价
8. 种子价名额上限和有效期均可在管理后台动态修改(`pricing.practitioner.seed_limit`、`pricing.practitioner.seed_period_end`)
### US-4.3 订阅状态与续费
**作为** 付费能量师
**我希望** 在个人中心看到我的订阅状态、到期日,并能续费
**以便** 管理我的会员身份
**验收标准:**
1. 个人中心("我的"页面)显示用户头像、昵称、角色标签(普通用户/C端年费/能量师 🌟)
2. 角色标签使用暗金色 #C9A84C,字体 12px
3. 用户卡片下方按角色显示**会员状态卡片**:
- **普通用户**(`vipType=NULL`):品牌色(墨蓝 #1E3A5F)渐变卡片 + 权益摘要(3-4项)+ "立即开通 ¥131/年"按钮
- **C端年费**(`vipType='annual'`,有效期内):橙色渐变卡片 + "已开通C端会员" + 有效期 + "升级能量师"按钮
- **能量师**(`vipType='practitioner'`,有效期内):紫色渐变卡片 + "能量师会员 🌟" + 有效期 + "续费"按钮
- **已过期**(`vipEndTime < now`):灰色卡片 + "已过期"标记 + "重新开通"按钮
4. 种子价会员在状态卡片下方额外显示"🌱 种子价专属标记"(暗金文字,淡金色背景)
5. 到期前 7 天在状态卡片上方显示黄色 banner:"⏰ 您的会员即将到期,请及时续费"
6. 资料不完整的用户(`profile_incomplete = true`)在状态卡片上方显示引导 banner:"📝 请完善个人资料,开启能量匹配"
7. 到期后自动降级为普通用户,分销面板入口隐藏
8. 续费操作按**标准价**执行(种子价仅限首次)
9. 续费成功后有效期在原到期日基础上延长1年
### US-4.4 年费升级能量师(升级定价与流程)
**作为** C端年费用户
**我希望** 从年费升级为能量师时,按已付年费的剩余价值抵扣差价
**以便** 不需要重复支付已经买过的部分
**验收标准(升级定价):**
1. 用户现有 `vipType='annual'` 且 `vipEndTime > now`,可在"我的"页面 → 会员状态卡片(C端年费)点击"升级能量师"按钮
2. 升级价格由后端计算,公式:
```
已付年费金额 = 该用户最近一笔 annual 订单的 amount(默认 ¥131)
已用天数 = 从 annual 支付成功日到当前日的天数
剩余价值 = 已付年费金额 × (365 - 已用天数) / 365
升级价格 = 当前能量师定价(种子/标准)− 剩余价值
```
3. 计算示例:用户 B 在 1月1日付 ¥131,第100天升级(种子价 ¥1,314):
```
剩余价值 = ¥131 × (365-100)/365 = ¥95.11
升级价格 = ¥1,314 − ¥95.11 = ¥1,218.89
```
4. 升级价格 < 0(理论极限)→ 按 ¥0.01 收取
5. 升级后 `vipType` 从 `'annual'` 变更为 `'practitioner'`
6. 升级后 `vipEndTime` 按能量师续费规则处理(在原能量师到期日 +365天,不叠加年费剩余天数)
7. 升级不享受种子价(种子价仅限通过创始人码首次购买 practitioner,见 US-4.2)
8. 升级后推广码不变(如之前已有推广码)
**验收标准(前端流程):**
9. 个人中心 → 升级能量师 → 展示升级详情卡片:
- 原年费支付金额:¥131
- 年费剩余价值:¥95.11("已使用 100/365 天")
- 能量师当前定价:¥1,314
- 应付差价:¥1,218.89
- 支付按钮:"支付 ¥1,218.89"
10. 升级详情卡片下方附带**权益对比**(精简表格),说明能量师相比年费额外获得的能力:推广能量师赚固定佣金(¥500+¥100)、客户批注与标签管理等
11. 点击支付 → 调 `POST /api/pay/create` 传 `{ productType: 'practitioner', isUpgrade: true, previousOrderId: xxx }`
12. 支付成功后调 `paySuccess()` → `vipType` 更新 + 佣金结算(见 US-6.3 升级场景)
**验收标准(接口):**
13. `POST /api/pricing/upgrade` 新增接口:传入 `userId`,返回升级价格详情
14. `POST /api/pay/create` 新增字段:
- `isUpgrade: boolean`
- `previousOrderId: Long`(原年费订单ID,用于佣金补差)
---
## EPIC 5:用户注册与资料(P1)
### US-5.1 微信登录 + 注册信息完善
**作为** 用户
**我希望** 通过微信授权一键登录,随后补充生日和性别完成注册
**以便** 使用命盘分析和未来的能量匹配社交功能
**验收标准(微信授权登录):**
1. 首次使用点击登录按钮时,弹出微信授权(`uni.login` 获取 code)
2. 授权后自动获取微信昵称和头像(`uni.getUserProfile`),用户可修改
3. 用户拒绝授权昵称/头像时,仍然可以使用 code 登录,昵称默认为"微信用户"
4. 后续使用自动登录(token 未过期时跳过授权)
5. 首次登录后端返回 `{ token, isNewUser: true, profileIncomplete: true }`
6. 老用户登录返回 `{ token, isNewUser: false, profileIncomplete: false }`
7. 老用户(旧版未填生日/性别的)返回 `{ token, profileIncomplete: true }`,引导补全资料
**验收标准(注册信息完善页):**
8. 新用户或资料不全的用户,微信授权后自动跳转到**注册完善页**(非首页)
9. 注册完善页表单包含以下字段:
| 字段 | 必填 | 预填 | 说明 |
|------|------|------|------|
| 头像 | 是 | 微信头像 | 可点击更换(从相册选择) |
| 昵称 | 是 | 微信昵称 | 1-20 字 |
| 性别 | 是 | 空 | 男 / 女,radio 选择 |
| 出生日期 | 是 | 空 | 年月日选择器,同命盘生日 |
| 个人简介 | 否 | 空 | 限 100 字,用于社交展示 |
| 所在城市 | 否 | 空 | 微信定位或手动选择 |
| 兴趣标签 | 否 | 空 | 多选:读书/运动/音乐/旅行/禅修/创业/心理学/玄学 |
| 想认识 | 否 | 空 | 单选:不限 / 朋友 / 导师 / 同修 |
10. 点击"提交"调用 `POST /api/profile/complete` 保存所有资料
11. 提交后标记用户 `profile_complete = true`
12. 提交成功后跳转到首页,进入正常使用流程
13. 资料不完整的用户在个人中心显示引导 banner:"请完善个人资料,开启能量匹配"
14. 用户可随时在个人中心 → 编辑资料 修改所有字段
**验收标准(后端与数据库变更):**
15. `users` 表新增字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `gender` | `TINYINT` | 0=未知, 1=男, 2=女 |
| `birth_date` | `DATE` | 出生日期(用于命盘+社交) |
| `bio` | `VARCHAR(200)` | 个人简介 |
| `city` | `VARCHAR(50)` | 所在城市 |
| `tags` | `VARCHAR(200)` | 兴趣标签,逗号分隔 |
| `looking_for` | `TINYINT` | 0=不限, 1=朋友, 2=导师, 3=同修 |
| `profile_complete` | `TINYINT(1)` | 0=资料未完善, 1=资料已完善 |
| `birth_year` | `INT` | 出生年份(冗余,从 birth_date 提取) |
| `birth_month` | `INT` | 出生月份(冗余) |
| `birth_day` | `INT` | 出生日期(冗余) |
16. `POST /api/auth/login` 响应增加 `profileIncomplete` 字段
17. 新增 `POST /api/profile/complete` 接口,接收所有注册字段
18. 新增 `POST /api/profile/update` 接口,允许修改任意字段
19. `POST /api/profile/info` 响应增加全部新字段,额外新增 `isSeedPriceUser`(Boolean — 当前用户是否以种子价购买的能量师)
**验收标准(老用户兼容):**
20. 已注册的老用户登录时,如果 `profile_complete = false`,登录后显示完善引导
21. 老用户不强制立即完善,可以正常使用命盘功能
22. 老用户在个人中心中看到"完善资料"入口和引导
### US-5.2 历史咨询记录
**作为** 能量师
**我希望** 查看我过去的所有咨询记录
**以便** 回顾和继续之前的咨询
**验收标准:**
1. "我的"页面 → "我的服务"分组 → "📋 我的分析"入口 → 跳转到咨询记录列表页
2. 列表按最后互动时间倒序排列
3. 每条记录显示:姓名、生日、咨询时间、最后一条消息摘要
4. 点击记录可跳转到对应的命盘展示页,并恢复完整的聊天历史
5. 支持单条删除
6. 同一用户+同一生日永远返回同一条咨询记录(`(userId, birthday)` 联合唯一)
7. 免费用户仅能看到近 7 天记录,已付费用户可见全部
### US-5.3 个人资料编辑(为陌生社交准备)
**作为** 用户
**我希望** 在个人中心编辑我的昵称、头像、简介、标签、城市等资料
**以便** 未来通过能量匹配结识志同道合的朋友
**验收标准:**
1. "我的"页面 → 用户卡片 → 头像旁的✏️图标(或文字链接)→ 跳转到编辑资料页
2. 编辑页包含:头像、昵称、性别、生日、简介、城市、兴趣标签、想认识
3. 资料字段定义同 US-5.1 注册完善页
4. 修改后调用 `POST /api/profile/update`
5. 修改成功后本地更新并提示"保存成功"
6. **陌生社交广场和能量匹配功能不属于 Phase 1,此处仅预留用户资料字段**
注:能量匹配社交将根据用户的命盘主性格数(O)、外部三组数(R/U/X)等计算能量兼容度,推荐匹配用户。此功能在 Phase 2 规划。
---
## EPIC 6:分销推广与佣金(P0-P1)
### US-6.1 推广码生成(付费后生成)
**作为** 付费用户(C端 / 能量师)
**我希望** 在首次付费后自动获得专属推广码
**以便** 分享给微信好友来发展下级并赚取佣金
**验收标准:**
1. 推广码在**注册时不生成**,仅在**首次支付成功后**生成(¥131年费 / ¥1,314+能量师费任一触发)
2. 推广码规则:6位大写字母+数字,字符集去掉易混淆的 `O/0/I/1`(即 `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`)
3. 推广码冲突时自动重新生成,保证全局唯一
4. 推广码永久有效,不随年费到期失效
5. 普通用户(未付费)没有推广码,也没有分销入口
6. 推广码展示在分销面板顶部,支持一键复制和生成推广海报
7. 推广码下游用于:分享带参注册(US-6.2)和佣金结算(US-6.3 / US-6.4)
---
### US-6.2 分享带参注册与上下级绑定
**作为** 平台
**我希望** 当用户通过推广链接/扫码打开小程序时,自动记录上下级关系
**以便** 为佣金分成打好基础
**验收标准:**
**分享链路:**
1. 推广链接格式:`/pages/index/index?ref=ABC123`(6位推广码)
2. 分享卡片 / 小程序码同样携带 `ref` 参数
**前端缓存策略:**
3. 用户通过链接打开小程序时:
- `onLaunch(options)` 读取 `options.query.ref`,转为大写后写入缓存 `pending_referrer`
- **每次点击新链接都覆盖**已有值,以用户**最后一次点击的链接**为准(新点击意图覆盖旧的)
4. `pending_referrer` **永不过期**,直到注册成功后才清除
5. 用户注册(微信授权登录)时,从缓存读取 `pending_referrer` 传入注册接口
**后端注册逻辑(`UserService.loginOrRegister()`):**
6. 用户不存在(新用户)+ `referrerCode` 有效 → `invitedBy = 上级ID`
7. 用户不存在 + `referrerCode` 无效 → `invitedBy = null`(静默降级,不报错)
8. 用户不存在 + `referrerCode` 为空 → `invitedBy = null`(正常注册)
9. 用户不存在 + `referrerCode` 为**自己的新openid** → `invitedBy = null`(防止自邀请)
10. 用户**已存在** → 忽略所有 `referrerCode`,不覆盖已有 `invitedBy`(关系一次性锁定)
11. `referrerCode` 查询不到对应上级 → 静默跳过,不抛异常
**数据库更新:**
12. 绑定成功后,如果上级存在,`inviter.directCount += 1`(实时更新统计字段)
**边界与异常:**
13. 用户未登录直接生成命盘(纯前端计算)→ 允许,不强制登录
14. 登录中途中断(拒绝授权)→ 不清除 `pending_referrer`,下次触发登录仍携带
15. 注册接口网络失败 → 保留 `pending_referrer`,重试时继续携带
16. 同一设备切换微信账号 → 新的 openid 按新用户处理,正常绑定关系
---
### US-6.3 B端佣金结算(能量师→能量师,固定金额)
**作为** 平台
**我希望** 当能量师A的下级B升级为能量师时,自动结算固定金额佣金
**以便** 能量师获得推广收益
**验收标准:**
**触发条件:**
1. 订单 `productType = "practitioner"` 且支付成功 → 进入 B端佣金结算流程
2. 订单 `productType = "annual"` → 走 US-6.4 C端佣金(互斥分支)
**佣金计算——按推荐人身份分两种情况:**
3. **推荐人 A 是能量师(`vipType='practitioner'`)** → 按标准固定金额:
| 级别 | 默认值 | 配置键 |
|------|--------|--------|
| L1(直接上级) | ¥500 | `commission.practitioner.l1` |
| L2(上上级) | ¥100 | `commission.practitioner.l2` |
4. **推荐人 A 是 C端年费用户(`vipType='annual'`)** → L1 拿半额:
| 级别 | 默认值 | 配置键 |
|------|--------|--------|
| L1(直接上级) | ¥200 | `commission.practitioner.l1_cend` |
| L2(上上级) | ¥100(不变) | `commission.practitioner.l2` |
5. 推荐人 A 既不是能量师也不是 C端年费(`vipType=null` 等异常情况)→ L1/L2 不创建佣金
6. 佣金单位为**分**(避免浮点精度问题)
**升级场景佣金补差(`isUpgrade=true`):**
7. 当订单标记 `isUpgrade=true`(从年费升级到能量师),L1/L2 佣金需**扣除**已支付过的年费佣金:
```
应发佣金 = 本次应发金额 − 该买家此前 annual 订单已支付给同一收款人的佣金总额
```
8. 计算逻辑:
- 查询 `commissions` 表中,同一 `buyer_id` + 同一 `收款人` 且 `product_type='annual'` 的所有佣金之和
- 从本次 practitioner 佣金中减去该金额
- 若差值 ≤ 0(理论情况),则本条佣金不创建
9. 示例——首次直接购买能量师(非升级,对比参考):
```
用户C为新用户(无年费记录),通过上级A(能量师)链接直接购买能量师 ¥1,314
isUpgrade = false → 全额发放:L1 = ¥500,L2 = ¥100
无年费佣金可扣除
```
10. 示例——年费升级补差:
```
用户B第1天付¥131(annual),上级A获¥52.40(40%)
用户B第100天升级能量师(种子价¥1,314),上级A是能量师→应发¥500
实际发放:¥500 − ¥52.40 = ¥447.60
```
10. 示例——C端推荐人升级补差:
```
用户B第1天付¥131(annual),上级A(C端)获¥52.40(40%)
用户B第100天升级能量师,上级A是C端→应发半额¥200
实际发放:¥200 − ¥52.40 = ¥147.60
```
**收款人资格:**
11. L1:`buyer.invitedBy` 对应的上级存在 → 按上述规则计算
12. L2:上级的 `invitedBy` 对应的上上级存在 → 按上述规则计算(L2 不因身份打折,仅 L1 打折)
13. 收款人不存在时 → 静默跳过,不报错
**状态与幂等:**
14. 佣金 `status` 直接写入 `"settled"`(即时到账,无 pending 冷静期)
15. 同一 `outTradeNo` 重复回调 → 幂等处理,不创建重复佣金
16. 佣金固定金额不受种子价/标准价差异影响(C端半额同样固定)
**统计更新:**
17. L1 创建时:`inviter.directCount += 1`,`inviter.convertedCount += 1`
18. L2 创建时:`inviter2.indirectCount += 1`
**配置项(后台可配):**
| 配置键 | 默认值 | 说明 |
|--------|--------|------|
| `commission.practitioner.l1` | `50000` | B端一级佣金—能量师推荐(分) |
| `commission.practitioner.l1_cend` | `20000` | B端一级佣金—C端推荐(分) |
| `commission.practitioner.l2` | `10000` | B端二级佣金(分) |
---
### US-6.4 C端佣金结算(年费推荐,比例分成)
**作为** 平台
**我希望** 当C端用户B通过上级A的链接注册并付费 ¥131 时,自动按比例分成
**以便** 激励所有付费用户推广
**验收标准:**
**触发条件:**
1. 订单 `productType = "annual"` 且支付成功 → 进入 C端佣金结算流程
**佣金计算:**
2. 直接佣金(L1):`totalFee × directRate / 10000`,从 `sys_config` 读取 `commission.annual.direct_rate`(默认 `4000` = 40%)
3. 上级佣金(L2):`totalFee × upstreamRate / 10000`,从 `sys_config` 读取 `commission.annual.upstream_rate`(默认 `500` = 5%)
4. 金额计算使用**整数截断**(非四舍五入),剩余零头归平台
> **注意**:年费佣金在后续用户升级能量师时可能被部分抵扣(见 US-6.3 升级补差逻辑)。`paySuccess()` 记录原始佣金(用于后续补差计算),补差逻辑在升级时执行。
> 因此 C端佣金的 `status` 仍直接写入 `"settled"`,不需要等待升级再结算。若后续升级,再由 US-6.3 扣除已付金额。
**收款人资格(与 B端不同):**
5. L1:`buyer.invitedBy` 对应的上级**必须已付费**(`referralCode != null`),才创建佣金
6. L2:上级的 `invitedBy` 对应的上上级**必须已付费**(`referralCode != null`),才创建佣金
7. C端佣金**不要求**收款人是"能量师"(practitioner),只要曾经付费即可
**状态与幂等:**
8. 佣金 `status` 直接写入 `"settled"`
9. 幂等处理同 US-6.3
**与 B端的差异:**
10. C端佣金不更新 `directCount/convertedCount/indirectCount`(这些统计仅用于B端"升级能量师")
11. C端佣金比例灵活(后台可配 0%~100%),B端固定金额
**每笔 ¥131 分配示例:**
| 分配对象 | 比例 | 金额 |
|---------|------|------|
| 直接上级 | 40% | ¥52.40 |
| 上上级 | 5% | ¥6.55 |
| 平台 | 55% | ¥72.05 |
**配置项(后台可配):**
| 配置键 | 默认值 | 说明 |
|--------|--------|------|
| `commission.annual.direct_rate` | `4000` | C端直接佣金比例(万分比,4000=40%) |
| `commission.annual.upstream_rate` | `500` | C端上级佣金比例(万分比,500=5%) |
---
### US-6.5 分销面板
**作为** 能量师
**我希望** 在分销面板中查看我的推广收益和团队数据
**以便** 追踪推广效果,激励持续推广
**验收标准:**
**入口权限:**
1. "我的"页面 → "我的服务"分组 → "💰 我的推广"入口 → 跳转到分销面板
2. **仅付费用户可见**(`vipEndTime > now`),普通用户看不到入口
3. 付费到期后入口自动隐藏
**页面结构(自上而下):**
**① 推广码卡片:**
4. 显示推广码 `我的推广码: ABC123` + 复制按钮
5. "生成推广海报"按钮 → 调用后端生成含小程序码的海报
6. 无推广码(极端情况)→ 显示"推广码生成中…"
**② 收益统计卡片(三列等宽):**
7. 总收益:累计所有佣金总额(`status = settled`)
8. **累计收益**:当前总收益金额(Phase 1 不提现,故不称"可提现"以免误解;页面加注"提现功能即将开放")
9. 今日新增:当日 00:00 至今产生的佣金总额
10. 金额以元为单位,保留两位小数(后端存储分,前端 `/100`)
**③ 团队统计(三列):**
11. 直接下级:`directCount`
12. 间接下级:`indirectCount`
13. 升级下级:`convertedCount`
14. 零数据时显示引导文案:"暂无推广数据,分享推广码给好友开始赚取佣金"
**④ 佣金明细列表(最近50条):**
15. 显示:时间(MM-DD HH:mm)、来源(匿名"用户****")、级别(L1/L2标签)、金额(+¥XXX.XX)、状态
16. 按时间倒序排列
17. 空数据时显示"暂无佣金记录"引导
**⑤ 推广工具区:**
18. "📱 生成推广海报" → 合成含小程序码的分享海报
19. "🔗 复制推广链接" → 复制带 ref 参数的小程序路径到剪贴板
20. "📊 提现申请" → **置灰不可点击**,显示"即将开放"(Phase 1 不做提现)
**页面状态:**
21. **Loading 状态**:各区块显示骨架屏脉冲动画
22. **错误状态**:顶部显示错误提示条 + "点击重试"按钮
23. **空数据状态**:佣金列表/团队统计显示引导文案
24. **下拉刷新**:重新请求所有数据
**后端 API 需求:**
| 接口 | 说明 |
|------|------|
| `POST /api/profile/info` | 含 `referralCode`(已有) |
| `POST /api/commission/list` | 佣金明细列表(已有) |
| `POST /api/commission/stats` | **新增**:总收益+可提现+今日新增+团队统计 |
---
### US-6.6 C端年费订阅入口
**作为** 免费用户(C端)
**我希望** 在AI解读页底部看到 ¥131 开通完整解读的引导
**以便** 一键订阅获取完整服务
**验收标准:**
**入口位置(用户已确认选项1——命盘解读页底部):**
1. `chart/index.vue` 底部(聊天区下方)固定显示订阅卡片(免费用户)
2. 已付费用户(C端或能量师) → 不显示订阅卡片,改为显示"✅ 已开通"徽章+生成推广海报入口
**交互流程(未登录):**
3. 显示完整订阅卡片:🔮 AI解读会员 ¥131/年,列出权益(无限AI解读、个性化报告、推广赚佣金)
4. 点击"登录后开通 ¥131" → 先跳登录页,登录后自动跳转支付页
**交互流程(已登录未付费):**
5. 显示同上"立即开通 ¥131"按钮
6. 点击 → 跳转支付页 `product=annual`,金额 ¥131
**交互流程(已付费):**
7. 订阅卡片替换为灰色小徽章"✅ 已开通AI解读 | 有效期至 2027-05-28"
8. 已付费用户显示"📱 生成推广海报"入口
9. 能量师(practitioner)显示"✅ 已开通能量师"
**支付页适配:**
10. `payment/index.vue` 支持 URL 参数 `product`:
- `product=annual` → 显示 C端年费 ¥131 和对应权益列表
- `product=practitioner` → 显示能量师价格(通过 API 获取种子/标准价)
11. `createOrder` 接口接收 `productType` 参数
**后端适应:**
12. Order `productType` 字段新增枚举值:`"annual"` | `"practitioner"` | `"practitioner_plan"`(人工方案)
13. User `vipType` 字段(`"annual"` | `"practitioner"` | `null`)
14. `paySuccess()` 佣金结算按 `productType` 分支:
- `annual` → US-6.4 C端年费佣金
- `practitioner` → US-6.3 B端佣金(含推荐人身份判断 + isUpgrade 补差)
- `practitioner_plan` → US-9.5/9.6 人工方案佣金(通过 CommerceService 结算,平台留存模型)
---
## EPIC 9:学业方向分析与人工方案(P1)
> **场景**:家长为孩子选择学业方向,通过AI解读初步了解孩子的天赋倾向,如需深度方案可申请能量师出方案。
> **佣金模型**:人工方案采用**平台佣金留存模型**(区别于年费的固定金额/比例模型),所有分销佣金从平台留存部分支出。
> **商城底座**:引入 lilishop(`iwt/lilishop`)作为商城引擎,本期通过 `CommerceService` 接口层预留对接,暂不实现实际商城付费。
---
### US-9.1 学业方向AI深度解读
**作为** 家长
**我希望** 输入孩子的生日后,看到针对学业方向的AI深度解读
**以便** 了解孩子的天赋倾向,为学业规划提供参考
**验收标准:**
**入口:**
1. 命盘解读页底部增加"🎓 学业方向分析"按钮(位于通用解读下方第二行)
2. "我的"页面 → "我的服务"分组 → "📖 学业方向"入口 → 跳转到学业方向历史列表页
3. 点击后调用新接口 `POST /api/chart/academic-orientation`
**AI 解读内容:**
3. 后端转发至 Dify **学业方向专用 Workflow**,命盘24个数字(A-X)作为 `inputs`
4. 解读内容至少包含以下章节:
- **天赋倾向**:基于主性格数字+外部三组数的自然天赋分析
- **适合方向**:文科倾向/理科倾向/艺术特长/体育潜能等,用百分比表示匹配度
- **学习特征**:专注力、理解方式、学习节奏偏好(基于J/K/L位置分析)
- **亲子沟通建议**:针对该命盘类型的孩子,应采用的沟通和教育方式
- **关键期提醒**:V/W/X中年区对应的升学/职业选择重要节点
5. 每个章节独立卡片展示,可折叠展开
6. 页面底部显示"💡 以上分析由AI生成,如需人工深度方案,可申请能量师出方案"
**付费控制:**
7. 免费用户每日可查看 1 次学业方向解读(与 US-3.2 主性格概要**独立配额**,互不消耗)
8. 已付费用户(C端/能量师)不限次
9. 超出次数后显示引导卡片:"今日学业分析次数已用完,开通会员享无限次"
**技术架构:**
10. 在 Dify 平台新增学业方向 Workflow,Workflow 输入为 24 个命盘数字(A-X),输出为结构化 JSON
11. 后端 `DifyService` 新增方法 `interpretAcademicOrientation(AcademicRequest request)`
12. 解读结果缓存同 US-3.3(同一命盘+同一天内重复请求返回缓存内容)
---
### US-9.2 申请能量师出方案
**作为** 家长
**我希望** 在AI学业解读后,申请能量师为孩子出具深度人工方案
**以便** 获得比AI更个性化的专业指导
**验收标准:**
**入口与表单:**
1. AI学业解读页底部固定"申请能量师出方案"按钮
2. 通用AI解读页底部也显示"💼 申请人工深度方案"按钮
3. 点击后弹出半屏表单,包含:
- **需求类型**:学业方向 / 职业规划 / 亲子关系 / 其他(单选)
- **具体需求描述**:文本输入框,限300字
- **期望价格范围**:下拉选项(¥50-99 / ¥100-299 / ¥300-499 / ¥500-999 / 面议)
4. 提交后生成一条"方案需求单"记录
**需求分配:**
5. 如果用户有 `invitedBy` 且上级是能量师(`vipType='practitioner'`)→ 自动把需求单分配给该能量师
6. 如果没有上级能量师 → 显示"暂未开放系统分配,请通过推荐链接找到专属能量师"
7. 分配后能量师收到通知(US-8.3 消息通知或在能量师工作台看到)
**数据库新增:**
8. `plan_requests` 表:`id, userId, chartId, requestType, description, budgetRange, assignedPractitionerId, status(pending/accepted/negotiating/paid/completed/cancelled), price, createdAt, updatedAt`
---
### US-9.3 能量师介入AI会话
**作为** 能量师
**我希望** 看到向我咨询用户的AI会话,并可以发起介入
**以便** 在用户需要时主动提供专业建议,促成人工方案
**验收标准:**
**触发条件:**
1. 仅 `invitedBy` 关系链中的能量师(B推荐A,A是B的上级)才能介入B的AI会话
2. 能量师在"能量师工作台"看到下级用户的咨询记录列表(来自 `invitedBy` 关系链,仅显示有实际咨询的用户)
3. 列表显示:用户头像/昵称/命盘日期/最后活跃时间
**介入流程:**
4. 能量师点击某个用户 → 进入"咨询监看"页面(只读模式查看AI聊天记录)
5. 页面底部有"介入会话"按钮
6. 点击后,用户侧聊天界面出现系统消息:**"🔔 能量师 张三 已进入本次咨询"**
7. 能量师侧出现输入框,可发送文字消息
8. 用户侧聊天流中,能量师消息显示为:**"👤 能量师张三:消息内容"**(绿色气泡,区别于AI的灰色气泡)
9. AI继续正常回答,能量师和AI的回答在聊天中交替显示
**权限边界:**
10. 用户随时可"请出能量师"(在消息长按菜单中选择"结束能量师介入")
11. 用户主动关闭后,能量师侧显示"用户已结束本次协同咨询"
12. 每次介入在 `chat_interventions` 表记录
**数据库新增:**
13. `chat_interventions` 表:`id, sessionId, practitionerId, startTime, endTime, endedBy(user/practitioner)`
14. `chat_messages` 表新增 `senderType` 枚举:`ai` / `user` / `practitioner` / `system` / `proposal`
---
### US-9.4 方案价格协商
**作为** 能量师
**我希望** 在聊天中向用户发送方案提议,并可与用户协商价格
**以便** 双方达成一致后完成付费
**验收标准:**
**出方案提议:**
1. 能量师在聊天输入框左侧有"📋 出方案"按钮
2. 点击弹出结构化表单:
- 方案标题(限50字)
- 方案描述(限500字)
- 方案价格(手动输入,单位元,整数,范围受系统配置限制)
3. 发送后在聊天中显示方案卡片(嵌入消息格式)
**方案卡片交互:**
4. 卡片包含:标题、描述、价格(¥XXX)、能量师名称
5. 用户侧卡片有三个操作按钮:
- **"💰 接受并支付"** → 进入 US-9.5 支付流程
- **"💬 议价"** → 弹出输入框,用户输入期望价格
- **"❌ 不感兴趣"** → 卡片标记为已拒绝,通知能量师
6. 用户议价后,能量师侧收到新消息:"用户希望价格改为 ¥XXX"
7. 能量师可:
- 接受新价 → 发送更新后的方案卡片(价格更新)
- 坚持原价 → 回复文字说明
- 提出折中价 → 发送新的方案卡片
**状态管理:**
8. 一条 `plan_requests` 记录对应多轮协商历史
9. 每次更新价格或状态在 `plan_request_logs` 表记录
10. 最大协商轮次 5 轮(超限后只能接受或拒绝当前价格)
**数据库新增:**
11. `plan_request_logs` 表:`id, planRequestId, action(propose/counter/accept/reject), oldPrice, newPrice, message, operatorId, createdAt`
---
### US-9.5 方案付费与交付
**作为** 家长
**我希望** 接受能量师方案报价后在线支付,并收到完整的方案报告
**以便** 获得专业的学业指导
**验收标准:**
**支付:**
1. 用户点击"接受并支付" → 弹出半屏支付确认页
2. 支付确认页显示:服务名称、能量师名称、协商价格、微信支付按钮
3. 点击支付 → `createOrder(productType="practitioner_plan", planRequestId=xxx)`
4. 订单 `productType = "practitioner_plan"`(新增类型)
5. 支付回调 → 调用 `CommerceService.onPaymentSuccess()` 处理平台佣金计算
**平台佣金计算(StubCommerceService):**
6. 读取分类佣金率:`commerce.category.practitioner_plan.commission_rate`(默认 `3000` = 30%)
7. 平台佣金 = 总价 × commission_rate / 10000(单位分)
8. 能量师应结算 = 总价 - 平台佣金
9. 如有上级推荐 → 从平台佣金中提取分销佣金:
- 直接上级提成 = 平台佣金 × `commission.practitioner_plan.referral_rate` / 10000(默认 `2000` = 20%)
- 上上级提成 = 平台佣金 × `commission.practitioner_plan.upstream_rate` / 10000(默认 `500` = 5%)
**示例计算:**
```
方案价 ¥299,平台佣金率 30%
平台佣金 = ¥299 × 30% = ¥89.70
能量师结算 = ¥299 - ¥89.70 = ¥209.30
有直接上级:分销佣金 = ¥89.70 × 20% = ¥17.94
平台净留 = ¥89.70 - ¥17.94 = ¥71.76
```
**交付:**
10. 能量师收到支付成功通知
11. 能量师工作台出现"方案交付入口"
12. 交付支持三种方式:
- **文字方案**:富文本编辑器输入,保存到 `plan_deliveries.text_content`
- **PDF方案**:上传PDF文件(与US-2.2共用PDF生成能力)
- **图文报告**:混合内容,包含命盘截图+文字解读
13. 交付后用户收到通知 + 聊天显示"📄 您的学业方案已交付"
14. 用户可查看/下载方案,平台不额外限制次数
**评价与完成:**
15. 用户确认接收方案 → 状态 `completed`
16. 用户可对能量师服务进行评分(1-5星)+ 文字评价
17. 评价写入 `practitioner_ratings` 表
**数据库新增:**
18. `plan_deliveries` 表:`id, planRequestId, deliveryType(text/pdf/mixed), textContent, fileUrl, createdAt`
19. `practitioner_ratings` 表:`id, planRequestId, userId, practitionerId, score(1-5), comment, createdAt`
---
### US-9.6 人工方案分销佣金(平台留存口径)
**作为** 平台
**我希望** 当用户购买人工方案时,从平台佣金中自动结算分销佣金
**以便** 激励推广者推荐用户给能量师
**验收标准:**
**触发条件:**
1. 订单 `productType = "practitioner_plan"` 且支付成功 → 进入分佣流程
2. 佣金来自平台留存部分(不是从能量师结算金额中扣除)
**佣金参数:**
| 参数 | 默认值 | 配置键 |
|------|--------|--------|
| 平台佣金率 | 30% | `commerce.category.practitioner_plan.commission_rate` |
| 直接上级分销比例 | 20%(占平台佣金) | `commission.practitioner_plan.referral_rate` |
| 上上级分销比例 | 5%(占平台佣金) | `commission.practitioner_plan.upstream_rate` |
**计算逻辑:**
3. `platformCommission = totalFee × commission_rate / 10000`
4. `referralCommission = platformCommission × referral_rate / 10000`
5. `upstreamCommission = platformCommission × upstream_rate / 10000`
6. 收款人资格同 US-6.4(须已付费用户才可收款)
**状态与幂等:**
7. 佣金 `status` 写入 `"settled"`(即时到账)
8. 同一 `outTradeNo` 重复回调 → 幂等处理
**示例:**
```
方案价 ¥299(29,900分)
平台佣金率 30%
平台佣金 = 29,900 × 30% = 8,970分 = ¥89.70
直接上级佣金 = 8,970 × 20% = 1,794分 = ¥17.94
上上级佣金 = 8,970 × 5% = 448分 = ¥4.48
能量师结算 = 29,900 - 8,970 = 20,930分 = ¥209.30
平台净留 = 8,970 - 1,794 - 448 = 6,728分 = ¥67.28
```
---
### CommerceService 接口层(商城预留)
> 本期不实现 lilishop 对接,先定义接口 + 桩实现。
**接口:**
```java
public interface CommerceService {
/** 创建商品(能量师上架服务) */
String createProduct(CommerceProductDTO product);
/** 创建订单 */
String createOrder(CommerceOrderDTO order);
/** 订单支付回调处理 */
void onPaymentSuccess(String orderSn, String payOrderNo);
/** 查询订单 */
CommerceOrderDTO getOrder(String orderSn);
/** 获取店铺结算信息 */
CommerceSettlementDTO getSettlement(String storeId);
/** 记录分销订单 */
void recordDistribution(String orderSn);
}
```
**Phase 1 桩实现(StubCommerceService):**
| 方法 | 实现策略 |
|------|---------|
| `createProduct` | 写入本地 `commerce_goods` 映射表 |
| `createOrder` | 走现有本地 `orders` 表 + 标记 `commerceReady=false` |
| `onPaymentSuccess` | 执行本地佣金计算逻辑(US-9.5 验收标准6-9) |
| `getSettlement` | 从本地佣金表聚合统计 |
| `recordDistribution` | 走现有 `commissions` 表逻辑 |
**Phase 2+ 对接 lilishop:**
- `createProduct` → lilishop Goods API
- `createOrder` → lilishop Order API
- `onPaymentSuccess` → 创建 StoreFlow + DistributionOrder
- `getSettlement` → lilishop Bill API
- `recordDistribution` → lilishop Distribution API
**数据库新增:**
```sql
-- lilishop 商品映射表(预留)
CREATE TABLE `commerce_goods` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`store_id` varchar(32) NOT NULL COMMENT '能量师storeId(= userId)',
`product_type` varchar(32) NOT NULL COMMENT '商品类型:practitioner_plan',
`lilishop_goods_id` varchar(32) DEFAULT NULL COMMENT 'lilishop商品ID(Phase 2 填充)',
`category_path` varchar(255) DEFAULT NULL COMMENT 'lilishop分类路径',
`price` bigint NOT NULL COMMENT '价格(分)',
`status` varchar(16) DEFAULT 'active' COMMENT '状态',
`created_at` datetime NOT NULL,
`updated_at` datetime DEFAULT NULL
);
```
---
### US-7.1 命盘批注
**作为** 能量师
**我希望** 在命盘图上添加文字批注或标记
**以便** 为每个客户记录个性化的解读要点
**验收标准(入口与权限):**
1. "编辑批注"按钮位于命盘展示页右上角菜单中(仅已付费用户可见)
2. 未付费用户看不到该按钮
3. **批注模式建议方案(开发选其一):**
- **方案A(推荐)**:在命盘页面底部展开批注编辑面板,命盘操作和数字点击互不干扰,无需切换模式。用户点击数字后可快速插入 `【位置X】` 标签到批注文本
- **方案B(备选)**:点击"编辑批注"进入专用模式,命盘页面其他操作暂时禁用,底部显示批注编辑区域
4. 批注内容支持富文本(加粗、斜体、分段、列表),最大 2000 字
5. 批注内容与 `chartId` 绑定,不同命盘各自独立
6. 批注支持按命盘位置打标签,格式示例:`【主性格O】天生领导者,数字1能量突出`
7. 同一位置的多条批注自动折叠,点击展开
8. 保存批注调用 `POST /api/chart/annotation/save`,传入 `chartId` + `content`
9. 保存成功后 toast 提示"批注已保存",编辑区域收起
10. 取消编辑时,若有未保存内容,弹出确认框"有未保存的内容,确定放弃?"
**验收标准(批注展示):**
11. 已有批注的命盘,在数字卡片上方显示小黄点标记(🟡)
12. 退出批注模式后,命盘页底部显示批注摘要区域(展开/折叠),显示最近 3 条批注
13. 点击"查看全部批注"跳到批注完整列表页
14. 批注列表页按位置分组,按创建时间倒序排列
15. 导出的 PDF(US-2.2)第二页包含批注内容
16. 批注支持删除(长按批注 → 确认删除)
**验收标准(后端与存储):**
17. 新增 `chart_annotations` 表:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `BIGINT PK` | 主键 |
| `chart_id` | `BIGINT` | 关联命盘记录 |
| `user_id` | `BIGINT` | 批注人(能量师) |
| `position` | `VARCHAR(2)` | 关联位置字母(A-X),可为 null |
| `content` | `TEXT` | 批注内容(富文本 HTML) |
| `created_at` | `DATETIME` | 创建时间 |
| `updated_at` | `DATETIME` | 最后修改时间 |
18. 新增接口:
- `POST /api/annotation/save` — 保存/更新批注
- `POST /api/annotation/list` — 获取命盘批注列表(按 chartId)
- `POST /api/annotation/delete` — 删除单条批注
### US-7.2 客户标签分组
**作为** 能量师
**我希望** 为命盘记录添加简单的标签或分组
**以便** 对客户进行分类管理
**验收标准(标签创建与选择):**
1. 发起咨询时(生日输入页确认弹窗中)显示"添加标签(选填)"区域
2. 标签区域包含:标签输入框 + "新建"按钮 + 已创建的标签列表(可点击选择)
3. 新建标签:输入 2-8 字,自动分配随机颜色,点击确定后存入用户标签库
4. 已有标签以胶囊样式展示:`[🟢 老客户]` `[🟠 朋友推荐]` `[🔵 线上引流]`
5. 一个命盘最多绑 3 个标签,超出提示"最多选择 3 个标签"
**验收标准(标签展示与筛选):**
6. 命盘列表中,每条记录右侧展示标签胶囊
7. 历史列表顶部增加标签筛选栏,可选择一个或多个标签组合筛选:
- 未选择任何标签时:显示所有记录
- 选择标签时:AND 逻辑(同时包含所有选中标签的记录)
- 支持"全部"按钮一键清除筛选
8. 已打标签的命盘记录,在列表中有标签标识,方便快速识别
**验收标准(标签管理):**
9. 个人中心 → 我的标签:展示该能量师创建的所有标签
10. 标签管理操作:
- 编辑标签名称
- 修改标签颜色(从预设 8 色中选择)
- 删除标签(删除后将移除所有关联命盘上的该标签)
11. 删除标签时弹出确认:"删除后,所有关联命盘上的该标签将被移除,确定?"
**验收标准(后端与存储):**
12. 新增 `user_labels` 表:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `BIGINT PK` | 主键 |
| `user_id` | `BIGINT` | 能量师 ID |
| `name` | `VARCHAR(20)` | 标签名称 |
| `color` | `VARCHAR(7)` | 颜色值(如 #4CAF50) |
| `created_at` | `DATETIME` | 创建时间 |
13. 新建关联表 `chart_labels`(命盘-标签多对多):
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `BIGINT PK` | 主键 |
| `chart_id` | `BIGINT` | 命盘记录 ID |
| `label_id` | `BIGINT` | 标签 ID |
| `created_at` | `DATETIME` | 关联时间 |
14. 新增接口:
- `POST /api/label/create` — 新建标签
- `POST /api/label/list` — 获取标签列表
- `POST /api/label/update` — 编辑标签名称/颜色
- `POST /api/label/delete` — 删除标签
- `POST /api/chart/label/set` — 为命盘设置标签
- `POST /api/chart/label/list` — 获取命盘标签列表(按 chartId)
---
## EPIC 8:管理后台(P0-P1)
### US-8.1 系统配置管理
**作为** 运营管理员
**我希望** 在管理后台可视化编辑定价和佣金参数
**以便** 灵活调整运营策略,无需开发介入
---
#### 8.1.1 验收标准
**DDL & Entity:**
1. 新建 `sys_config` 表,结构如下:
```sql
CREATE TABLE `sys_config` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`config_key` VARCHAR(64) NOT NULL COMMENT '配置键(点分命名法)',
`config_value` VARCHAR(255) DEFAULT NULL COMMENT '配置值(始终使用字符串存储)',
`description` VARCHAR(255) DEFAULT NULL COMMENT '中文说明',
`value_type` VARCHAR(20) NOT NULL DEFAULT 'string' COMMENT 'int | price | percent | string',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_config_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
2. 预置 9 条种子数据(见下,含新增的 `seed_period_end`)。
**后端:SysConfigService + 内存缓存:**
3. `@PostConstruct loadAllToCache()`:启动时将全量配置读入 `ConcurrentHashMap`
4. 对外提供 `getString(key, default)`、`getInt(key, default)` 两个读方法,O(1) 取值
5. `key` 不存在时 → 返回代码中传入的 `defaultValue`,**永不抛异常**
6. `updateConfig(key, value)`:更新 DB + 同步写入缓存,**无需重启**
7. `batchUpdate(Map)`:循环调用 `updateConfig`,整个操作在 `@Transactional` 中
**管理后台 API:**
8. `POST /api/admin/config/list` → 返回 `List`(含全部字段)
9. `POST /api/admin/config/update` → 接收 `{ "configKey": "value", ... }` → 批量更新
**小程序端 API:`POST /api/pricing/current`:**
```json
// Request: 无参数
// Response:
{
"code": 0,
"data": {
"seedPrice": 131400, // pricing.practitioner.seed(分)
"standardPrice": 198600, // pricing.practitioner.standard(分)
"annualFee": 13100, // pricing.annual(分)
"isSeedPrice": true, // 当前用户是否能享受种子价(三重条件判断)
"seedReason": "eligible", // "eligible"=三条件均满足 | "not_founder_code"=非创始人码 | "expired"=有效期已过 | "quota_full"=名额已满 | "no_referrer"=无推荐人(自然注册)
"remainingSeats": 298, // 种子名额余量
"seedLimit": 300, // 名额上限
"seedPeriodEnd": "2027-05-31T23:59:59" // 种子价有效期截止时间
}
}
```
10. `isSeedPrice` 的计算逻辑(三重条件,全部满足才为 `true`):
- 条件①:当前用户的直接推荐人(`invitedBy`)存在,且该推荐人 `invitedBy IS NULL AND vipType='practitioner'`(即推荐人是创始人)
- 条件②:当前时间 ≤ `pricing.practitioner.seed_period_end`
- 条件③:已通过创始人码付费的能量师数 < `seedLimit`(统计 `orders` 中 `product_type='practitioner'` AND `status='paid'` AND `is_seed_price=true` 的记录数)
- 自然注册用户(`invitedBy IS NULL`)→ 条件①不满足 → `isSeedPrice=false`
- `seedReason` 字段用于前端展示不同提示文案(如"种子价已过期"、"种子名额已满"、"需通过创始人邀请注册"等)
**管理后台页面(`src/views/settings/index.vue`):**
11. 左侧菜单新增"系统配置"入口,路由 `/settings`,图标 ⚙️
12. 页面分为三个卡片区域,每个卡片顶部有"恢复初始值"按钮(仅重置单个卡片内的字段为种子数据):
- hover 时 tooltip 显示具体初始值(如"恢复为 ¥500")
- 点击后弹出确认框:"将恢复本卡片内所有字段为初始值,确定吗?"
**定价配置卡片:**
13. 五个输入项:能量师种子价、标准价、种子价名额上限、种子价有效期截止时间、C端年费
14. 用户输入的是**元(整数)**,提交时自动 ×100 转为分(有效期除外)
15. 种子价名额上限下方实时显示"当前已使用:X / Y"(调用 `countByProductTypeAndStatus` + `isSeedPrice=true`)
16. 修改数量时,"已使用"比例随之动态变化
17. 种子价有效期使用 `el-date-picker`(datetime 类型),显示格式 `YYYY-MM-DD HH:mm:ss`,存储格式为 ISO 8601 字符串
18. 有效期下方显示当前状态:"🟢 有效期剩余 XX 天" 或 "🔴 已过期"(根据当前时间与 `seed_period_end` 比较)
19. 定价卡片顶部注释:"种子价三重条件:①扫创始人码注册 ②在有效期内 ③名额未满,三者同时满足才生效"
**B端佣金配置卡片:**
17. **三个输入项**:
- L1 佣金—能量师推荐(¥500)
- L1 佣金—C端推荐(¥200,新增)
- L2 佣金(¥100,不限推荐人身份)
18. 输入单位为元(整数),提交时 ×100 转为分
19. 每个输入项下方实时显示比例计算:
- "L1(能量师推荐)¥500 = 种子价 38.1% / 标准价 25.2%"
- "L1(C端推荐)¥200 = 种子价 15.2% / 标准价 10.1%"
- "L2 ¥100 = 种子价 7.6% / 标准价 5.0%"
20. 比例随定价或佣金值的修改**实时联动更新**
**C端佣金配置卡片:**
21. 两个输入项:直接佣金比例(40%)、上级佣金比例(5%)
22. 输入单位为"%"(整数,如输入"40"表示40%),提交时转为万分比 ×100
23. 下方实时显示金额分配预览:
- "每笔 ¥131:直接 ¥52.40(40.0%)/ 上级 ¥6.55(5.0%)/ 平台 ¥72.05(55.0%)"
24. 费率修改时金额分配**实时联动更新**
**保存逻辑:**
25. 点击"保存全部配置"按钮 → 触发 `POST /api/admin/config/update`
26. 所有字段批量提交,不逐个保存
27. 保存前做前端校验:金额 > 0、比例 0-100%、名额上限 > 0
28. 保存成功 → ElMessage.success("配置已保存,已实时生效")
29. 保存失败 → ElMessage.error("保存失败:" + 错误详情)
30. 前端无 `el-form` 的 reset 行为 —— 用户手动刷新页面可恢复到上次保存的值
**错误与边界:**
31. 网络失败时显示"保存失败,请重试",页面不跳转,已修改的值保留在输入框中
32. 同时两个管理员保存 → 后保存的覆盖先保存的,不做冲突检测(Phase 1 简化)
33. 输入非法值时(如空字符串、负数),`el-input-number` 的 min 属性直接阻止输入
34. 所有金额字段不允许出现小数(元为整数输入,单位转换在后端)
---
#### 8.1.2 后端接口规格
**`POST /api/admin/config/list`**
```json
// Response:
{
"code": 0,
"data": [
{
"id": 1,
"configKey": "pricing.practitioner.seed",
"configValue": "131400",
"description": "能量师种子价(分)",
"valueType": "price",
"updatedAt": "2026-05-28T10:00:00"
}
// ... 共 9 条(基础定价4+有效期1+佣金3+佣金1,详见种子数据SQL)
]
}
```
**`POST /api/admin/config/update`**
```json
// Request:
{
"pricing.practitioner.seed": "131400",
"commission.practitioner.l1": "60000"
}
// Response: { "code": 0 }
```
**`POST /api/admin/config/reset`**(可选,单条重置)
```json
// Request: { "configKey": "commission.practitioner.l1" }
// Response: { "code": 0, "data": { "configKey": "commission.practitioner.l1", "configValue": "50000" } }
```
---
#### 8.1.3 后端类设计
| 类名 | 位置 | 说明 |
|------|------|------|
| `SysConfig` | `entity/` | JPA 实体 |
| `SysConfigRepository` | `repository/` | JPA 接口 |
| `SysConfigService` | `service/` | 配置 CRUD + 内存缓存 |
| `AdminConfigController` | `controller/` | `/api/admin/config/*` 端点 |
| `PricingController` | `controller/` | `/api/pricing/current` 小程序端端点 |
| `OrderRepository` | `repository/` | 新增 `countByProductTypeAndStatus()` |
**`SysConfigService` 核心代码:**
```java
@Service
public class SysConfigService {
private final ConcurrentHashMap cache = new ConcurrentHashMap<>();
private final SysConfigRepository repository;
@PostConstruct
public void loadAllToCache() {
cache.clear();
repository.findAllByOrderByConfigKeyAsc()
.forEach(cfg -> cache.put(cfg.getConfigKey(), cfg.getConfigValue()));
}
public String getString(String key, String defaultValue) {
return cache.getOrDefault(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String val = cache.get(key);
if (val == null) return defaultValue;
try { return Integer.parseInt(val); }
catch (NumberFormatException e) { return defaultValue; }
}
@Transactional
public void updateConfig(String key, String value) {
SysConfig cfg = repository.findByConfigKey(key)
.orElseGet(() -> new SysConfig(key, value, "auto", "string"));
cfg.setConfigValue(value);
cfg.setUpdatedAt(LocalDateTime.now());
repository.save(cfg);
cache.put(key, value);
}
public void batchUpdate(Map configs) {
configs.forEach(this::updateConfig);
}
}
```
---
#### 8.1.4 管理后台前端规格
| 文件 | 操作 | 说明 |
|------|------|------|
| `src/views/settings/index.vue` | **新增** | 系统配置页面(~200行) |
| `src/router/index.ts` | 修改 | 新增 `/settings` 路由 |
| `src/layout/index.vue` | 修改 | 新增菜单项 |
**侧边栏菜单新增:**
```vue
系统配置
```
**页面组件结构:**
```
settings/index.vue
├── el-card (header-toolbar: "系统配置" + [保存全部配置])
├── el-card (定价配置)
│ ├── el-form-item: 能量师种子价 → el-input-number(min=0, max=999999, step=100)
│ ├── el-form-item: 能量师标准价 → el-input-number(...)
│ ├── el-form-item: 种子价名额上限 → el-input-number(min=1, max=99999, step=50)
│ ├── el-form-item: 种子价有效期 → el-date-picker(type="datetime", format="YYYY-MM-DD HH:mm:ss")
│ ├── 注记:种子价三重条件(创始人码+有效期+名额)
│ └── el-form-item: C端年费 → el-input-number(...)
├── el-card (B端佣金配置)
│ ├── el-form-item: L1 佣金(能量师推荐)→ el-input-number
│ ├── el-form-item: L1 佣金(C端推荐)→ el-input-number
│ ├── el-form-item: L2 佣金 → el-input-number
│ └── 实时比例显示
└── el-card (C端佣金配置)
├── el-form-item: 直接佣金比例 → el-input-number(min=0, max=10000, step=500)
├── el-form-item: 上级佣金比例 → el-input-number(min=0, max=5000, step=100)
└── 实时分配预览
├── el-card (人工方案配置 — EPIC 9)
│ ├── el-form-item: 平台佣金率 → el-input-number(min=0, max=10000, step=500)
│ │ └── 注记:方案价 × 佣金率 = 平台收入,剩余结算给能量师
│ ├── el-form-item: 分销佣金占比(直接) → el-input-number(min=0, max=10000, step=500)
│ │ └── 注记:从平台佣金中提取 × 此比例 = 直接上级佣金
│ ├── el-form-item: 分销佣金占比(上级) → el-input-number(min=0, max=5000, step=100)
│ │ └── 注记:从平台佣金中提取 × 此比例 = 上上级佣金
│ ├── el-form-item: 方案最低价 → el-input-number(min=0, max=999999, step=100)
│ ├── el-form-item: 方案最高价 → el-input-number(min=0, max=999999, step=100)
│ │ └── 实时预览示例:方案价¥299→佣金¥89.70→能量师¥209.30→分销¥17.94
│ └── el-form-item: 最大协商轮次 → el-input-number(min=1, max=20, step=1)
```
**表单校验规则:**
| 字段 | 校验规则 |
|------|---------|
| 所有金额(元) | > 0,整数,≤ 999999 |
| 比例(%) | 0-100,整数 |
| 名额上限 | ≥ 1,≤ 99999 |
---
#### 8.1.5 种子数据 SQL
```sql
INSERT INTO `sys_config` (`config_key`, `config_value`, `description`, `value_type`) VALUES
('pricing.practitioner.seed', '131400', '能量师种子价(分)', 'price'),
('pricing.practitioner.standard', '198600', '能量师标准价(分)', 'price'),
('pricing.practitioner.seed_limit', '300', '种子价名额上限', 'int'),
('pricing.practitioner.seed_period_end', '2027-05-31T23:59:59', '种子价有效期截止时间(ISO 8601)', 'datetime'),
('pricing.annual', '13100', 'C端年费(分)', 'price'),
('commission.practitioner.l1', '50000', 'B端一级佣金-能量师推荐(分)', 'price'),
('commission.practitioner.l1_cend', '20000', 'B端一级佣金-C端推荐(分)', 'price'),
('commission.practitioner.l2', '10000', 'B端二级佣金(分)', 'price'),
('commission.annual.direct_rate', '4000', 'C端直接佣金比例(万分比)', 'percent'),
('commission.annual.upstream_rate', '500', 'C端上级佣金比例(万分比)', 'percent'),
('commerce.category.practitioner_plan.commission_rate', '3000', '人工方案平台佣金率(万分比)', 'percent'),
('commission.practitioner_plan.referral_rate', '2000', '人工方案分销佣金占比(万分比)', 'percent'),
('commission.practitioner_plan.upstream_rate', '500', '人工方案上级佣金占比(万分比)', 'percent'),
('plan_request.enabled', 'true', '人工方案功能开关', 'bool'),
('plan_request.max_negotiation_rounds', '5', '最大协商轮次', 'int'),
('plan_request.auto_cancel_hours', '72', '协商超时自动取消(小时)', 'int'),
('plan_request.min_price', '5000', '方案最低价(分)', 'price'),
('plan_request.max_price', '999900', '方案最高价(分)', 'price');
```
---
#### 8.1.6 测试用例
| # | 场景 | 步骤 | 预期 |
|---|------|------|------|
| 1 | 初始加载 | 打开系统配置页 | 17个字段显示正确的默认值 |
| 2 | 修改定价 | 种子价改为 ¥2,000 → 保存 | 下次 `POST /api/pricing/current` 返回 seedPrice=200000 |
| 3 | 修改佣金 | L1 改为 ¥600 → 保存 | 新订单的佣金 amount=60000 |
| 4 | 比例联动 | 直接佣金改为 50% | 下方显示"每笔¥131:直接¥65.50 / 上级¥6.55 / 平台¥58.95" |
| 5 | 比例联动反向 | 种子价改为 ¥2,000 | B端卡片"L1占种子价%"从38.1%变为25.0% |
| 6 | 输入校验 | 在金额框输入 -100 | el-input-number 阻止输入,值保持为 0 |
| 7 | 保存失败 | 网络断开后保存 | 显示"保存失败",输入值保留不丢失 |
| 8 | 保存后立即生效 | 改种子价为 ¥2,000 → 保存 → 新用户打开支付页 | 显示 ¥2,000 |
| 9 | 缓存同步 | API 直接改 DB → 调用 getInt() | 返回值仍为旧值(下次 loadAllToCache 或调用 updateConfig 后更新) |
| 10 | 种子名额展示 | 3笔已支付 practitioner 种子价订单 | 显示"已使用:3 / 300" |
| 11 | 种子有效期展示 | 有效期设为未来日期 | 显示"🟢 有效期剩余 XX 天" |
| 12 | 种子有效期过期 | 有效期设为过去日期 | 显示"🔴 已过期" |
---
### US-8.2 订单管理
**作为** 运营管理员
**我希望** 查看所有订单记录,按产品类型和状态筛选
**以便** 核对收入和对账
---
#### 8.2.1 验收标准
**后端 API(补全 `AdminController.listOrders()`):**
1. `POST /api/admin/orders` → 接收筛选参数,返回分页订单列表
```json
// Request:
{
"productType": "", // 筛选:"" 全部 | "practitioner" | "annual"
"status": "", // 筛选:"" 全部 | "paid" | "pending"
"page": 1,
"pageSize": 20
}
// Response:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"userId": 42,
"outTradeNo": "202605280001",
"totalFee": 131400,
"productType": "practitioner",
"isSeedPrice": true,
"status": "paid",
"payType": "wxpay",
"paidAt": "2026-05-28T10:00:00",
"createdAt": "2026-05-28T09:59:00"
}
],
"total": 128,
"page": 1,
"pageSize": 20
}
}
```
2. 后端在 `OrderRepository` 中新增分页+动态筛选查询方法(使用 `Specification` 或 `@Query` 动态拼接)
**前端页面:**
3. 表格列:ID、订单号、用户ID、产品类型("能量师" / "年费")、金额(¥格式)、种子价标记、状态(已支付/待支付)、支付时间
4. 产品类型使用 el-tag 展示:能量师→蓝色,年费→绿色
5. 种子价订单显示 🌱 种子 el-tag(橙色),hover 提示"通过创始人码注册"
6. 状态使用 el-tag:已支付→success,待支付→warning
7. 表格上方的工具栏包含两个筛选器:
- 产品类型:el-select(全部 / 能量师 / C端年费)
- 状态:el-select(全部 / 已支付 / 待支付)
8. 筛选器 change 时重新请求接口
9. 底部分页组件:显示总条数,支持切换页码
**当前代码待补全:**
- `AdminController.listOrders()` 目前返回 `Result.success(null)` → 需替换为真实数据
- `Order` 实体和 `OrderRepository` 已有,但缺 `productType` 和 `isSeedPrice` 字段 → 需新增
- 订单表 `orders` 需增加 `product_type` 和 `is_seed_price` 列
- 分页需引入 `Pageable` + `Specification`(或简单用 `PageRequest`)
---
#### 8.2.2 后端类变更
| 类名 | 变更 | 说明 |
|------|------|------|
| `Order.java` | 新增字段 | `productType` (String)、`isSeedPrice` (Boolean) |
| `OrderRepository` | 新增方法 | `countByProductTypeAndStatus(productType, status)` |
| `OrderRepository` | 新增方法 | `findAll(Specification, Pageable)` 或 `findByProductTypeAndStatus` |
| `AdminController` | 重写 `listOrders()` | 支持筛选+分页 |
---
### US-8.3 佣金查看
**作为** 运营管理员
**我希望** 查看所有佣金记录和统计
**以便** 了解分销推广效果
---
#### 8.3.1 验收标准
**后端 API(增强现有):**
1. `POST /api/admin/commissions` → 支持分页和按级别筛选
```json
// Request:
{
"level": 0, // 0: 全部 | 1: L1 | 2: L2
"page": 1,
"pageSize": 20
}
// Response:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"orderId": 101,
"fromUserId": 42,
"toUserId": 10,
"level": 1,
"amount": 50000,
"status": "settled",
"remark": "直接推荐升级能量师",
"createdAt": "2026-05-28T10:00:00"
}
],
"total": 56,
"page": 1,
"pageSize": 20,
"summary": {
"settledTotal": 350000, // 已结算总佣金(分)
"pendingTotal": 0, // 待结算总佣金(分,Phase 1 始终为 0)
"grandTotal": 350000, // 总佣金(分)
"countByLevel": { // 各级别笔数
"1": 12,
"2": 8
}
}
}
}
```
2. 当前 `AdminController.listAllCommissions()` 返回 `commissionRepository.findAll()`(无分页)→ 需重写
**前端页面(增强现有 `commissions/index.vue`):**
3. 顶部统计卡片(已有框架)→ 增强:
- 已结算佣金:¥3,500.00(绿色)
- 待结算佣金:¥0.00(黄色,Phase 1 恒为 0,保留字段显示)
- 总佣金:¥3,500.00
- **新增第四个卡片**:总笔数(L1: 12 笔 / L2: 8 笔)
4. 表格列(增强现有):ID、订单ID、来源用户ID、获得用户ID、级别(L1/L2 el-tag)、金额(¥格式)、备注(remark 字段)、状态、创建时间
5. 新增级别筛选器:el-select(全部 / L1 / L2),筛选时重新请求接口
6. 底部分页:当前 `:total="commissions.length"` 是前端假分页,改为后端真分页
7. 备注列显示 remark 内容(如"直接推荐升级能量师"、"C端年费直接推荐佣金 (40%)")
---
#### 8.3.2 前后端差距分析
| 当前状态 | 目标状态 | 工作量 |
|---------|---------|--------|
| `AdminController.listAllCommissions()` 无分页,无筛选 | 分页 + level 筛选 + summary 统计 | M |
| `CommissionRepository` 无分页方法 | 新增 `findAll(Specification, Pageable)` | S |
| 前端假分页(全量拉取) | 后端真分页 | M |
| 前端无 level 筛选 | 新增 el-select 筛选器 | S |
| 前端无备注列 | 新增 remark 列 | S |
| 前端无笔数统计卡片 | 新增第四个统计卡片 | S |
---
### US-8.4 提现管理(推迟至 Phase 2)
**已决策:推迟,当前不做。**
---
## 开发优先级总表
| 用户故事 | 优先级 | 预估工期 | 依赖 |
|----------|--------|---------|------|
| US-1.1 生日输入与咨询发起 | P0 | 3天 | US-5.1(需登录)|
| US-1.2 三角形可视化 | P0 | 3天 | US-1.1 |
| US-1.4 数字点击查看含义 | P2 | 1天 | US-1.2 |
| US-2.1 分享图片 | P0 | 2天 | US-1.2 |
| US-2.2 PDF导出 | P0 | 2天 | US-1.2 |
| US-3.1 AI解读展示 | P0 | 2天 | US-1.1 + Dify Workflow配置(附录B) |
| US-3.2 AI解读免费/付费控制 | P0 | 1天 | US-3.1 |
| US-3.3 解读内容缓存 | P0 | 1天 | US-3.1 |
| US-3.4 AI交互问答(免费/付费控制) | P0 | 2天 | US-1.1 + Dify Chatflow配置(附录B) |
| US-4.1 能量师付费订阅 | P0 | 2天 | US-5.1 |
| US-4.2 种子价自动判断 | P0 | 1天 | US-8.1 |
| US-4.3 订阅状态与续费 | P0 | 1天 | US-4.1 |
| US-4.4 年费升级能量师(升级定价) | P1 | 1.5天 | US-4.1 + US-4.2 |
| US-5.1 微信登录+注册信息完善 | P1 | 3天 | 无 |
| US-5.2 历史咨询记录 | P1 | 2天 | US-5.1 |
| US-5.3 个人资料编辑(社交预留) | P2 | 1天 | US-5.1 |
| US-6.1 推广码生成 | P0 | 1天 | US-4.1 |
| US-6.2 分享带参注册与上下级绑定 | P0 | 2天 | US-5.1 |
| US-6.3 B端佣金结算(含升级补差+C端半额) | P0 | 3天 | US-4.1 + US-6.2 + US-4.4 + US-8.1 |
| US-6.4 C端佣金结算 | P0 | 1天 | US-6.6 + US-6.3 + US-8.1 |
| US-6.5 分销面板 | P1 | 2天 | US-6.1 + US-6.3 |
| US-6.6 C端年费订阅入口 | P0 | 1.5天 | US-3.2 + US-4.1 |
| US-7.1 批注功能 | P2 | 2天 | P0完成 |
| US-7.2 客户分组 | P2 | 2天 | P0完成 |
| US-8.1 系统配置管理 | **P0** | **2天** | **无(独立基础设施)** |
| US-8.2 订单管理 | P1 | 1.5天 | US-4.1 + Order 新增字段 |
| US-8.3 佣金查看 | P1 | 1天 | US-6.3 |
| US-9.1 学业方向AI解读 | P1 | 2天 | US-1.1 + Dify学业Workflow |
| US-9.2 申请能量师出方案 | P1 | 1.5天 | US-5.1 + US-9.1 |
| US-9.3 能量师介入AI会话 | P1 | 2天 | US-5.1 + US-3.4 |
| US-9.4 方案价格协商 | P1 | 1.5天 | US-9.3 |
| US-9.5 方案付费与交付 | P1 | 2天 | US-9.4 + CommerceService 接口 |
| US-9.6 人工方案分销佣金 | P1 | 1天 | US-9.5 + US-8.1 |
---
## 咨询+命盘依赖图
```
US-5.1 微信登录
│
▼
US-1.1 生日输入与咨询发起(后端查重(userId+birthday)→确认弹窗→后台计算→返回展示)
│
├──► US-1.2 三角形可视化(24位置新命名 I-X)
├──► US-3.4 AI交互问答(每日3轮,绑定当前命盘)
│
▼
US-5.2 历史咨询记录(按 userId+birthday 唯一归组,可恢复聊天历史)
```
## 分销相关依赖图
```
US-8.1 系统配置管理(独立基础设施——最先完成)
│
├──► US-4.2 种子价判断
├──► US-6.3 B端佣金(读取 L1/L2 金额)
└──► US-6.4 C端佣金(读取直接/上级比例)
US-5.1 微信登录
│
▼
US-6.2 分享带参注册(需要 openid)
│
▼ 绑定上下级关系
│
US-4.1 能量师付费 + US-6.6 C端年费订阅
│ │
▼ ▼
US-6.1 推广码生成 US-6.4 C端佣金结算
│ │
▼ │
US-6.3 B端佣金结算 ◄──────────────┘(共享 paySuccess() 分支)
│
▼
US-6.5 分销面板(前端展示所有数据)
```
## EPIC 9 依赖图(学业方向+人工方案)
```
US-8.1 系统配置(人工方案参数——独立基础设施)
│
▼
US-9.1 学业方向AI解读(Dify学业Workflow)
│
▼
US-9.2 申请能量师出方案
│
├──(有上级能量师)──► US-9.3 能量师介入AI会话
│ │
└──(无上级)→ 提示找推荐链接 │
▼
US-9.4 方案价格协商
│
▼
US-9.5 方案付费与交付
│ │
│ ▼
│ US-9.6 人工方案分销佣金
│
▼
CommerceService 接口层(lilishop预留)
```
## 排期建议
| 周次 | 交付内容 |
|------|---------|
| **Week 1** | US-5.1 登录+注册完善 → US-8.1 系统配置(独立基础设施) |
| **Week 2** | US-1.1 咨询发起(依赖US-5.1登录) → US-1.2 三角可视化 → US-1.3 计算规则实现(附录A Step 1-6) |
| **Week 3** | US-3.1 AI解读 + Dify Workflow配置(附录B) → US-3.3 缓存 → US-3.2 免费/付费控制 |
| **Week 4** | US-3.4 AI问答 + Dify Chatflow配置(附录B) → US-2.1 分享图片 → US-2.2 PDF导出 |
| **Week 5** | US-4.1 能量师付费 + US-4.2 种子价 + US-6.6 C端订阅入口 → **US-9.1 学业Dify Workflow配置** |
| **Week 6** | US-6.2 带参注册 → US-6.1 推广码 → US-6.3 B端佣金(含升级补差) |
| **Week 7** | US-6.4 C端佣金 → US-6.5 分销面板 + US-8.2 订单管理 → **US-9.2 需求单 + US-9.3 介入会话** |
| **Week 8** | 联调测试 + US-8.3 佣金查看 + US-4.3 订阅状态/续费 + US-4.4 升级能量师 + BUG修复 |
| **Week 9+** | **US-9.4 价格协商 + US-9.5 付费交付 + US-9.6 分佣 → CommerceService 接口层** |
> **Phase 1 总预估:约 8 周(含联调测试)**
> 排期原则:先做核心流程(登录→命盘→AI),再做支付分销。US-8.1(配置系统)作为基础设施优先完成,保障后续所有定价依赖。
> P2 功能(US-1.4 数字点击含义 / US-5.3 资料编辑 / US-7.x 批注与标签)可在 Phase 1 后期或 Phase 2 迭代。
---
## 附录A:【新计算规则】代码文件变更清单
> 以下清单基于新命名体系(A-X)和咨询会话管理需求,列出所有需修改的代码文件及变更内容。
### A.1 后端 Java
| 文件 | 变更类型 | 变更内容 |
|------|---------|---------|
| `CalculatorService.java` | 重写 | 内部7位从 `F,G,H,I,M,N,O` → `I,J,K,L,M,N,O`;新增外部9位 `P,Q,R,S,T,U,V,W,X` 的独立计算;新增 `calculateFullTriangle()` 返回全部24个位置 |
| `ChartService.java` | 改造 | 新增 `startConsultation(userId, birthday, name, questions)` 方法,含:①查 userId+birthday 是否已有记录;②已有则直接返回+聊天历史;③无则服务器端调 `CalculatorService` 计算+创建记录+可选首次AI解读 |
| `ChartController.java` | 改造 | 保留 `/api/chart/create`(兼容旧版);**新增 `POST /api/consultation/start` → 调 `ChartService.startConsultation()`** |
| `ChartRecord.java` | 微调 | 新增 `lastInteractionAt` 字段(`LocalDateTime`,用于列表排序);加 `@Table(uniqueConstraints=...)` 定义 `(userId, birthday)` 联合唯一 |
| `ChartRecordRepository.java` | 新增 | `Optional findByUserIdAndBirthday(Long userId, String birthday)` |
| `UserService.java` | 微调 | `checkDailyQuota()` 和 `consumeQuota()` 已有逻辑不变(3次/天全局计数),但新增注释明确"非VIP每日3次绑定当前命盘,切换生日不重置" |
| `ProfileController.java` | 微调 | 咨询列表接口按 `lastInteractionAt` 倒序 |
| `DifyService.java` | **新增** | Dify API 客户端,封装 `POST /v1/workflows/run`(命盘解读)和 `POST /v1/chat-messages`(AI问答)两个接口;含签名、错误重试、超时处理,详见**附录B** |
| `ChatService.java` | 改造 | 原直调 LLM 逻辑改为调 `DifyService`;解读生成走 `DifyService.runWorkflow()`;问答走 `DifyService.sendChatMessage()`;在调用前检查配额(US-3.4) |
### A.2 前端 Vue / JS
| 文件 | 变更类型 | 变更内容 |
|------|---------|---------|
| `client/utils/calculator.js` | 废弃/降级 | `calculateTriangle()` 保留为离线兜底,不再作为主路径调用;内部命名改为 `I,J,K,L,M,N,O`;新增 `calculateFullTriangle()` 含外部 P-X;主入口加注释 `@deprecated 请使用后端计算` |
| `client/stores/chart.js` | 改造 | `computeChart()` 不再调 `calculateTriangle()`,改为调 `consultationApi.start()`;返回数据中的 `positions` 直接写入 `currentChart`;不再调 `analyzeTriangle()`(由后端返回) |
| `client/components/TriangleChart.vue` | 重写 matrix() | `innerBottom` 从 `[d.F,d.G,d.H,d.I]` → `[d.I,d.J,d.K,d.L]`;`outerLeft` 从 `{main:d.M, sub:[d.F,d.G]}` → `{main:d.R, sub:[d.P,d.Q]}`;`outerRight` 从 `{main:d.N, sub:[d.H,d.I]}` → `{main:d.U, sub:[d.S,d.T]}`;`outerTop` 从 `{main:d.O, sub:[d.M,d.N]}` → `{main:d.X, sub:[d.V,d.W]}` |
| `client/pages/index/index.vue` | 改造 | `onAnalyze()` 改为:①调 `POST /api/consultation/start`(传 birthday + name + questions);②若返回 `isNew=true` 且 `requireConfirmation=true` 则弹确认框;③确认后再次请求;④成功后跳转 `chart/index`;⑤"想了解的问题"加 `maxlength=100` + 实时字数显示 |
| `client/utils/api.js` | 新增 | `consultationApi`:`start(data)` → `POST /api/consultation/start`;`detail(id)` → `POST /api/consultation/detail` |
| `client/pages/chart/index.vue` | 微调 | 适配新的 API 返回结构(`chartData.positions` 含全部 I-X);`onMounted` 加载历史消息逻辑不变 |
| `client/pages/records/index.vue` | 微调 | 列表按 `lastInteractionAt` 排序;详情页恢复聊天历史逻辑不变 |
### A.3 数据库
| 变更 | 说明 |
|------|------|
| `chart_records` 表加联合唯一索引 | `UNIQUE INDEX uk_user_birthday (user_id, birthday)`,确保同用户+同生日只有一条咨询记录 |
| `chart_records` 表加字段 | `last_interaction_at DATETIME`,默认等于 `created_at`,每次 AI 聊天时更新,用于列表排序 |
| `chart_records` 表现有字段 | `chart_data` 列存储的 JSON 结构从 7个旧位置 → 16个新位置(内部 I-O + 外部 P-X) |
### A.4 变更执行顺序
```
Step 1: US-1.3(本对照表)→ 开发团队通读,确保理解命名对应关系
Step 2: 数据库迁移 → 加索引+字段
Step 3: CalculatorService.java → 重写为新命名+外部计算
Step 4: ChartService.java + ChartController.java → 新增 startConsultation()
Step 5: stores/chart.js + api.js → 前端调后端新接口(主路径走后端;后端不可用时 calculator.js 降级兜底,仅展示离线计算版命盘,不提供AI解读和咨询记录)
Step 6: TriangleChart.vue → matrix() 改用新命名+独立外部值
Step 7: pages/index/index.vue → 确认弹窗 + 100字限制
Step 8: Dify 配置 →
8a. 在 Dify 平台创建 Workflow(命盘解读)和 Chatflow(AI问答)
8b. 上传知识库文档(数字能量学语料)
8c. 发布工作流,获取 API Key
Step 9: DifyService.java + ChatService.java → 集成 Dify API
Step 10: 联调 → 前后端打通 end-to-end,含 Dify 工作流调试验收
```
---
## 附录B:Dify AI 架构设计
> Dify 是开源 LLM 应用开发平台,提供可视化工作流编排、RAG 知识库、模型管理等能力。
> 本项目使用 Dify 的两个应用类型:**Workflow**(命盘解读生成)和 **Chatflow**(AI交互问答)。
### B.1 整体架构
```
┌─────────────────────────────────────────────────────────┐
│ 微信小程序前端 │
│ POST /api/chart/interpret POST /api/chat/send │
└────────────────────┬─────────────────────┬──────────────┘
│ │
┌────────────────────▼─────────────────────▼──────────────┐
│ Java 后端 (Spring Boot) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ChatController│ │ChartController│ │ChatService │ │
│ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────┬──────┘ │ │
│ │ │ │
│ ┌──────────▼──────────┐ ┌───────────▼────────┐ │
│ │ DifyService.java │ │ DifyService.java │ │
│ │ runWorkflow() │ │ sendChatMessage() │ │
│ └──────────┬──────────┘ └───────────┬─────────┘ │
└────────────────────┼─────────────────────────┼───────────┘
│ │
┌────────────────────▼─────────────────────────▼───────────┐
│ Dify API (自建部署 / SaaS) │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Workflow(解读) │ │ Chatflow(问答) │ │
│ │ │ │ │ │
│ │ 开始 → 知识检索 │ │ 开始 → 接收消息 │ │
│ │ → LLM生成解读 │ │ → 知识检索 → LLM回答 │ │
│ │ → 格式化输出 │ │ → 返回答案 │ │
│ └────────┬─────────┘ └──────────┬───────────┘ │
│ │ │ │
│ └──────────┬───────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ 知识库(RAG) │ │
│ │ ├─ 主性格含义库 │ │
│ │ ├─ 组合数字含义库 │ │
│ │ ├─ 三区年龄解读库 │ │
│ │ └─ 常见命盘案例库 │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
**关键原则:**
- Java 后端是**最薄的一层**,只做:参数校验 → 配额检查 → 调用 Dify API → 透传结果
- 所有 AI 逻辑(prompt 设计、知识检索策略、输出格式)都在 Dify 工作流中配置
- 修改解读模板或知识内容不需要改 Java 代码,只需要在 Dify 平台更新工作流
### B.2 Dify Workflow(命盘解读)—— 一次性解读生成
**用途:** US-3.1 AI解读展示
**Dify 应用类型:** **Workflow(工作流)**——单轮执行,无需维护对话历史
**触发方式:** API 调用,blocking 模式
**输入变量:**
| 变量名 | 类型 | 说明 |
|--------|------|------|
| `chart` | `object` | 24个命盘位置的值 `{ A:8, B:1, ..., X:5 }` |
| `name` | `string` | 用户姓名 |
| `birthday` | `string` | 出生日期 `YYYY-MM-DD` |
**工作流节点编排:**
```
[开始节点]
↓
[知识检索 1] 查主性格(O)含义
↓
[知识检索 2] 查左区(P,R)21-40岁解读
↓
[知识检索 3] 查顶部(V,X)41-60岁解读
↓
[知识检索 4] 查右区(S,U)61+岁解读
↓
[知识检索 5] 查特殊组合(如 I+J=11, M+O 等)
↓
[LLM 节点]
系统提示词:
"你是一个专业的数字能量学分析师。
根据以下命盘数据和检索到的知识,生成完整的命盘解读。
必须严格按照以下结构输出..."
↓
[代码节点 / 输出解析]:确保输出 JSON 结构正确
↓
[结束节点] → 输出 `{ sections: [...], summary: "..." }`
```
**输出格式(`data.outputs`):**
```json
{
"sections": [
{
"title": "主性格解读",
"position": "O",
"value": 6,
"content": "主性格数字 6 代表…",
"keywords": ["责任心", "家庭", "关爱"]
},
{
"title": "左区(21-40岁)",
"positions": ["P", "Q", "R"],
"values": [8, 3, 2],
"content": "左区数字组合 8-3-2 代表…"
},
{
"title": "顶部(41-60岁)",
"positions": ["V", "W", "X"],
"values": [4, 1, 5],
"content": "顶部数字组合 4-1-5 代表…"
},
{
"title": "右区(61+岁)",
"positions": ["S", "T", "U"],
"values": [6, 8, 5],
"content": "右区数字组合 6-8-5 代表…"
}
],
"summary": "整体命盘评价…",
"combination_notes": ["数字 I(4)+J(3)=7,形成…", "M(7)+O(6)=13→4,形成…"]
}
```
### B.3 Dify Chatflow(AI交互问答)—— 多轮对话
**用途:** US-3.4 AI交互问答
**Dify 应用类型:** **Chatflow(对话工作流)**——多轮对话,自动维护 `conversation_id`
**触发方式:** API 调用,streaming 模式(打字机效果)
**第一轮消息传入的上下文变量:**
```json
{
"inputs": {
"chart": { "A":8, "B":1, "C":9, "D":6, "E":3, "F":0, "G":1, "H":4,
"I":4, "J":3, "K":6, "L":5, "M":7, "N":2, "O":6,
"P":8, "Q":3, "R":2, "S":6, "T":8, "U":5, "V":4, "W":1, "X":5 },
"user_name": "张三",
"birthday": "1996-03-14"
},
"query": "我的主性格数字6代表什么?",
"user": "uid_123",
"response_mode": "streaming"
}
```
**后续轮次:** 只传 `query` + `conversation_id`,无需重复传入命盘数据
**Chatflow 节点编排:**
```
[开始节点]
↓
[问题分类节点]
├─ 问数字含义 → [知识检索] → [LLM 回答]
├─ 问运势建议 → [知识检索] → [LLM 回答]
└─ 其他问题 → [LLM 直接回答]
↓
[LLM 节点]
系统提示词:
"你是一个数字能量学咨询助手。
用户的命盘数据已作为上下文变量传入。
请基于用户的命盘数据和知识库内容回答问题。
如果问题涉及具体数字,请引用该数字在命盘中的位置和含义。"
↓
[结束节点] → 输出答案文本
```
### B.4 知识库设计
需要在 Dify 平台创建的知识库文档:
| 文档名称 | 内容 | 用途 |
|----------|------|------|
| **数字能量学基础** | 数字 1-9 的基础能量含义、吉凶属性 | LLM 基础理解 |
| **主性格数字解读** | 每个主性格数字(1-9)的详细性格特质、优缺点、代表人物 | 主性格解读 |
| **位置含义对照表** | 每个位置(A-X)代表的人生领域和能量属性 | 各章节解读 |
| **组合数字解读** | 常见数字组合(如11/22/33/13/14等)的特殊含义 | 组合分析 |
| **三区年龄段解读** | 21-40 / 41-60 / 61+ 三个年龄段的典型命理模式 | 三区解读 |
| **常见命盘案例** | 典型命盘 + 完整解读示例(Few-shot 示例) | 提升解读质量 |
**文档格式建议:**
- 每个文档的结构化程度越高越好,推荐 Markdown 表格 + 标题层级
- 示例格式:
```markdown
# 主性格数字 7
## 核心特质
- 关键词:分析力、哲学思维、追求真理
- 能量属性:精神导向,向内探索
## 性格描述
主性格数字7的人天生具有强烈的分析能力和探索精神…
## 优势
- 逻辑思维强
- 善于发现规律
- 独立自主
## 劣势
- 容易多疑
- 社交上偏孤僻
- 过度分析导致行动迟缓
## 代表人物
- 爱因斯坦、达芬奇
```
### B.5 Java 后端集成
**`DifyService.java` 核心接口:**
```java
@Service
public class DifyService {
// 配置(从 application.yml 读取)
@Value("${dify.api-base}")
private String apiBase; // e.g. "https://api.dify.ai/v1"
@Value("${dify.workflow-api-key}")
private String workflowApiKey; // 命盘解读 Workflow 密钥
@Value("${dify.chatflow-api-key}")
private String chatflowApiKey; // AI问答 Chatflow 密钥
/**
* 调用 Dify Workflow 生成命盘解读(US-3.1)
* @param chartData 24个数字的 Map
* @param userName 用户姓名
* @param birthday 出生日期
* @return 结构化解读结果(sections + summary)
*/
public InterpretationResult runInterpretation(
Map chartData, String userName, String birthday
) {
// 1. 构造 inputs
// 2. POST /v1/workflows/run (blocking)
// 3. 解析 data.outputs 为 InterpretationResult
// 4. 超时 15s,重试 1 次
}
/**
* 调用 Dify Chatflow 发送消息(US-3.4)
* @param chartData 24个数字(仅首轮传入)
* @param conversationId 已有会话ID(后续轮次)
* @param query 用户问题
* @param userId 用户标识
* @param isFirstRound 是否首轮
* @return Dify 流式响应或阻塞响应
*/
public ChatResponse sendChatMessage(
Map chartData,
String conversationId,
String query,
String userId,
boolean isFirstRound
) {
// 1. 首轮时传入 inputs(chart 数据)
// 2. POST /v1/chat-messages (streaming)
// 3. 返回 SSE 流或阻塞结果
}
}
```
**`ChatService.java` 改造要点:**
```java
@Service
public class ChatService {
public ChatResponse sendMessage(Long userId, Long chartId, String message) {
// 1. 检查用户配额(US-3.4)
if (!isVip(userId) && getTodayQuota(userId) >= 3) {
throw new BusinessException("今日AI问答次数已用完");
}
// 2. 获取命盘数据
ChartRecord record = chartRecordRepository.findById(chartId);
Map chartData = record.getChartData();
// 3. 调用 Dify(Chatflow)
String conversationId = record.getDifyConversationId();
boolean isFirstRound = (conversationId == null);
ChatResponse response = difyService.sendChatMessage(
chartData, conversationId, message, userId.toString(), isFirstRound
);
// 4. 首次对话保存 conversationId
if (isFirstRound) {
record.setDifyConversationId(response.getConversationId());
chartRecordRepository.save(record);
}
// 5. 消耗配额
consumeQuota(userId);
return response;
}
}
```
**`application.yml` 新增配置:**
```yaml
dify:
api-base: https://api.dify.ai/v1
workflow-api-key: app-xxxxx # 命盘解读 Workflow
chatflow-api-key: app-yyyyy # AI问答 Chatflow
timeout: 15000 # 单次调用超时 15s
```
### B.6 Dify 部署模式建议
| 模式 | 适用阶段 | 说明 |
|------|---------|------|
| **Dify SaaS(cloud.dify.ai)** | 开发/测试 | 快速上手,无需自建,有免费额度 |
| **自建部署(Docker)** | 生产 | 数据不出域,可控成本,建议生产环境使用 |
自建部署参考:`https://github.com/langgenius/dify`
### B.7 开发流程建议
1. **先配知识库**:在 Dify 平台创建知识库,上传数字能量学语料文档
2. **再搭 Workflow**:创建命盘解读工作流,配置节点链,用测试数据调通
3. **再搭 Chatflow**:创建 AI 问答对话工作流,测试多轮对话
4. **Java 集成**:开发 `DifyService.java`,调通 Workflow 和 Chatflow 两个 API
5. **联调验收**:前端通过后端调用 Dify,验证解读质量和问答效果
### B.8 与 US 的对应关系
| Dify 组件 | 对应 US | 备注 |
|-----------|---------|------|
| Workflow(命盘解读) | US-3.1 AI解读展示、US-3.2 付费控制 | Workflow 输出由 US-3.2 决定是否全文展示 |
| **Workflow(学业方向)** | **US-9.1 学业方向AI解读** | **新增专用Workflow,输入24个A-X数字,输出学业方向分析JSON** |
| Chatflow(AI问答) | US-3.4 AI交互问答 | 配额控制在 Java 后端,不经过 Dify;能量师介入消息不走Dify |
| 知识库 | US-3.1、US-3.4、US-9.1 | 三个工作流共用同一套知识库 |
| 无(纯后端) | US-3.3 解读内容缓存、US-9.5 CommerceService | 缓存逻辑在 Java 后端,不涉及 Dify |
---
## 附录C:三类用户角色权限对照表
> 覆盖系统所有功能的权限边界,按角色逐一对比。
### C.1 概览
| | 普通用户 | C端年费用户 | 能量师用户 |
|--|---------|------------|-----------|
| **年费** | ¥0 | ¥131 | ¥1,314(种子)/ ¥1,986(标准) |
| **数据库标记** | `vipType=NULL` | `vipType='annual'` | `vipType='practitioner'` |
| **到期降级** | — | → 普通用户 | → 普通用户 |
| **续费价格** | — | 标准价 ¥131 | 标准价 ¥1,986(种子价仅限创始人码首次购买) |
| **定位** | 浏览体验 | 给自己看,轻度社交 | 给客户看,商业工具 |
### C.2 功能权限总表
```
能力 普通用户 C端¥131 能量师¥1,314+
───────────────────────── ────────── ────────── ──────────────
生成命盘 ✅ ✅ ✅
查看命盘可视化 ✅ ✅ ✅
数字点击查看含义 ✅ ✅ ✅
AI解读·主性格概要 1次/天 不限 不限
AI解读·完整五区 ❌ ✅ ✅
AI问答互动 3轮/天 不限 不限
学业方向AI解读 1次/天 不限 不限
分享图片到微信 1次/天 不限 不限
导出PDF报告 ❌ 不限 不限
历史记录查看 近7天 全部 全部
推广码+分销面板 ❌ ✅ ✅
推广C端年费得佣金 ❌ 40%+5%分成 40%+5%分成
推广能量师年费得佣金 ❌ ❌ ¥500 + ¥100
申请能量师出方案 ✅ ✅ ✅
能量师介入AI会话 ❌(被动) ❌(被动) ✅(主动)
接受人工方案付费 ✅ ✅ ✅
命盘批注(P2) ❌ ✅ ✅
客户标签分组(P2) ❌ ❌ ✅
个人资料编辑 ✅ ✅ ✅
个人中心标签 "普通用户" "C端会员" "能量师"
订阅引导卡片 可见 不可见 不可见
续费提醒 — 到期前7天 到期前7天
```
### C.3 按场景的用户体验流程
#### 场景 1:首次进入
```
所有用户
│
├─ 微信登录 → 完善资料(昵称/头像/生日/性别)
│
└─ 输入生日 → 生成命盘 → 查看命盘图 → 查看主性格概要(免费1次)
│
├─ 遇到付费墙(完整解读/PDF/无限问答)
│ └─ 看到 ¥131 开通引导卡片
│
└─ 保持免费用户,每日 3 轮问答 + 1 次分享
```
#### 场景 2:升级为 C端年费(¥131)
```
普通用户点击"开通 ¥131"
│
├─ 微信支付 ¥131
├─ vipType → 'annual', vipEndTime → +365天
├─ 推广码自动生成
│
└─ 解锁能力:
├─ AI完整解读(无限次)
├─ AI问答(不限轮数)
├─ 分享图片(不限次)
├─ PDF导出(不限次)
├─ 历史记录(全部)
├─ 分销面板可见
└─ 推广C端年费赚 40% + 5%
```
#### 场景 3:升级为能量师(¥1,314 / ¥1,986)
```
用户点击"开通能量师"
│
├─ 通过创始人码注册 + 有效期内 + 名额未满 → 种子价 ¥1,314
├─ 其他情况 → 标准价 ¥1,986
├─ 微信支付 → vipType → 'practitioner', vipEndTime → +365天
├─ 推广码自动生成(若首次付费)
│
└─ 解锁能力(在C端基础上增加):
├─ 推广能量师年费赚 ¥500 + ¥100
├─ 客户标签分组(P2)
└─ 命盘批注(P2,C端也有但用途不同)
```
### C.4 关键决策点
**能量师 vs C端——AI能力完全一致。**
两者在AI解读和问答上没有任何区别(都不限次、完整内容),差异只在于:
| 维度 | C端¥131 | 能量师¥1,314+ |
|------|---------|--------------|
| 分销产品 | 仅 C端年费 | C端年费 + 能量师年费 |
| 佣金模式 | 比例(40%+5%) | 比例 + 固定(¥500+¥100) |
| 种子价资格 | ❌ | ✅(需扫创始人码+有效期内+名额未满) |
| 客户管理 | ❌ 不需要 | ✅ 批注+标签 |
| 使用场景 | 自己看命理 | 为客户解读 |
这意味着如果能量师**不打算做推广**,¥1,314+相比¥131的额外价值只有批注和标签(P2功能)。这可能影响高客单价转化策略——需要在能量师权益中强化"商业工具"的价值感知。
**普通用户的命盘创建无限。**
目前仅限制了AI问答3轮/天和AI解读1次/天,但普通用户可创建任意数量的命盘。每个命盘可获得一次免费主性格概要,理论上可通过不停创建新命盘获取多次概要。此场景消耗成本较低(1段文本),暂不设限。
### C.5 数据库字段
```sql
-- users 表相关字段
vipType: NULL 'annual' 'practitioner'
vipEndTime: NULL 2027-05-29 2027-05-29
referralCode: NULL 'A3X7K9' 'B2Y4M8'
profile_complete: true/false true/false true/false
-- 关联表
┌─ orders ─────────────────────┐
│ user_id, product_type, │
│ amount, status, invited_by │
└──────────────────────────────┘
┌─ commissions ────────────────┐
│ order_id, level, amount, │
│ status (pending/settled) │
└──────────────────────────────┘
```
### C.6 与 US 映射
| 约束规则 | 所在用户故事 |
|---------|------------|
| AI解读:普通用户仅主性格概要1次/天 | US-3.2 |
| AI问答:普通用户3轮/天全局计数 | US-3.4 |
| 分享图片:普通用户1次/天 | US-2.1 |
| PDF导出:仅付费用户可用,不限次 | US-2.2 |
| 历史记录:普通用户仅7天 | US-5.2 |
| 推广码:仅付费后生成 | US-6.1 |
| 分销面板:仅付费用户可见 | US-6.5 |
| C端佣金:上级须已付费才结算 | US-6.4 |
| 批注:仅已付费用户 | US-7.1 |
| 标签:仅能量师(practitioner)可用 | US-7.2 |
| 学业方向AI解读:普通用户1次/天 | US-9.1 |
| 能量师介入AI会话:仅上级能量师可介入 | US-9.3 |
| 人工方案分佣:平台留存模型 | US-9.6 |