Browse Source

docs(需求): 2026-11-12 两项需求变更说明(聊天功能仅会员可见 + 团队邀请功能及流程改造,对齐合入 3d35ab714/9afaa6e46)

Agnes Agent 17 hours ago
parent
commit
dc5522d09

+ 62 - 0
docs/需求分析/2026-11-12-团队邀请功能及流程改造需求变更.md

@@ -0,0 +1,62 @@
+# 团队邀请功能及流程改造需求变更
+
+> 变更编号:INVITE-2026-11-12-V1 | 变更日期:2026-11-12 | 版本:v1.0
+> 合入:`origin/cfclub` merge commit `9afaa6e46`(parents `8dcdd24bc` + `ac4ba5613`),2026-11-12 10:39
+
+## 一、需求原话
+
+> 改造团队邀请功能及流程,在我的页面点团队邀请时,不是生成海报,而是直接分享邀请页面,被邀请人直接填服务人员申请表,记在邀请人下。如果还未注册,先完成注册,推荐人也是邀请人
+
+## 二、现状核查
+
+- **入口**:`pages/profile-main/profile.vue`「+邀请成员」按钮与浮动按钮原 `@click="showShareQr"` 生成分享海报(ContentSharePoster),需用户手动保存/转发,链路长。
+- **服务人员申请表**:`pages/profile/service-role-apply.vue` → `POST /api/service-role/apply`(`ServiceRoleApplicationDTO`),统一表 `service_role_applications` + 审批回写 `users.*Status`;申请与邀请人**无任何归属关系**(无 inviter 字段)。
+- **注册与归属链**:`users.referrerId`(推荐人)由 `/api/invite/bind`→`CommissionService.bindReferral` 写入并发分销奖励;另有 `/api/auth/register-with-invite`(`registerByInviteCode`)。
+- **三套邀请码体系互不相通**:
+  1. 佣金/推广码 `users.referral_code`(6位 A-Z/2-9,`/api/invite/code` 生成、`/api/invite/bind` 绑定→写 `users.referrerId`)
+  2. 分销码 `distribution_relations.invite_code`(8位,`register-with-invite` 只认这套)
+  3. 家庭码 `family.invite_code`(8位 hex,`createJoinRequest` 不写 referrerId)
+  团队邀请属「邀请服务人员」,走**第①套佣金/推广码**。
+
+## 三、用户裁决口径(4 项,全部锁定)
+
+| 裁决项 | 口径 |
+|---|---|
+| 归属 | **两件都做**——记录级 `service_role_applications.inviter_user_id`(申请挂邀请人名下)+ 用户级 `users.referrerId`(推荐人=邀请人)**并存、互不替代** |
+| 注册判据 | **微信静默授权即算注册**;不引入短信验证码;**明确禁止**用 `/api/auth/register-with-invite` |
+| 邀请页形态 | **分享直达现有申请表** `service-role-apply`,不新建邀请人展示页、不加邀请人头像/昵称展示 |
+| 分销副作用 | **归属 + 发分销奖励**——邀请服务人员也算推广,走现有分销绑定、发 CF 值(真实 F24 资产,用户裁决接受) |
+
+## 四、需求变更定义
+
+| 变更 | 文件 | 内容 |
+|---|---|---|
+| S2-1 | `cfc-frontend/pages/profile-main/profile.vue` | 「+邀请成员」「浮动按钮」原生成海报入口改为内嵌透明 `<button open-type="share">`(`.share-overlay-btn`,`opacity:0` 非 `display:none`);新增页面级 `onShareAppMessage` 覆盖 mixin 默认首页分享,`path` 直指 `/pages/profile/service-role-apply?refCode=<码>`;`onShow` 预热 `loadReferralCode()` + 双缓冲;空码兜底 toast「邀请码准备中,请稍候重试」;`showShareQr()` 成死代码、海报不再触发 |
+| S2-2 | `cfc-frontend/pages/profile/service-role-apply.vue` | `onLoad` 解析 `options.refCode` 存组件字段 `inviteRefCode` + 独立 key `serviceRoleApplyRefCode`(不用会被消费清掉的 `storage.refCode`);无参分支从独立 key 恢复;未登录就地浮层登录后继续;**提交成功后串行执行推荐人绑定**(见 S2-4 资产闸门) |
+| S2-3 | 后端 `ServiceRoleApplyDTO`(+`refCode` 入参)/ `ServiceRoleApplication` 实体(+`inviterUserId`)/ `ServiceRoleApplicationServiceImpl.submit` / `schema.sql` / `DatabaseInitializer` 迁移467 / `API_REFERENCE.md` §4.44 | 记录级归属:`service_role_applications` 增 `inviter_user_id bigint NULL`(普通索引,存量不回填);`submit` 服务端按 `dto.getRefCode()` 反查 `User::getReferralCode` 写入,**剔除前端直传 inviterUserId(防冒领)**;复用无效码/自邀请拦截;`admin/list` 暴露 `inviterUserId`(`admin/operator` 权限门) |
+| S2-4 | `service-role-apply.vue` 资产闸门 | 三道防重复发 CF:前端 `userRefBound`/`bindingRef`/串行 await → 后端 `bindReferral:140-142` 抛错式幂等(已绑定在副作用前 throw)→ DB 唯一键 `cf_balance_log.uk_ref_type(member_id,type,ref_type,ref_id)` DuplicateKey 去重。失败留码可重试、失败 modal 不承诺虚假恢复路径 |
+
+### 三口径(全部在 `save()` 之前判定,不静默吞)
+
+1. 无 `refCode`(个人中心主动进入)→ `inviterUserId=null` 正常受理;
+2. 带 `refCode` 但无效/自邀请 → `Result.error("邀请码无效或不能邀请自己,请通过邀请人的分享链接进入")` 拒绝落库;
+3. 有效 `refCode` → 记录级写入 + 用户级走现有 `bindReferral`(发分销 CF)。
+
+## 五、资产一致性(发真实 CF 前的强制闸门)
+
+`doSubmit` 成功 → 才串行 `bindUserReferral`;清码移入绑定成功分支;绑定源用 `this.inviteRefCode`(两源同源防错配);`userRefBound` 双重幂等防重入;删两处空 catch(用户级失败可见、不静默)。杜绝「申请失败但 CF 已发 / 成功但绑定失败且码已清不可重试」两类错发/少发。
+
+## 六、验收与未测项
+
+- 验收:分享入口不再出海报、直达申请表带 `refCode`;未注册被邀请人静默授权即注册并继续填表;提交后记录级挂邀请人、用户级绑推荐人;重试绝不重复发 CF;无效码/自邀请拒绝落库。
+- **未测**(如实):`mvn` 编译(`.m2` 空、CI 兜底);真机/运行时端到端;邀请人查看/审批名下申请(本期明确不做)。
+- **登记技术债(不阻断)**:
+  - P2 幂等抛错误报循环:后端已成功但响应丢失→重试被 `bindReferral:140` 抛错→前端误报失败。修法=前端 catch 识别「已绑定推荐人」按成功收敛;**禁改后端**(`/api/invite/bind` 多链路共享)。
+  - P1 status view 无补绑入口:若加须直连 `bindUserReferral()`、勿接 `doSubmit`;**先 P2 后 P1**。
+  - 并发双穿应用幂等闸时 `directCount`/排行榜理论 +2(不涉真实资产,DB 唯一键兜住 CF)。
+
+## 七、关联记录
+
+- 合入实现:merge commit `9afaa6e46`(tree `b785438fa`,parent1 `8dcdd24bc` + parent2 `ac4ba5613`)
+- 文件清单(8):后端 `ServiceRoleApplyDTO.java`、`ServiceRoleApplication.java`、`ServiceRoleApplicationServiceImpl.java`、`schema.sql`、`DatabaseInitializer.java`;前端 `pages/profile-main/profile.vue`、`pages/profile/service-role-apply.vue`;文档 `docs/superpowers/api/API_REFERENCE.md`(§4.44)
+- 需求分析台账新增本章;无既有变更覆盖。

+ 48 - 0
docs/需求分析/2026-11-12-聊天功能仅会员可见需求变更.md

@@ -0,0 +1,48 @@
+# 聊天功能仅会员可见需求变更
+
+> 变更编号:CHAT-2026-11-12-V1 | 变更日期:2026-11-12 | 版本:v1.0
+> 合入:`origin/cfclub` merge commit `3d35ab714`(parents `5c4bfd487` + `63d405612`),2026-11-12 00:25
+
+## 一、需求原话
+
+> 小程序上,聊天功能只有会员才能看到,非会员入口都不能看到
+
+## 二、现状核查
+
+- 「聊天/消息类」入口在多页散落,且可见性与会员态无绑定:
+  - `pages/profile-main/profile.vue`「历史消息」菜单行(个人中心新版)
+  - `pages/profile/index.vue` → `ProfileMenu.vue`「消息历史」菜单项(个人中心旧版组件)
+  - `pages/home/app-library.vue` 与 `pages/index-home/index.vue` 首页快捷入口中 `key='ai'`(AI 管家)
+  - 四个五维详情页 `action-detail / body-detail / mind-detail / wisdom-detail` 页面内的浮动 AI 头像 `FloatingAvatar`(`components/AIFloatingAvatar.vue`),可直接唤起聊天
+- 会员判据此前各页自定义、口径不一,存在「运营开关关闭即放行非会员」的漏洞:后端 `ai.coach.member_gate_enabled` 关闭时,`/api/ai/gate` 会把未付费用户的 `status` 也置为 `ALLOW / canUse=true`;若用 `canUse`/`status` 判入口可见性,非会员会被错误放行。
+
+## 三、需求变更定义
+
+| 变更 | 文件 | 内容 |
+|---|---|---|
+| S1-1 | `utils/membershipGate.js`(**新增**) | 统一判据封装 `isActiveMember()`:唯一判据 = `POST /api/ai/gate` 返回 `data.isActive === true`。`isActive` 在运营开关判断之前按真实付费记录填充(`users.member_expire_time > now` 或 `family_memberships` `payment_status='paid'` 且 `end_date > now`),**不受运营开关、不受当日 AI 配额影响**。**fail-closed**:无 token 直接 `false`(不发请求);接口异常/超时/字段缺失一律 `false`;过期会员 `isActive=false` 一律隐藏、不补历史入口 |
+| S1-2 | 四个五维详情页 `action-detail/index.vue`、`body-detail/index.vue`、`mind-detail/index.vue`、`wisdom-detail/index.vue` | **彻底移除浮动 AI 头像**:删 `<FloatingAvatar />` 模板节点、`import FloatingAvatar`、`components` 注册项——页面级硬删除,非会员/会员都不再从详情页唤起聊天 |
+| S1-3 | `pages/home/app-library.vue` | `onShow` 每次 `refreshMemberActive()`(覆盖从升级/续费页返回),`key='ai'` 在快捷入口候选、频度候选、已锁定项合并出口多处统一过滤;`memberActive=false` 时 AI 管家不占位、不渲染 |
+| S1-4 | `pages/index-home/index.vue` | 同 S1-3:`refreshMemberActive()` + 频度候选剔除 `key='ai'` + 合并结果出口兜底过滤(连用户已锁定的 `ai` 项对非会员也不可见) |
+| S1-5 | `pages/profile-main/profile.vue` | 「历史消息」菜单行加 `v-if="memberActive"`;`onShow refreshMemberActive()`;同时移除页面浮动 AI 头像 `FloatingAvatar` |
+| S1-6 | `pages/profile/index.vue` + `pages/profile/components/ProfileMenu.vue` | `ProfileMenu` 新增 `memberActive` prop(默认 `false`,fail-closed),宿主页传入;「消息历史」菜单项加 `v-if="memberActive"`;`profile/index.vue` 注入 `refreshMemberActive()` |
+
+**判据要点**:可见性只认「真实付费有效态」`data.isActive`,与运营开关 `ai.coach.member_gate_enabled`、当日配额、`canUse/status` 全部解耦——堵住开关关闭导致非会员放行的既有漏洞。
+
+## 四、验收
+
+- 非有效会员(含过期会员):个人中心「历史消息 / 消息历史」不显示;首页快捷入口与频度入口不出现 AI 管家;五维详情页无浮动 AI 头像、无法唤起聊天。
+- 有效付费会员:上述入口全部正常显示,与改造前一致。
+- fail-closed:无 token / 接口超时 / 字段缺失时按非会员处理,入口全部隐藏。
+- 从升级/续费页返回后,入口随最新会员态刷新。
+
+## 五、风险
+
+- 判据多一次 `/api/ai/gate` 请求:各页 `onShow` 触发,已有该接口复用,无新增接口;无 token 短路不发请求。
+- 删除详情页 `FloatingAvatar` 为**页面级硬删除**(对会员也生效):需求为「聊天功能只有会员能看」,详情页浮动头像属聊天唤起入口,故按会员与非会员统一收敛为「仅保留会员专属聊天入口」,属既定口径内行为。
+
+## 六、关联记录
+
+- 合入实现:merge commit `3d35ab714`
+- 文件清单(10):`cfc-frontend/utils/membershipGate.js`(新增)+ `pages/profile-main/profile.vue`、`pages/profile/index.vue`、`pages/profile/components/ProfileMenu.vue`、`pages/home/app-library.vue`、`pages/index-home/index.vue`、`pages/action-detail/index.vue`、`pages/body-detail/index.vue`、`pages/mind-detail/index.vue`、`pages/wisdom-detail/index.vue`
+- 需求分析台账新增本章;无既有变更覆盖。