- 范围:全仓除已完成的 game-cloud/game-admin(及排除区);91 文件 - cn.iocoder.yudao→com.wanxiang.huijing / cn.wanxiang.game→com.wanxiang.huijing.game / cn.iocoder.cloud→com.wanxiang / Yudao*→Huijing* / bare yudao→huijing - 含 contracts/api-schemas(apiInterface FQCN)、docs/architecture+agent-specs(含中文名,git ls-files -z 修复)、AGENTS.md/CLAUDE.md 等根文档 - 保留不动:上游归属 URL(gitee/github yudaocode·YunaiV,改则断链+违反署名)、game-cloud Flyway 迁移注释(保 checksum)、lockfile(装包重生) - 纯文档/字符串,零构建影响(game-cloud/game-admin 字节码未动) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
309 lines
17 KiB
YAML
309 lines
17 KiB
YAML
openapi: 3.0.3
|
||
# 契约 #8 API | 模块:passport(真实鉴权 / 匿名玩家 / 邀请码受限激活)| owner:passport(system 内扩展,方案乙),全员 review
|
||
# 端:/app-api(产品端 game-studio:手机号验证码登录 / 邀请码注册 / 当前用户)
|
||
# /admin-api(管理端 game-admin:邀请码生成/分页/停用 + 玩家创作者白名单置位,RBAC)
|
||
# 前缀由 huijing 框架按 controller.app/admin 包名自动添加;本文件 path 直接写含前缀的完整对外路径。
|
||
# 错误码段(落 system 段 1-002-***-***,模块归属决定,不占 game 业务 1-1xx 段;取远段避上游增长区,现最大 1-002-028):
|
||
# 1-002-090-*** = 玩家身份(手机号已注册/未注册/被禁用/非白名单创作被拒…)
|
||
# 1-002-091-*** = 邀请码(无效/已停用/已过期/已耗尽/旁路已退役…)
|
||
# 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功。
|
||
# 拍板依据:2026-06-10 鉴权七项(评审版 §9.1)——拍板1=C 验证码为主+邀请码旁路 / 拍板3 开放注册(受限期凭码)/
|
||
# 拍板4 创作限 A2 白名单 / 拍板5 互动即弹一键登录 / 拍板7=B 纯客户端 anonId。
|
||
# 鉴权口径:send-sms-code / sms-login / invite-register 三个 @PermitAll(免登);me 登录态(不加 @PermitAll)。
|
||
# 发 token=OAuth2TokenService.createAccessToken(userId, UserTypeEnum.MEMBER(1), clientId, scopes);refresh 7d/30d 由 client 配置承载。
|
||
# 安全基线:日志与响应手机号一律脱敏(如 138****0000);token 不落日志(验收5)。
|
||
# 防枚举口径:send-sms-code 响应不区分手机号是否已注册(见端点 description)。
|
||
# 边界 OUT:不做三方社交登录 / 不开多租户(tenant.enable=false,新表 tenant_id=1)/ 不做实名收集流程(仅表字段预留)/
|
||
# 不做匿名影子账号合并(拍板5=A)/ 不做服务端 anonId 校验签名(拍板7=B 既定代价)/ game-admin 前端零改动(邀请码经 Knife4j/脚本调 admin-api,RBAC 管控,§5.3)。
|
||
info:
|
||
title: 绘境AI passport 模块 API
|
||
version: 1.0.0
|
||
description: >-
|
||
真实鉴权与匿名玩家身份:种子创作者手机号验证码登录(发真 userType=MEMBER OAuth2 token)+
|
||
陌生玩家邀请码受限激活注册(零短信依赖,报备后退役)+ 当前用户信息(前端守卫/白名单 UI 依据)+
|
||
管理端邀请码全生命周期运营(生成/分页/停用)与创作者白名单置位(A2 名单)。
|
||
匿名玩家免登刷 feed/试玩/遥测(读端点 @PermitAll,见 feed/runtime/telemetry 契约),互动才弹一键登录。
|
||
覆盖产品功能 P-ACC(账号鉴权)受限激活与 A2 白名单口径。
|
||
前端据此 vite-plugin-mock 自动生成 mock。
|
||
|
||
servers:
|
||
- url: http://localhost:48080
|
||
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
|
||
|
||
paths:
|
||
# ===========================================================================
|
||
# app 端(产品端 game-studio):验证码登录 / 邀请码注册 / 当前用户
|
||
# ===========================================================================
|
||
/app-api/passport/send-sms-code:
|
||
post:
|
||
tags: [app-passport]
|
||
summary: 发送登录短信验证码(@PermitAll;拍板1C 主路径)
|
||
description: >-
|
||
场景固定 SmsSceneEnum.MEMBER_LOGIN(1);种子期走 Debug 渠道(码落 system_sms_code 表,运营后台查码)。
|
||
鉴权:@PermitAll 免登(R6 安全前置:发码端点加 @RateLimiter IP 维度限频 + IP 每日/每小时上限,切真实渠道前补图形验证码,见 execution §7)。
|
||
防枚举口径:响应不区分手机号是否已注册(已注册→登录路径用;未注册→自动注册路径用,均发码),统一返回成功,避免手机号是否注册被探测。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/SendSmsCodeReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功(发码受理;不区分手机号是否已注册)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
/app-api/passport/sms-login:
|
||
post:
|
||
tags: [app-passport]
|
||
summary: 手机号验证码登录(已注册登录 / 未注册自动注册一体,@PermitAll;拍板3 开放注册)
|
||
description: >-
|
||
鉴权:@PermitAll 免登。已注册→登录;未注册→自动注册(register_channel=sms)+ 登录一体(验证码即占有证明)。
|
||
anonId 可选:仅注册时落 game_player.first_anon_id 一次(匿名↔登录归因衔接,目标3;缺失落空串,归因降级不阻断登录)。
|
||
竞态兜底:uk_mobile 唯一键冲突 → 静默转登录语义(与防枚举口径协调)。
|
||
前端登录成功后另补发 user_login 遥测事件(envelope.user 双带 userId+anonId,见 events.schema.json)。
|
||
响应安全基线:手机号脱敏、token 不落日志。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/SmsLoginReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功(返回真 OAuth2 token 与玩家身份摘要)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultLogin' }
|
||
|
||
/app-api/passport/invite-register:
|
||
post:
|
||
tags: [app-passport]
|
||
summary: 邀请码受限激活注册(手机号+邀请码,零短信依赖,@PermitAll;拍板1C/2/3)
|
||
description: >-
|
||
鉴权:@PermitAll 免登,受退役开关 wanxiang.passport.invite-register-enabled 管控(§5.4,关闭后返回 1-002-091 旁路已退役错误码)。
|
||
报备前陌生玩家旁路:mobile+inviteCode 注册(register_channel=invite,关联核销的 invite_code_id),零短信依赖(拍板1C)。
|
||
核销语义:核销与建号同一本地事务,条件更新原子累加(耗尽/停用/过期由前置查询给精确 1-002-091 错误码,条件更新做最终一致兜底),建号失败整体回滚。
|
||
手机号已注册 → 提示直接验证码登录(错误码 1-002-090);anonId 可选,落 first_anon_id(同 sms-login 口径)。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/InviteRegisterReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功(返回真 OAuth2 token 与玩家身份摘要,同 sms-login 响应)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultLogin' }
|
||
|
||
/app-api/passport/me:
|
||
get:
|
||
tags: [app-passport]
|
||
summary: 当前登录玩家信息(登录态,不加 @PermitAll;前端守卫/白名单 UI 依据)
|
||
description: >-
|
||
鉴权:登录态必需(无 @PermitAll,未登录返回 401)。
|
||
返回当前 token 对应玩家身份摘要,手机号脱敏(138****0000)。前端据 creatorFlag 渲染创作入口/守卫。
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultPlayerMe' }
|
||
|
||
# ===========================================================================
|
||
# admin 端(管理端 game-admin):邀请码生成/分页/停用 + 玩家创作者白名单置位
|
||
# RBAC 权限位:邀请码 system:passport-invite:*;玩家 system:passport-player:*(信任边界在后端,不依赖前端,§5.3)
|
||
# ===========================================================================
|
||
/admin-api/passport/invite-code/generate:
|
||
post:
|
||
tags: [admin-passport]
|
||
summary: 批量生成邀请码(RBAC system:passport-invite:create;拍板2 受限激活)
|
||
description: >-
|
||
随机生成 count 个邀请码(大写字母数字、避易混淆字符,uk_code 冲突重试),统一批次号/限量/有效期。
|
||
权限位 system:passport-invite:create。运营经 Knife4j(/doc.html) 或脚本调用(game-admin 零页面,§5.3)。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/InviteCodeGenerateReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功(返回生成的码值列表)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultInviteCodeList' }
|
||
|
||
/admin-api/passport/invite-code/page:
|
||
get:
|
||
tags: [admin-passport]
|
||
summary: 邀请码分页查询(RBAC system:passport-invite:query)
|
||
description: 按 batchNo/status 过滤分页查邀请码,含 used_count 核销进度。权限位 system:passport-invite:query。
|
||
parameters:
|
||
- { name: batchNo, in: query, required: false, schema: { type: string }, description: 批次号过滤 }
|
||
- { name: status, in: query, required: false, schema: { type: integer, enum: [0, 1] }, description: '状态过滤:0启用 1停用' }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultInviteCodePage' }
|
||
|
||
/admin-api/passport/invite-code/disable:
|
||
put:
|
||
tags: [admin-passport]
|
||
summary: 单码停用/退役(RBAC system:passport-invite:update)
|
||
description: >-
|
||
将指定邀请码 status 置 1(单码退役;全量旁路退役走配置开关 §5.4)。权限位 system:passport-invite:update。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/InviteCodeDisableReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
/admin-api/passport/player/set-creator:
|
||
put:
|
||
tags: [admin-passport]
|
||
summary: 玩家创作者白名单置位(RBAC system:passport-player:update;拍板4 A2 名单入口)
|
||
description: >-
|
||
置位/取消玩家的 creator_flag(A2 种子白名单的运营动作落点,拍板4)。
|
||
置位后该玩家方可走创作/发布写入口(后端 PlayerApi#validateCreator 校验,信任边界在后端)。
|
||
权限位 system:passport-player:update。
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/PlayerSetCreatorReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
components:
|
||
schemas:
|
||
# ---- Huijing CommonResult 信封 ----
|
||
CommonResultBoolean:
|
||
type: object
|
||
properties:
|
||
code: { type: integer, description: '0=成功,非0=错误码 1-002-090/091-***' }
|
||
data: { type: boolean }
|
||
msg: { type: string }
|
||
CommonResultLogin:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/LoginRespVO' }
|
||
msg: { type: string }
|
||
CommonResultPlayerMe:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/PlayerMeRespVO' }
|
||
msg: { type: string }
|
||
CommonResultInviteCodeList:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { type: array, items: { $ref: '#/components/schemas/InviteCodeRespVO' } }
|
||
msg: { type: string }
|
||
CommonResultInviteCodePage:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data:
|
||
type: object
|
||
properties:
|
||
list: { type: array, items: { $ref: '#/components/schemas/InviteCodeRespVO' } }
|
||
total: { type: integer, format: int64 }
|
||
msg: { type: string }
|
||
|
||
# ---- app 端请求 ----
|
||
SendSmsCodeReqVO:
|
||
type: object
|
||
required: [mobile]
|
||
properties:
|
||
mobile: { type: string, maxLength: 11, description: '手机号(场景固定 MEMBER_LOGIN;响应不区分是否已注册,防枚举)' }
|
||
SmsLoginReqVO:
|
||
type: object
|
||
required: [mobile, code]
|
||
properties:
|
||
mobile: { type: string, maxLength: 11, description: 手机号(登录主键) }
|
||
code: { type: string, description: '短信验证码(Debug 渠道运营查 system_sms_code 表)' }
|
||
anonId: { type: string, maxLength: 64, description: '客户端 anonId(可选;仅注册时落 first_anon_id 一次,匿名↔登录归因,拍板7=B)' }
|
||
InviteRegisterReqVO:
|
||
type: object
|
||
required: [mobile, inviteCode]
|
||
properties:
|
||
mobile: { type: string, maxLength: 11, description: 手机号(登录主键) }
|
||
inviteCode: { type: string, maxLength: 16, description: '邀请码(核销语义见 execution §4.2;无效/停用/过期/耗尽返回 1-002-091)' }
|
||
anonId: { type: string, maxLength: 64, description: '客户端 anonId(可选;同 sms-login 口径)' }
|
||
nickname: { type: string, maxLength: 30, description: '昵称(可选;不传则默认生成)' }
|
||
|
||
# ---- app 端响应 ----
|
||
LoginRespVO:
|
||
type: object
|
||
description: 登录/注册成功响应(sms-login 与 invite-register 共用);token 不落日志,手机号不在此返回
|
||
properties:
|
||
userId: { type: integer, format: int64, description: '玩家编号(即 OAuth2 token 的 userId,userType=MEMBER)' }
|
||
accessToken: { type: string, description: '访问令牌(Bearer);不得落日志' }
|
||
refreshToken: { type: string, description: '刷新令牌(refresh 7d/30d 由 OAuth2 client 配置承载,§3.4)' }
|
||
expiresTime: { type: integer, format: int64, description: '访问令牌过期时间戳(毫秒)' }
|
||
nickname: { type: string, description: 昵称 }
|
||
creatorFlag: { type: integer, description: '创作者白名单位:0玩家 1创作者(拍板4;前端据此渲染创作入口)' }
|
||
PlayerMeRespVO:
|
||
type: object
|
||
description: 当前登录玩家信息(前端守卫/白名单 UI 依据);手机号脱敏返回
|
||
properties:
|
||
userId: { type: integer, format: int64, description: 玩家编号 }
|
||
mobile: { type: string, description: '脱敏手机号(如 138****0000;安全基线,禁回明文)' }
|
||
nickname: { type: string, description: 昵称 }
|
||
avatar: { type: string, description: 头像 URL }
|
||
creatorFlag: { type: integer, description: '创作者白名单位:0玩家 1创作者' }
|
||
|
||
# ---- admin 端请求 ----
|
||
InviteCodeGenerateReqVO:
|
||
type: object
|
||
required: [count, maxUses]
|
||
properties:
|
||
count: { type: integer, minimum: 1, maximum: 100, description: '本批生成数量(≤100)' }
|
||
maxUses: { type: integer, minimum: 1, default: 1, description: '单码最大核销次数(限量)' }
|
||
expireTime: { type: string, format: date-time, description: '有效期(可选;不传=不过期)' }
|
||
batchNo: { type: string, maxLength: 32, description: '批次号(可选;运营按渠道发放归因,如 batch-xhs-01)' }
|
||
remark: { type: string, maxLength: 255, description: '备注(可选;发放对象/用途)' }
|
||
InviteCodeDisableReqVO:
|
||
type: object
|
||
required: [id]
|
||
properties:
|
||
id: { type: integer, format: int64, description: '邀请码编号(停用即 status=1,单码退役)' }
|
||
PlayerSetCreatorReqVO:
|
||
type: object
|
||
required: [userId, creatorFlag]
|
||
properties:
|
||
userId: { type: integer, format: int64, description: '玩家编号(game_player.id)' }
|
||
creatorFlag: { type: integer, enum: [0, 1], description: '创作者白名单位:0取消 1置位(A2 名单,拍板4)' }
|
||
|
||
# ---- admin 端响应 ----
|
||
InviteCodeRespVO:
|
||
type: object
|
||
description: 邀请码(生成返回 / 分页项;含核销进度)
|
||
properties:
|
||
id: { type: integer, format: int64, description: 编号 }
|
||
code: { type: string, description: 邀请码/口令 }
|
||
batchNo: { type: string, description: 批次号 }
|
||
maxUses: { type: integer, description: 最大核销次数 }
|
||
usedCount: { type: integer, description: '已核销次数(核销进度)' }
|
||
expireTime: { type: string, format: date-time, description: '有效期(NULL=不过期)' }
|
||
status: { type: integer, description: '状态:0启用 1停用' }
|
||
remark: { type: string, description: 备注 }
|
||
createTime: { type: string, format: date-time, description: 创建时间 }
|