openapi: 3.0.3 info: title: Patbond API — Auth & Me(第一批公开接口) version: 1.0.0 description: | Patbond 第一迭代「真实登录纵切」公开契约(冻结稿的正式化,字段与草案零偏差)。 ## 通用约定(development-plan 第 6 节) - 公开接口统一前缀 `/api/v1`;JSON 字段一律 `camelCase`;资源 ID 为 UUID 字符串。 - 所有时间字段为 ISO 8601 且带时区偏移(如 `2026-09-04T04:05:06.789Z`)。 - 统一响应信封 `{"code": 0, "message": "success", "data": …}`;错误同时携带正确的 HTTP 状态码与稳定业务码,业务码永不复用或改号。 - `/internal/**` 为服务间接口,不属于本公开契约,需 `X-Internal-Token` 服务凭证, 未携带或错误一律 401。 ## 错误码表 | 业务码 | HTTP | 场景 | | --- | --- | --- | | 0 | 200 | 成功 | | 40000 | 400 | 参数校验失败(含 JSON 不可解析;message 为首个字段错误) | | 40100 | 401 | 用户名或密码错误 | | 40101 | 401 | access token 无效或过期(缺失、伪造、篡改、过期) | | 40102 | 401 | refresh token 已失效或被重用(未知、过期、已轮换、已退出、家族已撤销) | | 40400 | 404 | 资源不存在 | | 40900 | 409 | 用户名已存在(大小写不敏感) | | 40901 | 409 | 手机号已被使用 | | 42300 | 423 | 登录失败次数过多,账号已临时锁定(见下) | | 50000 | 500 | 服务器内部错误 | | 50300 | 503 | 依赖服务暂不可用 | ## 会话模型(ADR-003,数值均为服务端配置项) - access token:JWT(RS256),有效期 15 分钟;由资源服务用公钥本地验签。 - refresh token:不透明随机串,有效期 30 天;**每次刷新即轮换**,旧值立即失效。 - 已轮换/已失效的 refresh token 再次被使用时,判定为重用,**整个 token family (该登录会话链)全部撤销**,持有者需重新登录。 - 允许多设备并行会话;退出仅撤销当前会话(由所提交的 refreshToken 标识), 其他设备不受影响。已签发的 access token 在剩余有效期内仍可用。 - 登录失败限制:同一账号在 15 分钟窗口内密码错误累计 5 次(配置项),账号锁定 15 分钟;锁定期间即使密码正确也返回 423/42300;一次成功登录重置计数窗口。 servers: - url: http://127.0.0.1:8081 description: patbond-auth(本地开发,/api/v1/auth/**) - url: http://127.0.0.1:8082 description: patbond-user(本地开发,/api/v1/me) tags: - name: auth description: 注册 / 登录 / 刷新 / 退出(patbond-auth) - name: user description: 当前用户(patbond-user) paths: /api/v1/auth/register: post: tags: [auth] summary: 注册并创建会话 operationId: register requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterRequest' responses: '200': description: 注册成功,返回令牌对 content: application/json: schema: $ref: '#/components/schemas/AuthTokenEnvelope' '400': $ref: '#/components/responses/ValidationError' '409': description: 用户名或手机号已被占用(code 40900 / 40901) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: usernameTaken: value: { code: 40900, message: 用户名已存在, data: null } phoneTaken: value: { code: 40901, message: 手机号已被使用, data: null } /api/v1/auth/login: post: tags: [auth] summary: 登录并创建会话 description: 多设备并行:每次登录开启独立会话(独立 token family),互不影响。 operationId: login requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: 登录成功,返回令牌对 content: application/json: schema: $ref: '#/components/schemas/AuthTokenEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': description: 用户名或密码错误(code 40100) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: invalidCredentials: value: { code: 40100, message: 用户名或密码错误, data: null } '423': description: 登录失败次数过多,账号临时锁定(code 42300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: locked: value: { code: 42300, message: 登录失败次数过多,账号已临时锁定, data: null } /api/v1/auth/refresh: post: tags: [auth] summary: 轮换 refresh token description: | 成功时返回全新令牌对,旧 refreshToken 立即失效(轮换)。提交已轮换或已失效的 refreshToken 返回 401/40102,且视为重用攻击:该 token family 的全部会话被撤销。 operationId: refresh requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RefreshRequest' responses: '200': description: 轮换成功,返回新的令牌对 content: application/json: schema: $ref: '#/components/schemas/AuthTokenEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': description: refresh token 已失效或被重用(code 40102) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: invalidated: value: { code: 40102, message: refresh token 已失效或被重用, data: null } /api/v1/auth/logout: post: tags: [auth] summary: 退出(撤销当前会话) description: | 撤销 body 中 refreshToken 对应的会话;其他设备的会话不受影响(ADR-003)。 需携带有效的 access token(从中取用户身份,防止跨账号撤销)。幂等:对已 失效的 refreshToken 仍返回成功。 operationId: logout security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LogoutRequest' responses: '200': description: 已退出 content: application/json: schema: $ref: '#/components/schemas/VoidEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' /api/v1/me: get: tags: [user] summary: 当前用户资料 description: 由 patbond-user 提供;access token 以 RS256 公钥本地验签,无需经过 auth 服务。 operationId: me security: - bearerAuth: [] responses: '200': description: 当前用户 content: application/json: schema: $ref: '#/components/schemas/MeEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 用户不存在(如已注销;code 40400) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: 'Authorization: Bearer (RS256 JWT)' responses: ValidationError: description: 参数校验失败(code 40000) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: validation: value: { code: 40000, message: 参数校验失败, data: null } AccessTokenInvalid: description: access token 缺失、无效或过期(code 40101) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: tokenInvalid: value: { code: 40101, message: token 无效或过期, data: null } schemas: RegisterRequest: type: object required: [username, password] properties: username: type: string minLength: 3 maxLength: 32 description: 用户名,大小写不敏感唯一 example: demo_user phone: type: string pattern: '^\+[1-9][0-9]{7,14}$' description: 手机号,E.164 格式;可选,唯一 example: '+8613800138000' password: type: string format: password minLength: 6 maxLength: 64 example: secret123 nickname: type: string minLength: 1 maxLength: 32 description: 昵称;可选(冻结稿之外的可选扩展字段,前端可忽略) example: 小柴 LoginRequest: type: object required: [username, password] properties: username: type: string example: demo_user password: type: string format: password example: secret123 RefreshRequest: type: object required: [refreshToken] properties: refreshToken: type: string description: 当前持有的 refresh token(不透明随机串) example: Zx3v…43位base64url…Qk LogoutRequest: type: object required: [refreshToken] properties: refreshToken: type: string description: 要撤销的当前会话的 refresh token example: Zx3v…43位base64url…Qk AuthTokens: type: object description: 注册 / 登录 / 刷新共用的令牌对(冻结契约,恰好这 6 个字段) required: - userId - tokenType - accessToken - accessTokenExpiresAt - refreshToken - refreshTokenExpiresAt properties: userId: type: string format: uuid example: 019212aa-0000-7000-8000-000000000001 tokenType: type: string enum: [Bearer] example: Bearer accessToken: type: string description: RS256 JWT,有效期 15 分钟(配置项) example: eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI… accessTokenExpiresAt: type: string format: date-time description: ISO 8601 带时区 example: '2026-09-04T04:20:06.789Z' refreshToken: type: string description: 不透明随机串,有效期 30 天(配置项),每次刷新轮换 example: Zx3v…43位base64url…Qk refreshTokenExpiresAt: type: string format: date-time example: '2026-10-04T04:05:06.789Z' Me: type: object description: 当前用户资料(冻结契约,恰好这 4 个字段) required: [userId, username, createdAt] properties: userId: type: string format: uuid example: 019212aa-0000-7000-8000-000000000001 username: type: string example: demo_user phone: type: string nullable: true description: E.164;未绑定时为 null example: '+8613800138000' createdAt: type: string format: date-time example: '2026-09-04T04:05:06.789Z' AuthTokenEnvelope: type: object required: [code, message] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/AuthTokens' MeEnvelope: type: object required: [code, message] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/Me' VoidEnvelope: type: object required: [code, message] properties: code: type: integer enum: [0] message: type: string example: success data: nullable: true example: null ErrorEnvelope: type: object required: [code, message] properties: code: type: integer description: 稳定业务错误码(见顶部错误码表) example: 40101 message: type: string example: token 无效或过期 data: nullable: true example: null