phase1-user-stories.md 111 KB

Phase 1 用户故事与验收标准

基于 phase1-product-plan.md1.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),但允许修改(能量师可输入客户生日)

验收标准(咨询会话管理):

  1. 后端按照 userId + birthday 唯一确定一条咨询记录(同用户+同生日始终返回同一条记录)
  2. 同一用户对同一生日重复点击"开始咨询",后端直接返回已有的咨询记录 + 完整聊天历史,不创建新记录;前端 toast 提示"已找到您之前的咨询记录"
  3. 输入的生日是全新生日时,前端弹出确认对话框:"此生日将开启全新的命盘咨询,确认吗?"
  4. 用户确认后,后端计算完整的数字命盘(24个位置),创建新咨询记录,并返回结果
  5. 用户取消确认,停留在首页,不跳转、不创建
  6. 点击"开始咨询"到命盘渲染完成,总耗时不超过 3 秒
  7. 命盘计算全部由后端 CalculatorService 完成,前端只负责展示(calculator.js 保留为降级兜底,不作为主路径)
  8. 命盘页面 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 响应示例)

{
  "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. 关闭面板后高亮状态取消

验收标准(面板内容):

  1. 面板标题区域显示位置名称(如"主性格")、字母编号(如 O)、对应数字
  2. 面板内容按 Tab 切换展示:
    • Tab 1 - 含义:该位置的核心能量含义(如"主性格:代表一个人与生俱来的天赋和性格特质")
    • Tab 2 - 特征:该数字在该位置的具体性格/运势描述(如"O=7 代表分析力强、追求真理")
    • Tab 3 - 建议:该位置该数字的注意事项和提升建议
  3. 面板底部显示数字的能量强弱 / 吉凶指示(如"吉数 ★★★★☆")
  4. 若该位置数字与相邻位置形成特定组合,面板顶部提示"该数字与 X 形成 XX 组合",可点击查看组合含义
  5. 面板内容中的"组合"和"特征"描述,后续可通过管理后台配置

验收标准(交互细节):

  1. 面板高度不超过屏幕 60%,内容可滚动
  2. 面板弹出动画:从底部平滑滑入(300ms ease-out)
  3. 连续快速点击不同数字时,面板内容直接替换,不重复弹入动画
  4. 面板支持手势下滑关闭(drag-to-dismiss)

EPIC 2:分享与导出(P0)

US-2.1 命盘分享图片

作为 数字能量师
我希望 将命盘生成为一张精美的图片,分享到微信或保存到相册
以便 发给客户或在朋友圈展示

验收标准(入口与权限):

  1. 分享按钮位于命盘展示页底部,始终可见
  2. 点击分享按钮,底部弹出分享方式选择菜单:
    • "保存到相册"
    • "分享给微信好友"
    • "分享到朋友圈"
  3. 免费用户每日限制分享 1 次,点击后显示剩余次数(如"今日还剩 1 次")
  4. 免费用户用完今日次数后,按钮置灰,提示"升级能量师享无限次分享"
  5. 已付费用户不限次数,不显示剩余次数

验收标准(分享卡片生成):

  1. 分享卡片在客户端生成(使用 canvas 绘图),不依赖后端
  2. 卡片内容包含:
    • 顶部:品牌 Logo + "数字能量命理分析" 标题
    • 中部:三角命盘图(颜色和样式与 app 内一致)
    • 底部:用户姓名 + 出生日期 + 生成日期
    • 右下角:小字水印(平台名称/二维码)
  3. 卡片设计使用样式指南中的品牌色系统(主色 #B8860B / 金色系)
  4. 横向卡片比例 4:3,宽度适配主流手机屏幕(建议 1080px 基准)

验收标准(保存与分享):

  1. 保存到相册:调用 uni.saveImageToPhotosAlbum,保存成功后 toast 提示"已保存到相册"
  2. 分享给微信好友:调用 uni.share(或小程序原生转发),携带卡片图片和默认文案"看看你的数字能量命盘"
  3. 分享到朋友圈:调用小程序端朋友圈分享 API(走官方渠道)
  4. 分享后返回 app 时,若为免费用户则扣除当日次数,显示更新后的剩余次数

验收标准(限制与风控):

  1. 免费用户每日分享到微信/朋友圈的次数在服务端记录,防止客户端篡改
  2. 分享次数限制仅对"分享到微信/朋友圈"生效;保存到相册不限制次数(客户端canvas生成无法强控)
  3. 每日 0 点重置次数,后端接口 POST /api/user/share-quota 返回当日剩余次数
  4. 分享卡片不得包含用户微信号、手机号等敏感信息

US-2.2 命盘PDF导出

作为 数字能量师
我希望 将命盘导出为 PDF 文件,可以直接打印或微信发送
以便 客户获得正式的纸质/电子版报告

验收标准(权限与入口):

  1. "导出 PDF" 按钮位于命盘展示页顶部操作栏(仅已付费用户可见)
  2. 未付费用户点击不可见或置灰 + 提示"升级能量师可导出 PDF 报告"
  3. 点击后出现加载指示器(loading + 进度百分比),防止重复点击
  4. 生成过程不超过 5 秒,超过 5 秒则显示"生成较慢,请稍候…"

验收标准(PDF 内容与排版):

  1. PDF 采用 A4 竖版(210mm × 297mm),页边距上下 15mm、左右 12mm
  2. 第一页内容按以下布局:
    • 头部:品牌 Logo + "数字能量命理分析报告" 标题(居中)
    • 副标题:生成日期 + 报告编号(格式:YYYYMMDD-XXXXX)
    • 用户信息区:姓名 / 出生日期 / 主性格数字
    • 三角命盘图:占页面 40% 高度,清晰可辨各位置数字
    • 表格区:24 个位置的字母编号、数字值、位置名称三列展示
    • 左中右三区标注:左侧(21-40 岁)/ 顶部(41-60 岁)/ 右侧(61+ 岁)
  3. PDF 使用品牌色系(金色 #B8860B 为标题色,深棕色 #4A3728 为正文字体)
  4. PDF 支持中文显示,字体嵌入(或使用系统黑体/宋体)
  5. 如有命盘批注内容(US-7.1),在 PDF 第二页以附录形式呈现

验收标准(导出流程):

  1. PDF 由后端生成(通过 iText 或 Apache PDFBox 库),前端仅发起请求
  2. 前端调用 POST /api/export/pdf 传入 chartId,后端返回 PDF 文件流
  3. 前端接收文件流后,使用 uni.openDocument 打开微信文件预览
  4. 微信文件预览界面支持:转发给好友 / 保存到本地 / 发送到电脑
  5. 导出记录保存在数据库 export_logs 表,便于统计

验收标准(限制):

  1. 已付费用户导出 PDF 不限制次数
  2. 同一命盘重复导出不重新生成,直接返回已缓存的文件(缓存有效期 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 集成):

  1. 问答交互通过 Dify Chatflow(对话工作流)实现,详见附录B
  2. 前端调用 POST /api/chat/send 发送用户消息,传入 { chartId, message }
  3. 后端 ChatService 将消息转发至 Dify Chatflow API(POST /v1/chat-messages
  4. Chatflow 保持多轮对话上下文(通过 Dify 的 conversation_id),无需后端自行维护会话历史
  5. 后端在首次向 Dify 发送消息时,将命盘 24 个数字(A-X)作为 Chatflow 的 inputs 传入,后续轮次无需重复传入
  6. Dify Chatflow 返回的答案中,如果包含数字能量学专业术语,后端不做二次处理,直接透传
  7. 后端每次问答调用前检查用户配额(未付费用户当日≤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 NULLvipType='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_limitpricing.practitioner.seed_period_end

US-4.3 订阅状态与续费

作为 付费能量师
我希望 在个人中心看到我的订阅状态、到期日,并能续费
以便 管理我的会员身份

验收标准:

  1. 个人中心显示:订阅状态(能量师/C端会员/已过期/普通用户)
  2. 显示到期日期,到期前 7 天显示续费提醒
  3. 到期后自动降级为普通用户,分销面板入口隐藏
  4. 续费操作按标准价执行(种子价仅限首次,不限能量师或C端)
  5. 续费成功后有效期在原到期日基础上延长1年

US-4.4 年费升级能量师(升级定价与流程)

作为 C端年费用户
我希望 从年费升级为能量师时,按已付年费的剩余价值抵扣差价
以便 不需要重复支付已经买过的部分

验收标准(升级定价):

  1. 用户现有 vipType='annual'vipEndTime > now,可在个人中心点击"升级能量师"
  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. 升级后推广码不变(如之前已有推广码)

验收标准(前端流程):

  1. 个人中心 → 升级能量师 → 展示升级详情卡片:
    • 原年费支付金额:¥131
    • 年费剩余价值:¥95.11("已使用 100/365 天")
    • 能量师当前定价:¥1,314
    • 应付差价:¥1,218.89
    • 支付按钮:"支付 ¥1,218.89"
  2. 升级详情卡片下方附带权益对比(精简表格),说明能量师相比年费额外获得的能力:推广能量师赚固定佣金(¥500+¥100)、客户批注与标签管理等
  3. 点击支付 → 调 POST /api/pay/create{ productType: 'practitioner', isUpgrade: true, previousOrderId: xxx }
  4. 支付成功后调 paySuccess()vipType 更新 + 佣金结算(见 US-6.3 升级场景)

验收标准(接口):

  1. POST /api/pricing/upgrade 新增接口:传入 userId,返回升级价格详情
  2. 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 },引导补全资料

验收标准(注册信息完善页):

  1. 新用户或资料不全的用户,微信授权后自动跳转到注册完善页(非首页)
  2. 注册完善页表单包含以下字段:
字段 必填 预填 说明
头像 微信头像 可点击更换(从相册选择)
昵称 微信昵称 1-20 字
性别 男 / 女,radio 选择
出生日期 年月日选择器,同命盘生日
个人简介 限 100 字,用于社交展示
所在城市 微信定位或手动选择
兴趣标签 多选:读书/运动/音乐/旅行/禅修/创业/心理学/玄学
想认识 单选:不限 / 朋友 / 导师 / 同修
  1. 点击"提交"调用 POST /api/profile/complete 保存所有资料
  2. 提交后标记用户 profile_complete = true
  3. 提交成功后跳转到首页,进入正常使用流程
  4. 资料不完整的用户在个人中心显示引导 banner:"请完善个人资料,开启能量匹配"
  5. 用户可随时在个人中心 → 编辑资料 修改所有字段

验收标准(后端与数据库变更):

  1. 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 出生日期(冗余)
  1. POST /api/auth/login 响应增加 profileIncomplete 字段
  2. 新增 POST /api/profile/complete 接口,接收所有注册字段
  3. 新增 POST /api/profile/update 接口,允许修改任意字段
  4. POST /api/profile/info 响应增加全部新字段

验收标准(老用户兼容):

  1. 已注册的老用户登录时,如果 profile_complete = false,登录后显示完善引导
  2. 老用户不强制立即完善,可以正常使用命盘功能
  3. 老用户在个人中心中看到"完善资料"入口和引导

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 参数

前端缓存策略:

  1. 用户通过链接打开小程序时:
    • onLaunch(options) 读取 options.query.ref,转为大写后写入缓存 pending_referrer
    • 每次点击新链接都覆盖已有值,以用户最后一次点击的链接为准(新点击意图覆盖旧的)
  2. pending_referrer 永不过期,直到注册成功后才清除
  3. 用户注册(微信授权登录)时,从缓存读取 pending_referrer 传入注册接口

后端注册逻辑(UserService.loginOrRegister()):

  1. 用户不存在(新用户)+ referrerCode 有效 → invitedBy = 上级ID
  2. 用户不存在 + referrerCode 无效 → invitedBy = null(静默降级,不报错)
  3. 用户不存在 + referrerCode 为空 → invitedBy = null(正常注册)
  4. 用户不存在 + referrerCode自己的新openidinvitedBy = null(防止自邀请)
  5. 用户已存在 → 忽略所有 referrerCode,不覆盖已有 invitedBy(关系一次性锁定)
  6. referrerCode 查询不到对应上级 → 静默跳过,不抛异常

数据库更新:

  1. 绑定成功后,如果上级存在,inviter.directCount += 1(实时更新统计字段)

边界与异常:

  1. 用户未登录直接生成命盘(纯前端计算)→ 允许,不强制登录
  2. 登录中途中断(拒绝授权)→ 不清除 pending_referrer,下次触发登录仍携带
  3. 注册接口网络失败 → 保留 pending_referrer,重试时继续携带
  4. 同一设备切换微信账号 → 新的 openid 按新用户处理,正常绑定关系

US-6.3 B端佣金结算(能量师→能量师,固定金额)

作为 平台
我希望 当能量师A的下级B升级为能量师时,自动结算固定金额佣金
以便 能量师获得推广收益

验收标准:

触发条件:

  1. 订单 productType = "practitioner" 且支付成功 → 进入 B端佣金结算流程
  2. 订单 productType = "annual" → 走 US-6.4 C端佣金(互斥分支)

佣金计算——按推荐人身份分两种情况:

  1. 推荐人 A 是能量师(vipType='practitioner' → 按标准固定金额:
级别 默认值 配置键
L1(直接上级) ¥500 commission.practitioner.l1
L2(上上级) ¥100 commission.practitioner.l2
  1. 推荐人 A 是 C端年费用户(vipType='annual' → L1 拿半额:
级别 默认值 配置键
L1(直接上级) ¥200 commission.practitioner.l1_cend
L2(上上级) ¥100(不变) commission.practitioner.l2
  1. 推荐人 A 既不是能量师也不是 C端年费(vipType=null 等异常情况)→ L1/L2 不创建佣金
  2. 佣金单位为(避免浮点精度问题)

升级场景佣金补差(isUpgrade=true):

  1. 当订单标记 isUpgrade=true(从年费升级到能量师),L1/L2 佣金需扣除已支付过的年费佣金:

    应发佣金 = 本次应发金额 − 该买家此前 annual 订单已支付给同一收款人的佣金总额
    
  2. 计算逻辑:

    • 查询 commissions 表中,同一 buyer_id + 同一 收款人product_type='annual' 的所有佣金之和
    • 从本次 practitioner 佣金中减去该金额
    • 若差值 ≤ 0(理论情况),则本条佣金不创建
  3. 示例——首次直接购买能量师(非升级,对比参考):

    用户C为新用户(无年费记录),通过上级A(能量师)链接直接购买能量师 ¥1,314
    isUpgrade = false → 全额发放:L1 = ¥500,L2 = ¥100
    无年费佣金可扣除
    
  4. 示例——年费升级补差:

    用户B第1天付¥131(annual),上级A获¥52.40(40%)
    用户B第100天升级能量师(种子价¥1,314),上级A是能量师→应发¥500
    实际发放:¥500 − ¥52.40 = ¥447.60
    
  5. 示例——C端推荐人升级补差:

    用户B第1天付¥131(annual),上级A(C端)获¥52.40(40%)
    用户B第100天升级能量师,上级A是C端→应发半额¥200
    实际发放:¥200 − ¥52.40 = ¥147.60
    

收款人资格:

  1. L1:buyer.invitedBy 对应的上级存在 → 按上述规则计算
  2. L2:上级的 invitedBy 对应的上上级存在 → 按上述规则计算(L2 不因身份打折,仅 L1 打折)
  3. 收款人不存在时 → 静默跳过,不报错

状态与幂等:

  1. 佣金 status 直接写入 "settled"(即时到账,无 pending 冷静期)
  2. 同一 outTradeNo 重复回调 → 幂等处理,不创建重复佣金
  3. 佣金固定金额不受种子价/标准价差异影响(C端半额同样固定)

统计更新:

  1. L1 创建时:inviter.directCount += 1inviter.convertedCount += 1
  2. 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端佣金结算流程

佣金计算:

  1. 直接佣金(L1):totalFee × directRate / 10000,从 sys_config 读取 commission.annual.direct_rate(默认 4000 = 40%)
  2. 上级佣金(L2):totalFee × upstreamRate / 10000,从 sys_config 读取 commission.annual.upstream_rate(默认 500 = 5%)
  3. 金额计算使用整数截断(非四舍五入),剩余零头归平台

注意:年费佣金在后续用户升级能量师时可能被部分抵扣(见 US-6.3 升级补差逻辑)。paySuccess() 记录原始佣金(用于后续补差计算),补差逻辑在升级时执行。
因此 C端佣金的 status 仍直接写入 "settled",不需要等待升级再结算。若后续升级,再由 US-6.3 扣除已付金额。

收款人资格(与 B端不同):

  1. L1:buyer.invitedBy 对应的上级必须已付费referralCode != null),才创建佣金
  2. L2:上级的 invitedBy 对应的上上级必须已付费referralCode != null),才创建佣金
  3. C端佣金不要求收款人是"能量师"(practitioner),只要曾经付费即可

状态与幂等:

  1. 佣金 status 直接写入 "settled"
  2. 幂等处理同 US-6.3

与 B端的差异:

  1. C端佣金不更新 directCount/convertedCount/indirectCount(这些统计仅用于B端"升级能量师")
  2. 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. 付费到期后入口自动隐藏

页面结构(自上而下):

① 推广码卡片:

  1. 显示推广码 我的推广码: ABC123 + 复制按钮
  2. "生成推广海报"按钮 → 调用后端生成含小程序码的海报
  3. 无推广码(极端情况)→ 显示"推广码生成中…"

② 收益统计卡片(三列等宽):

  1. 总收益:累计所有佣金总额(status = settled
  2. 累计收益:当前总收益金额(Phase 1 不提现,故不称"可提现"以免误解;页面加注"提现功能即将开放")
  3. 今日新增:当日 00:00 至今产生的佣金总额
  4. 金额以元为单位,保留两位小数(后端存储分,前端 /100

③ 团队统计(三列):

  1. 直接下级:directCount
  2. 间接下级:indirectCount
  3. 升级下级:convertedCount
  4. 零数据时显示引导文案:"暂无推广数据,分享推广码给好友开始赚取佣金"

④ 佣金明细列表(最近50条):

  1. 显示:时间(MM-DD HH:mm)、来源(匿名"用户****")、级别(L1/L2标签)、金额(+¥XXX.XX)、状态
  2. 按时间倒序排列
  3. 空数据时显示"暂无佣金记录"引导

⑤ 推广工具区:

  1. "📱 生成推广海报" → 合成含小程序码的分享海报
  2. "🔗 复制推广链接" → 复制带 ref 参数的小程序路径到剪贴板
  3. "📊 提现申请" → 置灰不可点击,显示"即将开放"(Phase 1 不做提现)

页面状态:

  1. Loading 状态:各区块显示骨架屏脉冲动画
  2. 错误状态:顶部显示错误提示条 + "点击重试"按钮
  3. 空数据状态:佣金列表/团队统计显示引导文案
  4. 下拉刷新:重新请求所有数据

后端 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端或能量师) → 不显示订阅卡片,改为显示"✅ 已开通"徽章+生成推广海报入口

交互流程(未登录):

  1. 显示完整订阅卡片:🔮 AI解读会员 ¥131/年,列出权益(无限AI解读、个性化报告、推广赚佣金)
  2. 点击"登录后开通 ¥131" → 先跳登录页,登录后自动跳转支付页

交互流程(已登录未付费):

  1. 显示同上"立即开通 ¥131"按钮
  2. 点击 → 跳转支付页 product=annual,金额 ¥131

交互流程(已付费):

  1. 订阅卡片替换为灰色小徽章"✅ 已开通AI解读 | 有效期至 2027-05-28"
  2. 已付费用户显示"📱 生成推广海报"入口
  3. 能量师(practitioner)显示"✅ 已开通能量师"

支付页适配:

  1. payment/index.vue 支持 URL 参数 product
    • product=annual → 显示 C端年费 ¥131 和对应权益列表
    • product=practitioner → 显示能量师价格(通过 API 获取种子/标准价)
  2. createOrder 接口接收 productType 参数

后端适应:

  1. Order productType 字段新增枚举值:"annual" | "practitioner" | "practitioner_plan"(人工方案)
  2. User vipType 字段("annual" | "practitioner" | null
  3. 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. 点击后调用新接口 POST /api/chart/academic-orientation

AI 解读内容:

  1. 后端转发至 Dify 学业方向专用 Workflow,命盘24个数字(A-X)作为 inputs
  2. 解读内容至少包含以下章节:
    • 天赋倾向:基于主性格数字+外部三组数的自然天赋分析
    • 适合方向:文科倾向/理科倾向/艺术特长/体育潜能等,用百分比表示匹配度
    • 学习特征:专注力、理解方式、学习节奏偏好(基于J/K/L位置分析)
    • 亲子沟通建议:针对该命盘类型的孩子,应采用的沟通和教育方式
    • 关键期提醒:V/W/X中年区对应的升学/职业选择重要节点
  3. 每个章节独立卡片展示,可折叠展开
  4. 页面底部显示"💡 以上分析由AI生成,如需人工深度方案,可申请能量师出方案"

付费控制:

  1. 免费用户每日可查看 1 次学业方向解读(与 US-3.2 主性格概要独立配额,互不消耗)
  2. 已付费用户(C端/能量师)不限次
  3. 超出次数后显示引导卡片:"今日学业分析次数已用完,开通会员享无限次"

技术架构:

  1. 在 Dify 平台新增学业方向 Workflow,Workflow 输入为 24 个命盘数字(A-X),输出为结构化 JSON
  2. 后端 DifyService 新增方法 interpretAcademicOrientation(AcademicRequest request)
  3. 解读结果缓存同 US-3.3(同一命盘+同一天内重复请求返回缓存内容)

US-9.2 申请能量师出方案

作为 家长
我希望 在AI学业解读后,申请能量师为孩子出具深度人工方案
以便 获得比AI更个性化的专业指导

验收标准:

入口与表单:

  1. AI学业解读页底部固定"申请能量师出方案"按钮
  2. 通用AI解读页底部也显示"💼 申请人工深度方案"按钮
  3. 点击后弹出半屏表单,包含:
    • 需求类型:学业方向 / 职业规划 / 亲子关系 / 其他(单选)
    • 具体需求描述:文本输入框,限300字
    • 期望价格范围:下拉选项(¥50-99 / ¥100-299 / ¥300-499 / ¥500-999 / 面议)
  4. 提交后生成一条"方案需求单"记录

需求分配:

  1. 如果用户有 invitedBy 且上级是能量师(vipType='practitioner')→ 自动把需求单分配给该能量师
  2. 如果没有上级能量师 → 显示"暂未开放系统分配,请通过推荐链接找到专属能量师"
  3. 分配后能量师收到通知(US-8.3 消息通知或在能量师工作台看到)

数据库新增:

  1. 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. 列表显示:用户头像/昵称/命盘日期/最后活跃时间

介入流程:

  1. 能量师点击某个用户 → 进入"咨询监看"页面(只读模式查看AI聊天记录)
  2. 页面底部有"介入会话"按钮
  3. 点击后,用户侧聊天界面出现系统消息:"🔔 能量师 张三 已进入本次咨询"
  4. 能量师侧出现输入框,可发送文字消息
  5. 用户侧聊天流中,能量师消息显示为:"👤 能量师张三:消息内容"(绿色气泡,区别于AI的灰色气泡)
  6. AI继续正常回答,能量师和AI的回答在聊天中交替显示

权限边界:

  1. 用户随时可"请出能量师"(在消息长按菜单中选择"结束能量师介入")
  2. 用户主动关闭后,能量师侧显示"用户已结束本次协同咨询"
  3. 每次介入在 chat_interventions 表记录

数据库新增:

  1. chat_interventions 表:id, sessionId, practitionerId, startTime, endTime, endedBy(user/practitioner)
  2. chat_messages 表新增 senderType 枚举:ai / user / practitioner / system / proposal

US-9.4 方案价格协商

作为 能量师
我希望 在聊天中向用户发送方案提议,并可与用户协商价格
以便 双方达成一致后完成付费

验收标准:

出方案提议:

  1. 能量师在聊天输入框左侧有"📋 出方案"按钮
  2. 点击弹出结构化表单:
    • 方案标题(限50字)
    • 方案描述(限500字)
    • 方案价格(手动输入,单位元,整数,范围受系统配置限制)
  3. 发送后在聊天中显示方案卡片(嵌入消息格式)

方案卡片交互:

  1. 卡片包含:标题、描述、价格(¥XXX)、能量师名称
  2. 用户侧卡片有三个操作按钮:
    • "💰 接受并支付" → 进入 US-9.5 支付流程
    • "💬 议价" → 弹出输入框,用户输入期望价格
    • "❌ 不感兴趣" → 卡片标记为已拒绝,通知能量师
  3. 用户议价后,能量师侧收到新消息:"用户希望价格改为 ¥XXX"
  4. 能量师可:
    • 接受新价 → 发送更新后的方案卡片(价格更新)
    • 坚持原价 → 回复文字说明
    • 提出折中价 → 发送新的方案卡片

状态管理:

  1. 一条 plan_requests 记录对应多轮协商历史
  2. 每次更新价格或状态在 plan_request_logs 表记录
  3. 最大协商轮次 5 轮(超限后只能接受或拒绝当前价格)

数据库新增:

  1. 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):

  1. 读取分类佣金率:commerce.category.practitioner_plan.commission_rate(默认 3000 = 30%)
  2. 平台佣金 = 总价 × commission_rate / 10000(单位分)
  3. 能量师应结算 = 总价 - 平台佣金
  4. 如有上级推荐 → 从平台佣金中提取分销佣金:
    • 直接上级提成 = 平台佣金 × 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

交付:

  1. 能量师收到支付成功通知
  2. 能量师工作台出现"方案交付入口"
  3. 交付支持三种方式:
    • 文字方案:富文本编辑器输入,保存到 plan_deliveries.text_content
    • PDF方案:上传PDF文件(与US-2.2共用PDF生成能力)
    • 图文报告:混合内容,包含命盘截图+文字解读
  4. 交付后用户收到通知 + 聊天显示"📄 您的学业方案已交付"
  5. 用户可查看/下载方案,平台不额外限制次数

评价与完成:

  1. 用户确认接收方案 → 状态 completed
  2. 用户可对能量师服务进行评分(1-5星)+ 文字评价
  3. 评价写入 practitioner_ratings

数据库新增:

  1. plan_deliveries 表:id, planRequestId, deliveryType(text/pdf/mixed), textContent, fileUrl, createdAt
  2. 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 |

计算逻辑:

  1. platformCommission = totalFee × commission_rate / 10000
  2. referralCommission = platformCommission × referral_rate / 10000
  3. upstreamCommission = platformCommission × upstream_rate / 10000
  4. 收款人资格同 US-6.4(须已付费用户才可收款)

状态与幂等:

  1. 佣金 status 写入 "settled"(即时到账)
  2. 同一 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 对接,先定义接口 + 桩实现。

接口:

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

数据库新增:

-- 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. 取消编辑时,若有未保存内容,弹出确认框"有未保存的内容,确定放弃?"

验收标准(批注展示):

  1. 已有批注的命盘,在数字卡片上方显示小黄点标记(🟡)
  2. 退出批注模式后,命盘页底部显示批注摘要区域(展开/折叠),显示最近 3 条批注
  3. 点击"查看全部批注"跳到批注完整列表页
  4. 批注列表页按位置分组,按创建时间倒序排列
  5. 导出的 PDF(US-2.2)第二页包含批注内容
  6. 批注支持删除(长按批注 → 确认删除)

验收标准(后端与存储):

  1. 新增 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 最后修改时间
  1. 新增接口:
    • 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 个标签"

验收标准(标签展示与筛选):

  1. 命盘列表中,每条记录右侧展示标签胶囊
  2. 历史列表顶部增加标签筛选栏,可选择一个或多个标签组合筛选:
    • 未选择任何标签时:显示所有记录
    • 选择标签时:AND 逻辑(同时包含所有选中标签的记录)
    • 支持"全部"按钮一键清除筛选
  3. 已打标签的命盘记录,在列表中有标签标识,方便快速识别

验收标准(标签管理):

  1. 个人中心 → 我的标签:展示该能量师创建的所有标签
  2. 标签管理操作:
    • 编辑标签名称
    • 修改标签颜色(从预设 8 色中选择)
    • 删除标签(删除后将移除所有关联命盘上的该标签)
  3. 删除标签时弹出确认:"删除后,所有关联命盘上的该标签将被移除,确定?"

验收标准(后端与存储):

  1. 新增 user_labels 表:
字段 类型 说明
id BIGINT PK 主键
user_id BIGINT 能量师 ID
name VARCHAR(20) 标签名称
color VARCHAR(7) 颜色值(如 #4CAF50)
created_at DATETIME 创建时间
  1. 新建关联表 chart_labels(命盘-标签多对多):
字段 类型 说明
id BIGINT PK 主键
chart_id BIGINT 命盘记录 ID
label_id BIGINT 标签 ID
created_at DATETIME 关联时间
  1. 新增接口:
    • 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 表,结构如下:

    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 + 内存缓存:

  1. @PostConstruct loadAllToCache():启动时将全量配置读入 ConcurrentHashMap<String, String>
  2. 对外提供 getString(key, default)getInt(key, default) 两个读方法,O(1) 取值
  3. key 不存在时 → 返回代码中传入的 defaultValue永不抛异常
  4. updateConfig(key, value):更新 DB + 同步写入缓存,无需重启
  5. batchUpdate(Map<String, String>):循环调用 updateConfig,整个操作在 @Transactional

管理后台 API:

  1. POST /api/admin/config/list → 返回 List<SysConfig>(含全部字段)
  2. POST /api/admin/config/update → 接收 { "configKey": "value", ... } → 批量更新

小程序端 API:POST /api/pricing/current

// 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" // 种子价有效期截止时间
  }
}
  1. isSeedPrice 的计算逻辑(三重条件,全部满足才为 true):
    • 条件①:当前用户的直接推荐人(invitedBy)存在,且该推荐人 invitedBy IS NULL AND vipType='practitioner'(即推荐人是创始人)
    • 条件②:当前时间 ≤ pricing.practitioner.seed_period_end
    • 条件③:已通过创始人码付费的能量师数 < seedLimit(统计 ordersproduct_type='practitioner' AND status='paid' AND is_seed_price=true 的记录数)
    • 自然注册用户(invitedBy IS NULL)→ 条件①不满足 → isSeedPrice=false
    • seedReason 字段用于前端展示不同提示文案(如"种子价已过期"、"种子名额已满"、"需通过创始人邀请注册"等)

管理后台页面(src/views/settings/index.vue):

  1. 左侧菜单新增"系统配置"入口,路由 /settings,图标 ⚙️
  2. 页面分为三个卡片区域,每个卡片顶部有"恢复初始值"按钮(仅重置单个卡片内的字段为种子数据):
    • hover 时 tooltip 显示具体初始值(如"恢复为 ¥500")
    • 点击后弹出确认框:"将恢复本卡片内所有字段为初始值,确定吗?"

定价配置卡片:

  1. 五个输入项:能量师种子价、标准价、种子价名额上限、种子价有效期截止时间、C端年费
  2. 用户输入的是元(整数),提交时自动 ×100 转为分(有效期除外)
  3. 种子价名额上限下方实时显示"当前已使用:X / Y"(调用 countByProductTypeAndStatus + isSeedPrice=true
  4. 修改数量时,"已使用"比例随之动态变化
  5. 种子价有效期使用 el-date-picker(datetime 类型),显示格式 YYYY-MM-DD HH:mm:ss,存储格式为 ISO 8601 字符串
  6. 有效期下方显示当前状态:"🟢 有效期剩余 XX 天" 或 "🔴 已过期"(根据当前时间与 seed_period_end 比较)
  7. 定价卡片顶部注释:"种子价三重条件:①扫创始人码注册 ②在有效期内 ③名额未满,三者同时满足才生效"

B端佣金配置卡片:

  1. 三个输入项
    • L1 佣金—能量师推荐(¥500)
    • L1 佣金—C端推荐(¥200,新增)
    • L2 佣金(¥100,不限推荐人身份)
  2. 输入单位为元(整数),提交时 ×100 转为分
  3. 每个输入项下方实时显示比例计算:
    • "L1(能量师推荐)¥500 = 种子价 38.1% / 标准价 25.2%"
    • "L1(C端推荐)¥200 = 种子价 15.2% / 标准价 10.1%"
    • "L2 ¥100 = 种子价 7.6% / 标准价 5.0%"
  4. 比例随定价或佣金值的修改实时联动更新

C端佣金配置卡片:

  1. 两个输入项:直接佣金比例(40%)、上级佣金比例(5%)
  2. 输入单位为"%"(整数,如输入"40"表示40%),提交时转为万分比 ×100
  3. 下方实时显示金额分配预览:
    • "每笔 ¥131:直接 ¥52.40(40.0%)/ 上级 ¥6.55(5.0%)/ 平台 ¥72.05(55.0%)"
  4. 费率修改时金额分配实时联动更新

保存逻辑:

  1. 点击"保存全部配置"按钮 → 触发 POST /api/admin/config/update
  2. 所有字段批量提交,不逐个保存
  3. 保存前做前端校验:金额 > 0、比例 0-100%、名额上限 > 0
  4. 保存成功 → ElMessage.success("配置已保存,已实时生效")
  5. 保存失败 → ElMessage.error("保存失败:" + 错误详情)
  6. 前端无 el-form 的 reset 行为 —— 用户手动刷新页面可恢复到上次保存的值

错误与边界:

  1. 网络失败时显示"保存失败,请重试",页面不跳转,已修改的值保留在输入框中
  2. 同时两个管理员保存 → 后保存的覆盖先保存的,不做冲突检测(Phase 1 简化)
  3. 输入非法值时(如空字符串、负数),el-input-number 的 min 属性直接阻止输入
  4. 所有金额字段不允许出现小数(元为整数输入,单位转换在后端)

8.1.2 后端接口规格

POST /api/admin/config/list

// 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

// Request:
{
  "pricing.practitioner.seed": "131400",
  "commission.practitioner.l1": "60000"
}
// Response: { "code": 0 }

POST /api/admin/config/reset(可选,单条重置)

// 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 核心代码:

@Service
public class SysConfigService {
    private final ConcurrentHashMap<String, String> 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<String, String> configs) {
        configs.forEach(this::updateConfig);
    }
}

8.1.4 管理后台前端规格

文件 操作 说明
src/views/settings/index.vue 新增 系统配置页面(~200行)
src/router/index.ts 修改 新增 /settings 路由
src/layout/index.vue 修改 新增菜单项

侧边栏菜单新增:

<el-menu-item index="/settings">
  <el-icon><Setting /></el-icon>
  <span>系统配置</span>
</el-menu-item>

页面组件结构:

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

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 → 接收筛选参数,返回分页订单列表

    // 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 动态拼接)

前端页面:

  1. 表格列:ID、订单号、用户ID、产品类型("能量师" / "年费")、金额(¥格式)、种子价标记、状态(已支付/待支付)、支付时间
  2. 产品类型使用 el-tag 展示:能量师→蓝色,年费→绿色
  3. 种子价订单显示 🌱 种子 el-tag(橙色),hover 提示"通过创始人码注册"
  4. 状态使用 el-tag:已支付→success,待支付→warning
  5. 表格上方的工具栏包含两个筛选器:
    • 产品类型:el-select(全部 / 能量师 / C端年费)
    • 状态:el-select(全部 / 已支付 / 待支付)
  6. 筛选器 change 时重新请求接口
  7. 底部分页组件:显示总条数,支持切换页码

当前代码待补全:

  • AdminController.listOrders() 目前返回 Result.success(null) → 需替换为真实数据
  • Order 实体和 OrderRepository 已有,但缺 productTypeisSeedPrice 字段 → 需新增
  • 订单表 orders 需增加 product_typeis_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 → 支持分页和按级别筛选

    // 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):

  1. 顶部统计卡片(已有框架)→ 增强:
    • 已结算佣金:¥3,500.00(绿色)
    • 待结算佣金:¥0.00(黄色,Phase 1 恒为 0,保留字段显示)
    • 总佣金:¥3,500.00
    • 新增第四个卡片:总笔数(L1: 12 笔 / L2: 8 笔)
  2. 表格列(增强现有):ID、订单ID、来源用户ID、获得用户ID、级别(L1/L2 el-tag)、金额(¥格式)、备注(remark 字段)、状态、创建时间
  3. 新增级别筛选器:el-select(全部 / L1 / L2),筛选时重新请求接口
  4. 底部分页:当前 :total="commissions.length" 是前端假分页,改为后端真分页
  5. 备注列显示 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,OI,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<ChartRecord> 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=truerequireConfirmation=true 则弹确认框;③确认后再次请求;④成功后跳转 chart/index;⑤"想了解的问题"加 maxlength=100 + 实时字数显示
client/utils/api.js 新增 consultationApistart(data)POST /api/consultation/startdetail(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):

{
  "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 模式(打字机效果)

第一轮消息传入的上下文变量:

{
  "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 表格 + 标题层级
  • 示例格式:

    # 主性格数字 7
    
    ## 核心特质
    - 关键词:分析力、哲学思维、追求真理
    - 能量属性:精神导向,向内探索
    
    ## 性格描述
    主性格数字7的人天生具有强烈的分析能力和探索精神…
    
    ## 优势
    - 逻辑思维强
    - 善于发现规律
    - 独立自主
    
    ## 劣势
    - 容易多疑
    - 社交上偏孤僻
    - 过度分析导致行动迟缓
    
    ## 代表人物
    - 爱因斯坦、达芬奇
    

B.5 Java 后端集成

DifyService.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<String, Integer> 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<String, Integer> chartData,
        String conversationId,
        String query,
        String userId,
        boolean isFirstRound
    ) {
        // 1. 首轮时传入 inputs(chart 数据)
        // 2. POST /v1/chat-messages (streaming)
        // 3. 返回 SSE 流或阻塞结果
    }
}

ChatService.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<String, Integer> 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 新增配置:

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 数据库字段

-- 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