games-development-ai/docs/agent-specs/2026-06-10-真实鉴权与匿名玩家-execution.md
zizi 8a59a35133 docs(audit)+docs(mvp): 三向审计修复全链入库——报告+W1回填+创始人拍板波+W2清洗+三件产出
- 审计报告 HJ-AUDIT-001 入库 + W1 文档回填波(技术决策版/Doc B/开发团队版废止横幅/AGENTS.md/两规则档/tech-decisions)
- 创始人拍板波(2026-06-10 晚,回填铁律逐项执行):
  · R4=维持 D2 按 D2 改建(M-c 建 merge→idle→tycoon,动作类降 P1 留契约)→ D2 复审注+tech-decisions #6
  · 鉴权七项全拍(§9.1:C 验证码+邀请码旁路/受限激活/开放注册/创作限白名单/一键登录/实名提现前/纯客户端 anonId)→ glossary+events.schema.json 同步
  · A1 闸门看板建账(12+1 项含短信报备+大模型登记/算法备案/分账选型,主体已确认)+ 律所合规咨询 brief
  · 奇绩 ★1/2/3 定稿 + 品牌造梦→绘境清扫(4 档正文+文件名、demo 改名 huijing-ai-demo.html、禁投/禁外发横幅、内部引用 5 处)
  · M-c 四细节(两批 merge 先行/idle 纯前端+storage/10 校准+20 正式/拖拽为主)
  · R3 收口(叠加规则=IP 从创作者份额出·净额基数·平台恒 20%,eCPM 档 15/30/60)→ D3 复审注+glossary+BP:299 勘误
- W2 对外清洗收口:BP 改造版红线清洗(20 处锁风系红线词清零/独家→非独家/绝对化清零/06-10 实证+三线排序入文,留 3★ 待创始人)
- 三件产出:鉴权 execution 版(V11/system 内扩展/14 @PermitAll 端点/NOT NULL 硬边界)+ Mc 模板波 review 版(已拍)+ 单位经济敏感性模型(基准 324 元/月/千DAU,覆盖基建需 1.33 万 DAU→实证 B 端现金线优先)
- 两本账同步:作战清单(五件拍板项清零)+ 进度总账(三行执行记录)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 08:51:30 +00:00

448 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 真实鉴权与匿名玩家身份 · execution 版
- **文档编号**: HJ-PASSPORT-EXEC-001
- **日期**: 2026-06-10
- **状态**: 待主 agent 验收(评审版七项拍板已全落,见 §0
- **唯一设计依据**: `docs/agent-specs/2026-06-10-真实鉴权与匿名玩家-review.md`(含 §9.1 创始人七项拍板)。本文不得与其矛盾;执行中发现矛盾,停工上报,以评审版为准修订本文。
- **读者**: 实现 agent 分工认领;主 agent 验收
- **环境铁律**: 构建/测试一律 mini-desktopgit push→pull 同步,本机严禁 mvn/npm buildstaging app 在 mini-desktop `http://100.64.0.7:48080`;批跑依赖 `Bearer test1` 的链路**零中断**§10 保障措施);密钥只走环境变量;含中文 SQL 必须 `--default-character-set=utf8mb4`;前端验证门=`npm run build`vue-tsc --noEmit 是假门禁,记忆 `frontend-spine-built`)。
---
## 0. 拍板前提(评审版 §9.1,七项全拍,本文全按拍板结果起草)
| # | 决策 | 拍板结果(本文落点) |
|---|---|---|
| 1 | 首发登录方式 | **C验证码为主 + 邀请码旁路**§5 passport 端点三件套;旁路退役开关 §5.4 |
| 2 | 报备前激活玩家漏斗 | **是·受限激活**邀请码注册实现§4.2 邀请码表 + §5.2 |
| 3 | 玩家注册 | **开放**(受限期凭邀请码;报备后 `invite-register` 退役、验证码全量) |
| 4 | 创作/发布 | **限 A2 白名单**`game_player.creator_flag` + PlayerApi 校验§6.3 |
| 5 | 游客转正 | **互动即弹一键登录**(前端 §9.4;影子账号合并不做) |
| 6 | 实名收集 | **提现前**`game_player` 仅预留加密字段,本件不做收集流程) |
| 7 | 匿名身份 | **B纯客户端 anonId + 聚合侧异常剔除**(剔除桩 §8glossary/events.schema.json 已修订完毕,亲核 §2.4,不需再改) |
---
## 1. 目标与范围边界
### 1.1 目标
1. **种子创作者真实登录**手机号验证码Debug 渠道后台查码)→ 发 userType=MEMBER 真 OAuth2 token → 创作/发布全链真身份A2 名单(~5 人creator_flag 置位即可用。
2. **匿名玩家零门槛**:免登刷 feed / 试玩 / 遥测(读端点 `@PermitAll` + anonId 透传),互动才弹一键登录。
3. **邀请码旁路**:陌生玩家凭 `手机号+邀请码` 注册(零短信依赖),报备后开关退役。
4. **身份衔接**:登录请求携带 anonId 落 `game_player.first_anon_id` + 前端登录后补发 `user_login` 遥测事件anonId+userId 双带1001/1 错位消除。
5. **staging 批跑零中断**mock(test1) 与真 token 并存;新增真 token 黄金闭环 e2e。
### 1.2 明确不做(继承评审版 §3实现 agent 不得擅自扩界)
- 三方社交登录(微信/抖音/小程序);多租户开启(维持 `tenant.enable=false`,新表带 `tenant_id=1`生产短信供应商采购与签名报备A 轨闸门);防沉迷/适龄展示v2.0member 模块复活及其运营包袱(积分/等级/找回密码/换绑/注销)。
- **实名收集流程不做**(仅表字段预留);**匿名影子账号+转正合并不做**(拍板 5=A
- **服务端匿名票据不做**(拍板 7=B聚合侧剔除只做**最小桩**§8阈值可粗暴
- **生产 profile 不在本件建立**`yudao-server/src/main/resources/` 现仅 `application-{dev,local,staging}.yaml`(实查 2026-06-10——"生产关死 mock + test1→401 负路径"挂生产部署轨,本件只在 spec 留验收注记§11 用例 E10
- **game-admin 前端零改动**(含邀请码管理页,裁决见 §5.4)。
- **不做** anonId 服务端校验/签名(拍板 7=B 的既定代价);不做 P-ACC-02 全功能(隐私政策仅占位文案,评审版 §3 口径声明)。
---
## 2. 代码现状亲核账2026-06-10 逐文件实查;与评审版不符处 ⚠ 标出)
> 行号以 dev/2.0.0 工作区为准,漂移时以语义定位。
### 2.1 鉴权框架与配置
| 文件 | 亲核事实 | 与评审版比对 |
|---|---|---|
| `game-cloud/yudao-framework/yudao-spring-boot-starter-security/.../filter/TokenAuthenticationFilter.java` | :60-65 先 `buildLoginUserByToken` 查真 token、查不到才 `mockLoginUser`:92-95 userType 不匹配抛 AccessDeniedADMIN token 调 app-api 被拒);:110 注释"线上一定要关":117-129 mock 实现(:118 `mockEnable` 开关、:122 `mockSecret` 前缀、:126 截后缀为 userId | 一致 |
| `.../config/SecurityProperties.java` | :38-40 `mockEnable` 默认 false:44 `mockSecret` 默认 "test":45 `permitAllUrls` 配置位 | 一致 |
| `.../config/YudaoWebSecurityConfigurerAdapter.java` | :125-141 收集 `@PermitAll` 注解 URL 按 HttpMethod 放行 + :141 合并 `permit-all-urls` 配置——**加注解即免登,无需改安全配置类** | 评审版未展开,机制实证 |
| `game-cloud/yudao-server/src/main/resources/application-staging.yaml` | :153 `tenant.enable: false`:154-155 `captcha.enable: false`:157 `mock-enable: true`:158-159 `permit-all-urls: [/actuator/**]` | 一致(评审版引 :152-157 |
| `yudao-server/src/main/resources/` | 仅 application-{dev,local,staging}.yaml 三份 profile**生产 profile 未建立** | 一致 |
### 2.2 system 基座能力(落点依据)
| 文件 | 亲核事实 |
|---|---|
| `yudao-module-system-server/.../service/sms/SmsCodeServiceImpl.java` | :53-67 发码频控仅手机号维度(:56-59 send-frequency、:61-63 当日上限);**:65-66 按 IP 每日/每小时限制是上游 TODO**:93-110 验码(不存在/过期/已用三态) |
| `yudao-module-system-api/.../enums/sms/SmsSceneEnum.java` | :19 `MEMBER_LOGIN(1, "user-sms-login", "会员用户 - 手机号登陆")` 场景已内置 |
| `yudao-module-system-server/.../controller/admin/auth/AuthController.java` | :129-135 `POST /sms-login` + :138-139 `POST /send-sms-code``@PermitAll`、:120-123 `POST /register`——**admin 侧验证码登录范式完整passport 照此克隆** |
| `yudao-module-system-server/.../api/oauth2/OAuth2TokenApiImpl.java` | `@RestController + @Primary`M1 修法注释"同 PermissionApiImpl 范式"`createAccessToken(userId, userType, clientId, scopes)` user-type 无关,可直发 MEMBER token |
| `yudao-module-system-server/.../controller/app/` | 已有 `app/dict``app/ip``app/tenant` 三个 app 端控制器目录——**system 内建 app 端控制器有既成先例** |
| `yudao-module-system-api/.../enums/ErrorCodeConstants.java` | system 段 `1_002_***_***`,现用业务子段最大 **1_002_028**notify send |
| `yudao-module-system-server/pom.xml` | :100-104 protection starter 依赖**被注释**"TODO 芋艿:暂时去掉" |
### 2.3 存量 app 端控制器(@PermitAll 改造面)
- `@PermitAll` 在 game-module-* 全部 server 代码 **0 命中**grep 实证)——评审版 §1.3"app-api 无一处 @PermitAll"属实。
- app 控制器 `getLoginUserId` **28 处 / 7 文件**grep -c 实证aigc 5 / feed 3 / project 5 / runtime 4 / studio 4 / telemetry 3 / trade 4admin 侧 3 处 3 文件——评审版 §1.3 计数口径**完全属实**。
- 各端点逐行清单见 §6.1/§6.2。
-**评审版未提的硬边界**`contracts/db-schemas/V3.0.0__create_game_runtime.sql:94` `game_runtime_session.player_user_id BIGINT NOT NULL`——匿名开试玩会话session/start userId=null会撞 NOT NULL 约束(`RuntimeSessionServiceImpl.java:50 setPlayerUserId(userId)` 直落)。**V11 必须 ALTER**§4.3),否则"匿名试玩"只是半截。
- telemetry 侧匿名已留位:`V5.0.0__create_game_telemetry.sql:23-24` `user_id NULL` + `anon_id NOT NULL DEFAULT ''`,零改动。
- `RuntimePackageServiceImpl.java:194-203` 预览归属校验 :198 `userId == null` 即拒——**取包端点放开后 play 场景免登、preview 场景天然兜底**,无需新代码。
### 2.4 拍板 7=B 的文档同步现状(亲核,不需本件再改)
| 文件 | 现状 |
|---|---|
| `.agents/knowledge/glossary.md`"DataPermission 之外的隔离"词条,约 :40 | **已修订**:免登读路径 + 纯客户端 anonId + 聚合侧剔除 + ⚠2026-06-10 显式变更声明 |
| `contracts/events.schema.json:28` | **已修订**"纯客户端生成的稳定匿名 ID…身份不可信防伪造/刷量由聚合侧…剔除兜底——2026-06-10 鉴权拍板 §9-7=B…" |
| ⚠ `game-module-telemetry/.../vo/EnvelopeReqVO.java:64` | **未修订**,仍写"匿名 token 派生的稳定匿名 ID"——本件回写§15.1 给修改稿)。评审版引 :61-66实为 UserVO :59-66、anonId 在 :64微差如实记 |
### 2.5 与评审版不符处汇总(修正声明)
1.**protection starter 引入面**:评审版 §5A 称"仅 yudao-server/system/bpm 引入"——实查 system pom :100-104 与 bpm pom :85 均为**注释状态**,真正生效的只有 `yudao-server/pom.xml:207`(外加 yudao-dependencies :356 的版本管理。结论不变game-module-* 均未依赖),但引入面表述修正为"仅 yudao-server 实际引入"。
2.**免登清单规模**:评审版估"约 7-10 个"——实查放行清单为 **14 个端点**§6.1:普通读/上报 12 + ad 计费 2评审版漏列 compliance rating 与 ad slot 拉取、session/end。
3.**`game_runtime_session.player_user_id NOT NULL`**:评审版未提(见 §2.3V11 新增 ALTER。
4.**TelemetryEventConsumer 注释口径过时**`TelemetryEventConsumer.java:12,:17-18` 仍写幂等键 `uk_dedup(traceId,event,ts)`,但 V10 已切 `uk_event_id` 且同步聚合实际在 `EventIngestServiceImpl`(消费者是骨架占位)。评审版 §5A 引该文件作幂等出处时口径偏旧;幂等真身=eventId`EnvelopeReqVO.java:23-25`)。不影响结论(对变造刷量无约束依然成立),如实记录。
5. 其余抽查mock 过滤器顺序、staging yaml 行号、28 处/7 文件计数、SmsCodeServiceImpl :65-66、AppFeedController.interact :66 anon_id 预埋注释)**与评审版一致**。
---
## 3. 落点定稿system 内扩展(评审版倾向项,正式采纳)
**结论:不建新模块 game-module-passport在 `yudao-module-system-server` 内以独立子包 `passport` 扩展。**
### 3.1 依据(实证四条)
1. **零 RPC**`SmsCodeService`/`SmsSendService`/`OAuth2TokenService` 均为 system-server 内部 bean——只有在 system 内才能直接 `@Resource` 注入;新模块只能走 system-api Feign + @Primary 本地化,重走 M1 装配教训(记忆 `m1-runtime-bringup-state`CommonApi 4 处 @Primary 补丁)。
2. **口径一致**:账号 owner=system 基座是三文档套件既定(`需求模块映射.md:237,258`,评审版已核)。
3. **包结构先例**system-server 已有 `controller/app/{dict,ip,tenant}`§2.2 实证app 端控制器落 system 不破坏既有约定。
4. **体量不配新模块**:两张表 + 4 个 app 端点 + 3 个 admin 端点,走 game-module 黄金模板全套(三 pom/契约/错误码段/AGENTS 登记)为过度工程。
### 3.2 包路径fork 侵入隔离:全部新文件收敛在 `passport` 子包上游合并冲突面≈0呼应评审版 R5
```
yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/
├── controller/app/passport/ # 新建AppPassportController + vo/
├── service/passport/ # 新建PassportService(Impl)、InviteCodeService(Impl)
├── dal/dataobject/passport/ # 新建PlayerDO、InviteCodeDO
└── dal/mysql/passport/ # 新建PlayerMapper、InviteCodeMapper
yudao-module-system-server/.../controller/admin/passport/ # 新建:邀请码管理 3 端点§5.4
yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/passport/
└── PlayerApi + PlayerRespDTO # 新建跨模块创作者白名单校验§6.3
```
### 3.3 错误码段裁决
落 system 段 `1_002_***_***`(模块归属决定,不占 game 业务 1-1xx 段);为避开上游 yudao 增长区(现最大 1_002_028**取远段**
- `1_002_090_***` = 玩家身份(手机号已注册/未注册/被禁用/非白名单创作被拒…)
- `1_002_091_***` = 邀请码(无效/已停用/已过期/已耗尽/旁路已退役…)
登记处:`yudao-module-system-api/.../enums/ErrorCodeConstants.java` 文件尾追加(带"造梦AI passport 扩展段"中文注释,与上游段隔离)。
### 3.4 OAuth2 客户端与 refresh 轮换
- 发 token 用 `OAuth2TokenService.createAccessToken(userId, UserTypeEnum.MEMBER(1), clientId, scopes)`clientId 取 yudao 默认客户端(推断="default"**执行时核 `system_oauth2_client` 初始数据**)。
- refresh 7d/30d 轮换(验收 5由 client 的 `access-token-validity/refresh-token-validity` 配置承载——**执行时核对该 client 配置并按安全基线调整**`.agents/rules/security-and-reliability.md:15`)。
---
## 4. 数据契约Flyway **V11.0.0**(定稿)
### 4.1 版本号定稿与让位关系
- 实查 `contracts/db-schemas/` 与执行副本 `game-cloud/yudao-server/src/main/resources/db/migration/`**V1.0.0V10.0.0 两处一致diff 实证无差异V10 已被黄金闭环占用**。
- **本件占 V11.0.0**`V11.0.0__create_passport_player_invite.sql`,双落点同步(契约源 + yudao-server 唯一执行副本;遵守 V10 守门②——跨模块迁移不放单模块 classpath
- **让位声明**`docs/mvp/MVP进度总账.md:94` Wave4 community/biz 曾规划 V10/V11——V10 已被 golden loop 占、V11 被本件占,**community/biz 顺延 V12/V13**总账该行回写§15.3)。
### 4.2 DDL契约级列注释中文、带 tenant_id=1 兼容、唯一键含 deleted 与既有表同范式)
```sql
-- 玩家/创作者最小身份(拍板 6实名字段仅预留提现前才收集加密=应用层 AES 落密文)
CREATE TABLE `game_player` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '玩家编号(即 OAuth2 token 的 userIduserType=MEMBER',
`mobile` VARCHAR(11) NOT NULL COMMENT '手机号(登录主键;日志/响应必须脱敏,库内 MVP 明文+唯一键)',
`nickname` VARCHAR(30) NOT NULL DEFAULT '' COMMENT '昵称(注册默认生成,可改)',
`avatar` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '头像 URL',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态0正常 1禁用CommonStatusEnum',
`creator_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '创作者白名单位0玩家 1创作者A2 种子名单拍板4置位走 admin 端点)',
`real_name` VARCHAR(256) NULL COMMENT '真实姓名AES 密文预留提现前收集拍板6本件不写入',
`id_card_no` VARCHAR(256) NULL COMMENT '身份证号AES 密文预留;同上)',
`register_channel` VARCHAR(16) NOT NULL DEFAULT 'sms' COMMENT '注册通道sms验证码 / invite邀请码旁路漏斗归因',
`register_ip` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '注册 IP风控/审计)',
`invite_code_id` BIGINT NULL COMMENT '核销的邀请码编号invite 通道必填,关联 game_invite_code.id',
`first_anon_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '注册时携带的客户端 anonId匿名↔登录归因衔接评审版目标3',
`login_date` DATETIME NULL COMMENT '最近登录时间',
-- 标准审计列creator/create_time/updater/update_time/deleted/tenant_id与既有 game_* 表同范式)
PRIMARY KEY (`id`),
UNIQUE KEY `uk_mobile` (`mobile`, `deleted`, `tenant_id`) COMMENT '手机号唯一(同 V3 uk_version 含 deleted/tenant_id 范式)'
) COMMENT '玩家/创作者最小身份system passport 扩展,方案乙)';
-- 邀请码拍板1=C/拍板2 受限激活:生成/核销/限量/有效期/退役全生命周期)
CREATE TABLE `game_invite_code` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
`code` VARCHAR(16) NOT NULL COMMENT '邀请码/口令(生成时随机大写字母数字,避易混淆字符)',
`batch_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '批次号(运营按渠道发放归因,如 batch-xhs-01',
`max_uses` INT NOT NULL DEFAULT 1 COMMENT '限量:最大核销次数',
`used_count` INT NOT NULL DEFAULT 0 COMMENT '已核销次数(条件更新原子累加,见核销语义)',
`expire_time` DATETIME NULL COMMENT '有效期NULL=不过期)',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态0启用 1停用停用=单码退役;全量退役走配置开关 §5.4',
`remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注(发放对象/用途)',
-- 标准审计列同上
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`, `deleted`, `tenant_id`) COMMENT '码值唯一'
) COMMENT '邀请码报备前玩家受限激活旁路报备后退役拍板1C/2';
-- 匿名试玩会话兼容§2.3 ⚠ 硬边界player_user_id 放空 + 补 anon_id
ALTER TABLE `game_runtime_session`
MODIFY COLUMN `player_user_id` BIGINT NULL COMMENT '试玩玩家用户 ID匿名试玩为空2026-06-10 鉴权件放开;预览场景=创作者本人)',
ADD COLUMN `anon_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '客户端 anonId匿名会话归属与 telemetry anon_id 同口径)';
```
**核销语义(防并发超发,写明给实现 agent**:核销与建号同一本地事务;`UPDATE game_invite_code SET used_count = used_count + 1 WHERE id = ? AND status = 0 AND used_count < max_uses AND (expire_time IS NULL OR expire_time > NOW())`affected=0 即核销失败(耗尽/停用/过期分支由前置查询给精确错误码,条件更新做最终一致兜底)。
### 4.3 V3 既有数据兼容
`game_runtime_session` 存量行 player_user_id 均有值mock 喂 1MODIFY 放宽 NULL 不破坏存量anon_id DEFAULT '' 兼容。Flyway 纪律V11 合入后禁改,回滚写补偿迁移 V11.0.1§13
---
## 5. 契约先行:新增 `contracts/api-schemas/passport.yaml`
> 风格对齐 feed.yamlopenapi 3.0.3 + 头部中文契约注释 + CommonResult 信封 + 错误码段声明 1-002-090/091。**契约先合入,再动代码**(工作协议 §6
### 5.1 app 端(产品端 game-studio全部 `@PermitAll` 或登录态,见处置列)
| 端点 | 方法 | 鉴权 | 请求 → 响应(要点) |
|---|---|---|---|
| `/app-api/passport/send-sms-code` | POST | `@PermitAll`R6 安全前置绑定 §7 | `{mobile}``Boolean`。场景固定 `SmsSceneEnum.MEMBER_LOGIN(1)`;种子期走 Debug 渠道(码落 `system_sms_code` 表,运营后台查码);**响应不区分手机号是否已注册**(防枚举) |
| `/app-api/passport/sms-login` | POST | `@PermitAll` | `{mobile, code, anonId?}``{userId, accessToken, refreshToken, expiresTime, nickname, creatorFlag}`。已注册→登录;未注册→**自动注册**register_channel=sms+ 登录一体验证码即占有证明anonId 落 first_anon_id仅注册时写一次 |
| `/app-api/passport/invite-register` | POST | `@PermitAll`受退役开关§5.4 | `{mobile, inviteCode, anonId?, nickname?}` → 同上响应。零短信依赖(拍板 1C核销语义 §4.2;手机号已注册→提示直接验证码登录(错误码 1-002-090-xxx开关关闭→旁路已退役错误码 |
| `/app-api/passport/me` | GET | 登录态(不加 @PermitAll | → `{userId, mobile(脱敏 138****0000), nickname, avatar, creatorFlag}`。前端守卫/白名单 UI 依据 |
**响应安全基线**日志与响应手机号一律脱敏token 不落日志(验收 5
### 5.2 admin 端邀请码管理RBAC 权限位 `system:passport-invite:*`
| 端点 | 方法 | 说明 |
|---|---|---|
| `/admin-api/passport/invite-code/generate` | POST | `{count≤100, maxUses, expireTime?, batchNo?, remark?}` → 码值列表随机生成uk_code 冲突重试) |
| `/admin-api/passport/invite-code/page` | GET | 分页查batchNo/status 过滤;含 used_count 核销进度) |
| `/admin-api/passport/invite-code/disable` | PUT | 单码停用status=1 |
| `/admin-api/passport/player/set-creator` | PUT | `{userId, creatorFlag}`——A2 白名单置位入口(拍板 4 的运营动作落点) |
### 5.3 邀请码管理端裁决:**给 admin-api 端点,不建 game-admin 页面**
理由:①邀请码是报备前临时旁路(拍板 1C "报备后旁路自然退役"),为退役机制建 Vue 页面game-admin `views/wanxiang/` 现仅 adSlot/dashboard/featured/review/withdraw 五目录属一次性投入②运营动作低频生成几批、查核销、停用staging/生产经 Knife4j(`/doc.html`)或脚本调用 admin-api 即可RBAC 权限位照常管控(信任边界在后端,不依赖前端);③若报备拖期超 1 个月再评是否补页面(显式 backlog不在本件
### 5.4 旁路退役开关
`yudao-server` yaml 新增 `wanxiang.passport.invite-register-enabled: true`staging——关闭后 `invite-register` 返回"旁路已退役"错误码。退役是一次性事件,配置发布可接受,不做动态配置(最小变更)。生产 profile 建立时默认按报备状态配置(部署轨注记)。
---
## 6. 存量后端改动清单
### 6.1 `@PermitAll` 放行清单(**14 个端点,逐一实读核验**;改法=方法上加 `@jakarta.annotation.security.PermitAll` + 类 javadoc 鉴权口径同步改,每处 1-2 行)
| # | 端点 | file:line控制器实读 | 处置与备注 |
|---|---|---|---|
| 1 | GET `/app-api/feed/stream` | `game-module-feed/.../AppFeedController.java:42-48` | 放行。:46 getLoginUserId 已可空(互动态恒 false零逻辑改动 |
| 2 | GET `/app-api/feed/zones` | 同上 `:50-54` | 放行。无 userId 依赖 |
| 3 | GET `/app-api/feed/zone/stream` | 同上 `:56-61` | 放行。同 #1 |
| 4 | GET `/app-api/feed/share/{gameId}` | 同上 `:71-77` | 放行。分享落地页 OG玩家漏斗入口 |
| 5 | GET `/app-api/runtime/package/{versionId}` | `game-module-runtime/.../AppRuntimeController.java:57-69` | 放行。play 场景门禁在 Servicestatus 权威判定preview 场景 `RuntimePackageServiceImpl.java:198` userId=null 即拒,匿名不可越权预览,零新代码 |
| 6 | GET `/app-api/runtime/package/{versionId}/manifest` | 同上 `:71-91` | 放行。与 #5 同源门禁(:82 复用 getPackageManifest |
| 7 | POST `/app-api/runtime/session/start` | 同上 `:93-106` | 放行 + **依赖 V11 ALTER§4.2**`SessionStartReqVO`(现 5 字段 :22-38`anonId` 可选字段;`RuntimeSessionServiceImpl.java:50` 兼容 userId=null 落 anon_id |
| 8 | POST `/app-api/runtime/session/end` | 同上 `:108-114` | 放行。幂等收口,匿名会话同链路 |
| 9 | POST `/app-api/telemetry/events/batch` | `game-module-telemetry/.../AppTelemetryController.java:39-45` | 放行。:42 注释已declare匿名合法信封 anonId 已留位V5:24 |
| 10 | POST `/app-api/telemetry/perf/beacon` | 同上 `:47-52` | 放行。sendBeacon 兜底通道 |
| 11 | GET `/app-api/compliance/rating/get` | `game-module-compliance/.../ComplianceRatingController.java:37-45` | 放行。只读分级,无 userId评审版清单未列补入 |
| 12 | GET `/app-api/ad/slot/list-enabled` | `game-module-ad/.../AdController.java:45-51` | 放行。拉配置非计费 |
| 13 | POST `/app-api/ad/report/impression` | 同上 `:53-57` | **计费链裁决:放行**(裁决与条件见 §6.2 |
| 14 | POST `/app-api/ad/report/reward` | 同上 `:59-63` | **计费链裁决:放行**(同上) |
**不放行(保持强制登录,零改动)**feed interact`AppFeedController.java:63-69`,转化点,拍板 5studio 全部(`AppStudioController.java:44-79`aigc 全部(`AppAigcTaskController.java:44-88`project 全部(`AppProjectController.java:42-81`trade 全部(`TradeController.java:55-82`。admin 侧 3 文件AdminProjectController/ComplianceBanController/TradeAdminController自 M1 有真实登录,不在面内。
### 6.2 广告曝光端点裁决(评审版 §5A 留给本执行版的单独评审项)
**裁决:#13/#14 进免登清单,绑定三个并行条件,缺一不得合入。**
理由:①匿名玩家是种子期主要流量,曝光端点不免登 = 匿名场景广告曝光全丢 = 变现链路与分成数据在最大流量面上断(与目标 2 冲突);②当前计费为 **mock eCPM**`AdController.java:54` "mock 算 eCPM 收入落台账"实证),伪造曝光污染的是 mock 台账与 quality 信号,**尚不直接动真金**——真接广告联盟前另有对账闸门;③拍板 7=B 已确认"聚合侧剔除"为既定兜底路径。
绑定条件:
1. **限频**:两端点加 `@RateLimiter`IP 维度 keyResolver依赖 §7.2 的 ad pom 补齐;
2. **幂等已有**reportImpression/reward 幂等键=traceId`AdController.java:54,:60` 注释实证),保持;
3. **剔除桩覆盖**§8 telemetry 最小桩的异常名单输出必须**同时标注 ad 计费事件**ad 反作弊正式项 owner=compliance`技术架构与模块.md` 技术项 backlog§15.2——本件只挂桩不建正式反作弊)。
### 6.3 创作/发布 A2 白名单校验(拍板 4 的可信边界落点)
前端守卫挡不住直连 API**必须后端校验**。复用本仓既有跨模块范式telemetry→FeedApi`EventIngestServiceImpl.java:86,:195-201` Feign+@Primary 本地化实证):
- 新增 `system-api``PlayerApi#validateCreator(userId)`@FeignClient primary=false + system-server 内 `PlayerApiImpl` @RestController @Primary,照 OAuth2TokenApiImpl 范式)——非 creator_flag=1 抛 `1_002_090_***`"创作功能限内测白名单"。
- **校验挂点5 个创作写入口的 Service 入层)**studio 建草稿/提交生成(`AppStudioController.java:50,:58` 对应 Service、aigc 直发生成(`AppAigcTaskController.java:50`、project 创建/提交发布(`AppProjectController.java:50,:76`)。读自己任务/项目的端点不拦(已有归属过滤)。
- pom 改动game-module-{studio,aigc,project}-server 增 `yudao-module-system-api` 依赖(实查三者现均未依赖 system-api
- mock 兼容staging test1→userId=1 无 game_player 行——**白名单校验在 mock 身份下放行**(按"game_player 无此行 + mock-enable=true → 放行"或 staging 配置白名单豁免 userId=1执行时择一实现并在中文注释写明保证批跑生成 QA 闭环走 studio/aigc 链零中断§10
### 6.4 anonId 入参原则
仅"数据归属需要"才加session/start§6.1#7 VO 加字段)+ passport 登录注册§5.1。feed 流/分享/取包/分级**不加** anonId 入参服务端无归属诉求MVP 无个性化telemetry 信封已带——避免无消费方的橱窗参数(反孤儿设计)。
---
## 7. R6 切渠道安全前置(与"切真实短信渠道"绑定排期,不可省)
### 7.1 现状(亲核)
- 发码频控仅手机号维度;**按 IP 每日/每小时为上游 TODO**`SmsCodeServiceImpl.java:65-66`)。
- staging `captcha.enable: false``application-staging.yaml:154-155`)。
- `@RateLimiter` 所在 `yudao-spring-boot-starter-protection`**仅 `yudao-server/pom.xml:207` 实际引入**system/bpm 为注释§2.5 修正game-module-* 全无。
### 7.2 交付项
| 项 | 落点 | 时序 |
|---|---|---|
| pom 补依赖 | `yudao-module-system-server/pom.xml`(取消 :100-104 注释或重写依赖块passport 发码端点用)+ `game-module-ad-server/pom.xml`§6.2 条件 1 | 本件内 |
| 发码端点限频 | `/app-api/passport/send-sms-code``@RateLimiter`IP keyResolver阈值建议 10 次/小时/IP**推断值**,执行时与运营确认);手机号维度频控复用 SmsCodeProperties 既有 | 本件内(种子期 Debug 渠道+白名单下风险≈0提前落不伤 |
| 图形验证码开关 | 确认 `yudao.captcha.enable` 一键启用路径对 passport 发码生效admin AuthController 已有 captcha 集成范式app 端发码是否接同一 captcha 校验**执行时核**,不通则在 passport 发码前置 captcha 校验补齐) | 切真实渠道前必须验证可用 |
| IP 每日/每小时上限 | 在 passport 发码 Service 入层补(不改上游 SmsCodeServiceImpl避免 fork 侵入;以 Redis 计数实现,复用既有 redis starter | 切渠道前 |
**纪律**:切渠道 = 配置切换 + 上表后两项验证通过,二者绑定,不得只切配置。
---
## 8. §5A 聚合侧剔除最小桩owner=telemetry
**边界先立、桩可粗暴**(拍板 7=B 的配套义务):
- **落点**`game-module-telemetry-server` 新增 `service/quality/AnomalyFilterService`(名称执行时可调,包归 telemetry挂在 `EventIngestServiceImpl` 聚合链(:153 ingestOne 的 engagement 闸门处,:183-205 quality 重算前)。
- **最小实现边界(本件只做这么多)**:按 `anonId``register_ip/请求 IP` 维度做**滑动窗口计数**Redis单 anonId/单 IP 对同一 gameId 的 engagement 事件超阈值(**粗暴初值100 次/小时,推断**,可配置)→ 该批事件**照常落库**(原始数据保真)但**不进聚合/不触发 quality 重算**,中文日志记剔除原因(可审计)。
- **接口先立**`AnomalyFilterService#shouldExclude(envelope, ip): boolean` + 剔除计数指标日志——ad 反作弊owner=compliance后续接同一判定输出§6.2 条件 3
- **不做**离线回溯重算、设备指纹、模型识别v2.0`技术架构与模块.md` 补技术项后排期)。
---
## 9. 前端改动game-studio
### 9.1 登录页(新增 `src/views/login/Login.vue`Vant
- 两 Tab①手机号+验证码(主路径,调 send-sms-code/sms-login②邀请码注册mobile+inviteCode调 invite-register**受限激活期的陌生玩家入口**,页内文案注明"内测邀请")。
- 隐私政策/用户协议**占位文案**勾选(评审版 §3 口径声明P-ACC-02 占位提前,正式文案挂 A 轨法务)。
- 携带 `useUserStore().anonId` 入参(身份衔接);支持 `?redirect=` 回跳。
### 9.2 store/user.ts实读挂点确认
- `setLogin``store/user.ts:44-49` 已预留):登录成功调用,**新增 token/userId 落 localStorage 持久化**(现纯内存,刷新即丢);`logout`:52-57同步清。
- token 初值优先级改造(:27-28`localStorage 真 token > VITE_STUDIO_TOKEN(env) > 'mock-studio-token'`——**env=test1 的批跑路径原样保留**§10
- 新增 `isRealLogin()`:仅 localStorage 真 token 视为真实登录(现 `isLogin()` :38-41 因 token 永有值恒 true**不可用作守卫**——实读发现,评审版未提);守卫与互动弹窗一律用 `isRealLogin()`。staging 批跑/agent 不受影响(不走守卫的 UI 路径或 env token 直连 API
- userId 固定 '1001':30真实登录后由 setLogin 覆盖为登录响应 userId1001/1 错位在真实路径消除mock 路径维持现状,评审版 R4
### 9.3 路由守卫(`src/router/index.ts`,现无 beforeEach:95-102 实证)
- 新增 route `/login`
- meta 增 `requiresAuth: true``/create``/create/task/:taskId``/create/preview/:versionId`:46-66`/project*` 三条(:68-88
- `router.beforeEach`requiresAuth 且 `!isRealLogin()``/login?redirect=`**feed/play/share 与 /login 放行**:17-44 玩家区零门槛)。
### 9.4 401 统一拦截与互动转正
- `src/api/request.ts` 响应错误分支(:101-109 现无 401 处理,实证):补 `error.response?.status === 401` 与业务包络 `code === 401` → 清登录态 + Toast + 跳 `/login?redirect=当前路由`
- 互动即弹一键登录(拍板 5`views/feed/Feed.vue` 互动入口(:139-167 `onInteract/onShare/onReport``feedStore.interact`)前置 `isRealLogin()` 判定,未登录弹 Vant Dialog 引导跳登录(不直接发请求吃 401体验优先401 拦截作兜底)。
- 新增 `src/api/passport.ts`4 端点封装,复用 request 信封解包)。
### 9.5 遥测衔接
登录成功补发 `user_login` 事件buildEnvelope 双带 userId+anonId`telemetry/index.ts:137-169` 机制现成)——**前置**`contracts/events.schema.json` eventRegistry 登记 `user_login`(契约先行;未登记会被入口轻量校验计 rejected——`EnvelopeReqVO.java:27` 实证)。
---
## 10. staging/mock 并存与批跑零中断保障
| 机制 | 出处/措施 |
|---|---|
| 真 token 优先、mock 兜底 | `TokenAuthenticationFilter.java:60-65`(先查 OAuth2 token:63-65 查不到才 mock——**框架原生并存,无需任何开关切换** |
| staging 保持 `mock-enable: true` | `application-staging.yaml:157` 不动;`.env.staging:5 VITE_STUDIO_TOKEN=test1` 不动 |
| 批跑路径回归 | 黄金闭环 e2e/生成 QA 闭环全依赖 `Bearer test1`(记忆 `golden-loop-b1-done`):①@PermitAll 只放宽不收紧test1 请求行为不变;②白名单校验 mock 豁免§6.3);③合入后**先跑一轮既有批跑冒烟再宣并存成立**(验收 §11 E8 |
| 新增真 token 黄金闭环 e2e | §11 E1与 test1 批跑在同一 staging 并行各跑一遍,互不干扰即证并存 |
| 生产关死 mock | `mock-enable=false` + `mockSecret` 随机化(`SecurityProperties.java:44` 默认 "test" 必须改——挂生产部署轨§1.2),本件在部署清单文档留注记 |
---
## 11. e2e 用例与验收(评审版 §8 六条展开staging=mini-desktop浏览器用例走既有 CDP 配方)
| # | 用例 | 步骤要点 | 通过判据 |
|---|---|---|---|
| E1 | 真 token 创作者全链(验收 1 | A2 名单手机号 → send-sms-codeDebug 渠道admin 查 `system_sms_code` 取码)→ sms-login 得真 token → 创作 → 发布 → admin 审核 → feed 可见 | 全程请求头无 test1抓包/日志证据game_project.creator_user_id=真实 userId |
| E2 | 匿名零门槛(验收 2 | 无任何 tokenGET feed/stream、zone、share、runtime package+manifest、POST session/start+end、telemetry batch、ad slot/list-enabled、compliance rating | 全部 200`game_telemetry_event.anon_id` 落库非空;`game_runtime_session` 行 player_user_id=NULL+anon_id 非空 |
| E3 | 互动触发登录(验收 2/拍板 5 | 匿名点赞 → 前端弹登录引导;绕前端直 POST feed/interact 无 token | 前端弹窗;直连返回 401信任边界在后端 |
| E4 | 邀请码注册(拍板 1C/2/3 | admin generate 邀请码 → 陌生手机号+码 invite-register → 得 token → 点赞成功 | game_player 行 register_channel=invite、invite_code_id 关联、used_count+1 |
| E5 | 负路径·无邀请码陌生玩家 | 未注册手机号直接 sms-login→ 走验证码自动注册(开放注册,拍板 3**invite-register 用无效/耗尽/过期/停用码** | 四种码各返回对应 1-002-091 错误码;耗尽并发双发只成功 max_uses 次§4.2 条件更新) |
| E6 | 负路径·非白名单创作被拒(拍板 4 | E4 注册的玩家真 token POST studio 草稿 / aigc generate / project 创建+发布 | 均返回 1-002-090 白名单错误码A2 创作者set-creator 置位后)同端点成功 |
| E7 | 身份衔接(验收 3 | 同浏览器:匿名刷 feedanonId=X→ 登录携带 X → 互动 | game_player.first_anon_id=X`user_login` 事件 user 双带;互动落库 userId=真实 ID1001/1 错位消除) |
| E8 | mock 并存(验收 4 前半) | 改动合入后原样重跑既有批跑冒烟test1 | 既有黄金闭环/生成 QA 链零失败 |
| E9 | 旁路退役开关 | staging 临时置 `invite-register-enabled=false` 重验 invite-register | 返回"旁路已退役"错误码;验后恢复 true |
| E10 | 生产负路径(验收 4 后半) | **挂生产部署轨**:生产 profile `mock-enable=false`+随机 mockSecret 后实测 `Bearer test1` | →401。**生产 profile 未建立§2.1 实证),本件不验,部署落地后补测**,不阻塞其余验收 |
| E11 | 安全基线抽查(验收 5 | 日志 grep 手机号/tokenrefresh 配置核对;登录页占位协议展示 | 日志脱敏refresh 7d/30d 生效§3.4 执行时核);协议占位可见 |
| E12 | 运营不回归(验收 6 | game-admin 原生登录 + 审核链冒烟 | 通过admin 零改动,冒烟即可) |
| E13 | 剔除桩冒烟§8 | 脚本以单 anonId 高频灌 engagement 事件超阈值 | 原始事件照落、聚合不动、quality 不被刷动、剔除日志可见 |
---
## 12. 边界失败路径(实现 agent 必须显式处理 + 中文日志)
| 场景 | 处置 |
|---|---|
| 短信通道失败Debug 渠道落库失败 / 将来真实渠道超时) | sendSingleSms 异常 → 发码端点返回明确错误码(不吞);验证码未落库不计发送频次;切真实渠道后通道超时按既有 SmsSendService 异步链路兜底(执行时核对其重试语义,不另造) |
| 邀请码耗尽/并发核销 | §4.2 条件更新affected=0 → 核销失败错误码;建号事务整体回滚(无半成品 game_player 行) |
| 转正时 anonId 缺失 | anonId 为可选入参缺失→first_anon_id 落 '',登录正常完成(归因降级不阻断登录);前端 ensureAnonId 已有 localStorage 不可用退化路径(`store/user.ts:20-23` |
| 同手机号重复注册竞态 | uk_mobile 唯一键兜底:插入冲突 → 转登录语义或返回"已注册"错误码(与 §5.1 防枚举口径协调sms-login 路径静默转登录invite-register 路径明确报已注册) |
| token 校验链异常 | 框架既有:`buildLoginUserByToken` ServiceException → null`TokenAuthenticationFilter.java:101-104`),免登端点照常匿名通过、登录端点 401——不改框架 |
| 匿名 session 收口失败 | session/end 幂等(`AppRuntimeController.java:109` 注释实证sendBeacon 兜底不变 |
| 剔除桩 Redis 不可用 | 降级放行(聚合照常)+ WARN 日志——桩失效不可阻断遥测主链 |
---
## 13. 回滚策略
- **代码回滚**passport 全部新文件收敛在独立子包§3.2revert 提交即净退;存量改动仅"@PermitAll 注解 + VO 字段 + Service 白名单挂点",逐文件 revert 无连带。
- **DB 回滚**Flyway 纪律 V11 合入后禁改——回滚写补偿迁移 `V11.0.1`DROP 两新表 + game_runtime_session 还原 NOT NULL 前须确认无 NULL 行anon_id 列可留存不删,无害)。
- **行为回滚(不动代码)**:邀请码旁路=配置开关关§5.4mock 并存本身零切换风险staging 配置未动);@PermitAll 若需紧急收口,可单独 revert 注解提交前端匿名路径同步降级为弹登录feed 浏览回到 test1 形态)。
- **批跑兜底**E8 失败 → 立即回滚白名单挂点提交(最可能破坏点),其余改动可保留排查。
---
## 14. 完成条件(全部满足才可宣称收口)
1. `contracts/api-schemas/passport.yaml` + `contracts/db-schemas/V11.0.0__*.sql` + `events.schema.json` 登记 `user_login` 合入(契约先行);
2. 后端passport 4+4 端点上线 staging14 端点 @PermitAll 生效;白名单校验 5 挂点生效V11 迁移绿Flyway 历史无校验失败);
3. 前端:登录页/守卫/401 拦截/邀请码入口合入,`npm run build` 绿;
4. §11 用例 E1E9、E11E13 全过且留证据E10 留部署轨注记);
5. 单测:核销并发/白名单校验/发码频控 三类最小单测绿mini-desktop `mvn test`
6. 文档回写§15完成 + `docs/mvp/MVP进度总账.md` 登记本件收口;
7. R6 安全前置中"本件内"两项落地§7.2"切渠道前"两项列入 A 轨闸门看板备注。
---
## 15. 文档回写清单(收口时执行)
### 15.1 `EnvelopeReqVO.java:64` anonId javadoc修改稿照抄
现文案:`@Schema(description = "匿名 token 派生的稳定匿名 ID", example = "anon-xyz")`
改为:`@Schema(description = "纯客户端生成的稳定匿名 IDlocalStorage 持久化;身份不可信,防伪造/刷量由聚合侧按 anonId/IP 异常剔除兜底——2026-06-10 鉴权拍板 §9-7=B取代旧'匿名 token 派生'机制)", example = "anon-xyz")`
(与 `contracts/events.schema.json:28` 已修订文案逐字对齐,#5 为单一事实源。)
### 15.2 `docs/architecture/技术架构与模块.md`:补"广告曝光反作弊"技术项owner=compliance**只列清单不代改正文设计**)——内容=曝光/激励计费事件的异常剔除与对账闸门MVP 形态=消费 telemetry 剔除桩判定输出§6.2/§8真接广告联盟前升级为正式项。
### 15.3 `docs/mvp/MVP进度总账.md:94`Wave4 community/biz Flyway 规划 V10/V11 → **V12/V13**(让位声明 §4.1)。
### 15.4 `.agents/` distillation鉴权落点/邀请码机制/mock 并存配方写回 `knowledge/tech-decisions.md` 或新条目(收口时按 §7 协议判重)。
### 15.5 无需回写(亲核已完成):`glossary.md` 匿名条目、`contracts/events.schema.json` anonId 描述§2.4)。
---
## 16. 推断/待执行时定项(主 agent 知悉)
| # | 项 | 性质 |
|---|---|---|
| 1 | OAuth2 clientId="default" 及 refresh 7d/30d 的 client 配置值 | 推断,执行时核 `system_oauth2_client` 初始数据§3.4 |
| 2 | 发码 IP 限频阈值10 次/小时/IP与剔除桩阈值100 次/小时) | 推断初值可配置执行时与运营确认§7.2/§8 |
| 3 | `yudao.captcha.enable` 对 app 端 passport 发码是否原生生效 | 待执行时核admin 端集成已实证app 端路径未核不通则补前置校验§7.2 |
| 4 | 白名单校验的 mock 豁免实现形态(无行放行 vs 配置豁免 userId=1 | 执行时择一,铁律=批跑零中断§6.3 |
| 5 | Debug 短信渠道在 staging 的 `system_sms_channel` 配置现状 | 待执行时核 DB渠道为 DB 配置非 yaml5 渠道客户端代码已实证存在) |
| 6 | SmsSendService 真实渠道超时/重试语义 | 切渠道前核对§12本件不动 |
| 7 | 体量估算 3-5 个有效工作日 | 沿用评审版推断,以实际排期为准 |
---
*所有 file:line 均为 2026-06-10 本仓实读核验;与评审版不符处已在 §2.5 如实修正。下游:实现 agent 按 §4→§5→§6/§7/§8→§9 顺序认领(契约先行),主 agent 按 §11/§14 验收。*