- 审计报告 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>
42 KiB
真实鉴权与匿名玩家身份 · execution 版
- 文档编号: HJ-PASSPORT-EXEC-001
- 日期: 2026-06-10
- 状态: 待主 agent 验收(评审版七项拍板已全落,见 §0)
- 唯一设计依据:
docs/agent-specs/2026-06-10-真实鉴权与匿名玩家-review.md(含 §9.1 创始人七项拍板)。本文不得与其矛盾;执行中发现矛盾,停工上报,以评审版为准修订本文。 - 读者: 实现 agent 分工认领;主 agent 验收
- 环境铁律: 构建/测试一律 mini-desktop(git push→pull 同步,本机严禁 mvn/npm build);staging 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 + 聚合侧异常剔除(剔除桩 §8;glossary/events.schema.json 已修订完毕,亲核 §2.4,不需再改) |
1. 目标与范围边界
1.1 目标
- 种子创作者真实登录:手机号验证码(Debug 渠道后台查码)→ 发 userType=MEMBER 真 OAuth2 token → 创作/发布全链真身份,A2 名单(~5 人)creator_flag 置位即可用。
- 匿名玩家零门槛:免登刷 feed / 试玩 / 遥测(读端点
@PermitAll+ anonId 透传),互动才弹一键登录。 - 邀请码旁路:陌生玩家凭
手机号+邀请码注册(零短信依赖),报备后开关退役。 - 身份衔接:登录请求携带 anonId 落
game_player.first_anon_id+ 前端登录后补发user_login遥测事件(anonId+userId 双带),1001/1 错位消除。 - staging 批跑零中断:mock(test1) 与真 token 并存;新增真 token 黄金闭环 e2e。
1.2 明确不做(继承评审版 §3,实现 agent 不得擅自扩界)
- 三方社交登录(微信/抖音/小程序);多租户开启(维持
tenant.enable=false,新表带tenant_id=1);生产短信供应商采购与签名报备(A 轨闸门);防沉迷/适龄展示(v2.0);member 模块复活及其运营包袱(积分/等级/找回密码/换绑/注销)。 - 实名收集流程不做(仅表字段预留);匿名影子账号+转正合并不做(拍板 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 不匹配抛 AccessDenied(ADMIN 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 控制器
getLoginUserId28 处 / 7 文件(grep -c 实证:aigc 5 / feed 3 / project 5 / runtime 4 / studio 4 / telemetry 3 / trade 4),admin 侧 3 处 3 文件——评审版 §1.3 计数口径完全属实。 - 各端点逐行清单见 §6.1/§6.2。
- ⚠ 评审版未提的硬边界:
contracts/db-schemas/V3.0.0__create_game_runtime.sql:94game_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-24user_id NULL+anon_id NOT NULL DEFAULT '',零改动。 RuntimePackageServiceImpl.java:194-203预览归属校验 :198userId == 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 与评审版不符处汇总(修正声明)
- ⚠ 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 实际引入"。 - ⚠ 免登清单规模:评审版估"约 7-10 个"——实查放行清单为 14 个端点(§6.1:普通读/上报 12 + ad 计费 2),评审版漏列 compliance rating 与 ad slot 拉取、session/end。
- ⚠
game_runtime_session.player_user_id NOT NULL:评审版未提(见 §2.3),V11 新增 ALTER。 - ⚠ TelemetryEventConsumer 注释口径过时:
TelemetryEventConsumer.java:12,:17-18仍写幂等键uk_dedup(traceId,event,ts),但 V10 已切uk_event_id且同步聚合实际在EventIngestServiceImpl(消费者是骨架占位)。评审版 §5A 引该文件作幂等出处时口径偏旧;幂等真身=eventId(EnvelopeReqVO.java:23-25)。不影响结论(对变造刷量无约束依然成立),如实记录。 - 其余抽查(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 依据(实证四条)
- 零 RPC:
SmsCodeService/SmsSendService/OAuth2TokenService均为 system-server 内部 bean——只有在 system 内才能直接@Resource注入;新模块只能走 system-api Feign + @Primary 本地化,重走 M1 装配教训(记忆m1-runtime-bringup-state:CommonApi 4 处 @Primary 补丁)。 - 口径一致:账号 owner=system 基座是三文档套件既定(
需求模块映射.md:237,258,评审版已核)。 - 包结构先例:system-server 已有
controller/app/{dict,ip,tenant}(§2.2 实证),app 端控制器落 system 不破坏既有约定。 - 体量不配新模块:两张表 + 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.0–V10.0.0 两处一致(diff 实证无差异),V10 已被黄金闭环占用。 - 本件占 V11.0.0:
V11.0.0__create_passport_player_invite.sql,双落点同步(契约源 + yudao-server 唯一执行副本;遵守 V10 守门②——跨模块迁移不放单模块 classpath)。 - 让位声明:
docs/mvp/MVP进度总账.md:94Wave4 community/biz 曾规划 V10/V11——V10 已被 golden loop 占、V11 被本件占,community/biz 顺延 V12/V13(总账该行回写,§15.3)。
4.2 DDL(契约级;列注释中文、带 tenant_id=1 兼容、唯一键含 deleted 与既有表同范式)
-- 玩家/创作者最小身份(拍板 6:实名字段仅预留,提现前才收集;加密=应用层 AES 落密文)
CREATE TABLE `game_player` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '玩家编号(即 OAuth2 token 的 userId,userType=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 喂 1),MODIFY 放宽 NULL 不破坏存量;anon_id DEFAULT '' 兼容。Flyway 纪律:V11 合入后禁改,回滚写补偿迁移 V11.0.1(§13)。
5. 契约先行:新增 contracts/api-schemas/passport.yaml
风格对齐 feed.yaml(openapi 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 场景门禁在 Service(status 权威判定);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,转化点,拍板 5);studio 全部(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 已确认"聚合侧剔除"为既定兜底路径。
绑定条件:
- 限频:两端点加
@RateLimiter(IP 维度 keyResolver),依赖 §7.2 的 ad pom 补齐; - 幂等已有:reportImpression/reward 幂等键=traceId(
AdController.java:54,:60注释实证),保持; - 剔除桩覆盖:§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 覆盖为登录响应 userId,1001/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-167onInteract/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-code(Debug 渠道,admin 查 system_sms_code 取码)→ sms-login 得真 token → 创作 → 发布 → admin 审核 → feed 可见 |
全程请求头无 test1(抓包/日志证据);game_project.creator_user_id=真实 userId |
| E2 | 匿名零门槛(验收 2) | 无任何 token:GET 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) | 同浏览器:匿名刷 feed(anonId=X)→ 登录携带 X → 互动 | game_player.first_anon_id=X;user_login 事件 user 双带;互动落库 userId=真实 ID(1001/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 手机号/token;refresh 配置核对;登录页占位协议展示 | 日志脱敏;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.2),revert 提交即净退;存量改动仅"@PermitAll 注解 + VO 字段 + Service 白名单挂点",逐文件 revert 无连带。
- DB 回滚:Flyway 纪律 V11 合入后禁改——回滚写补偿迁移
V11.0.1(DROP 两新表 + game_runtime_session 还原 NOT NULL 前须确认无 NULL 行;anon_id 列可留存不删,无害)。 - 行为回滚(不动代码):邀请码旁路=配置开关关(§5.4);mock 并存本身零切换风险(staging 配置未动);@PermitAll 若需紧急收口,可单独 revert 注解提交(前端匿名路径同步降级为弹登录,feed 浏览回到 test1 形态)。
- 批跑兜底:E8 失败 → 立即回滚白名单挂点提交(最可能破坏点),其余改动可保留排查。
14. 完成条件(全部满足才可宣称收口)
contracts/api-schemas/passport.yaml+contracts/db-schemas/V11.0.0__*.sql+events.schema.json登记user_login合入(契约先行);- 后端:passport 4+4 端点上线 staging;14 端点 @PermitAll 生效;白名单校验 5 挂点生效;V11 迁移绿(Flyway 历史无校验失败);
- 前端:登录页/守卫/401 拦截/邀请码入口合入,
npm run build绿; - §11 用例 E1–E9、E11–E13 全过且留证据(E10 留部署轨注记);
- 单测:核销并发/白名单校验/发码频控 三类最小单测绿(mini-desktop
mvn test); - 文档回写(§15)完成 +
docs/mvp/MVP进度总账.md登记本件收口; - R6 安全前置中"本件内"两项落地(§7.2),"切渠道前"两项列入 A 轨闸门看板备注。
15. 文档回写清单(收口时执行)
15.1 EnvelopeReqVO.java:64 anonId javadoc(修改稿,照抄)
现文案:@Schema(description = "匿名 token 派生的稳定匿名 ID", example = "anon-xyz")
改为:@Schema(description = "纯客户端生成的稳定匿名 ID(localStorage 持久化;身份不可信,防伪造/刷量由聚合侧按 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 配置非 yaml;5 渠道客户端代码已实证存在) |
| 6 | SmsSendService 真实渠道超时/重试语义 | 切渠道前核对(§12),本件不动 |
| 7 | 体量估算 3-5 个有效工作日 | 沿用评审版推断,以实际排期为准 |
所有 file:line 均为 2026-06-10 本仓实读核验;与评审版不符处已在 §2.5 如实修正。下游:实现 agent 按 §4→§5→§6/§7/§8→§9 顺序认领(契约先行),主 agent 按 §11/§14 验收。