Appearance
9 Action 完整字段表
1.1 captcha —— 图形验证码
前置条件:无 频率限制:同 IP 60s 内 30 次(超出 40101005)
请求:
json
{ "action": "captcha", "payload": {} }(payload 必须为空对象,多传字段 400)
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
captchaId | string (UUID) | 凭据,120s 有效,一次性 |
png | string | 真 PNG dataURL(data:image/png;base64,…,V2.0.43+)。前端唯一正解:原生 <image :src="png"> |
svg | string | SVG 源码(含抗指纹扰动)。仅回退/桌面端用途 |
前端要点:
- 一次消费即失效(成败皆消费);任何失败分支后重新拉取
- 不做本地缓存复用;换网后直接刷新
- ⚠️ 渲染三定律(2026-09-17 真机三层实测,勿再踩):
uni_modules/svg-iconAPP 端 renderjs 对动态返回的 svg/dataURL 均渲染失败(空白无报错)— 勿用 svg-icon 承载验证码- Android 原生 image 不渲染
data:image/svg+xmldataURL(V2.0.42 及之前png字段名不副实,实为 svg dataURL → 页面空白) - 唯一正解:原生
<image>+png字段 + 后端 ≥ V2.0.43(真 PNG),三条件缺一不可
- 前端自查清单:
res.data.png前缀应为data:image/png;base64,(若是data:image/svg+xml→ 后端版本 < V2.0.43,先升后端)
1.2 sms-send —— 发送短信验证码
前置条件:先成功获取 captcha调用时机:登录页 / 绑定手机页点「获取验证码」
请求 payload:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
phone | string | ✅ | ^1[3-9]\d{9}$ | 11 位大陆手机号 |
captchaId | string | ✅ | 8~64 字符 | 1.1 返回的凭据 |
captchaCode | string | ✅ | 1~8 字符 | 用户输入(不区分大小写比对) |
clientId | string | ⬜ | ^[A-Za-z0-9_-]{1,32}$ | 短信码按 (phone, clientId) 隔离存储 |
响应 data:{ "ok": true }
处理顺序(服务端硬编码,联调排错按此序定位): 图形码校验 → IP 30/min → 手机号 60s/1 → 手机号 24h/10 → 生成 4 位码(10min TTL)→ 腾讯云下发。
⚠️ 验证码只认真实下发:生产环境没有任何固定码 / 万能码绕过,必须走完图形码 + 真实短信才能登录。 联调环境是否开启「固定码」捷径由运维配置(属内部信息)—— 需要时找运维要码,不要假设某个值可用。
错误分支(全量):
| 层 | HTTP | code | 场景 | 前端处置 |
|---|---|---|---|---|
| Zod | 400 | — | 字段缺失/格式错(msg 定位字段) | 开发期修复 |
| 图形码 | 200 | 40101003 | 验证码错误 | 提示 + 重新拉 captcha |
| 图形码 | 200 | 40101004 | >120s 过期 | 静默刷新 captcha |
| 限流 | 200 | 40101005 | 三层任一(msg 区分:IP/间隔/日上限) | 按 msg 提示,倒计时保持禁用 |
| 下发 | 200 | 40101006 | 短信通道故障 / SMS 凭据未配置期 | 提示稍后再试 + 重新拉 captcha |
前端要点:
- ⚠️ 图形码无论成败即消费 —— 任何失败分支重试前必须重新
captcha - 成功即启动 60s 倒计时(服务端 60s/1 条硬限);短信码 4 位、10min 有效、累计错 3 次即作废
1.3 sms-verify —— 预验证短信码(可选)
语义:校验短信码是否正确,consume=false 不消费——同一短信码可多次调用(分步表单:第 1 步验码 ✅ → 用户回填其他字段 → 第 2 步真正提交)。 调用时机:绑定手机页等分步场景的「下一步」前置校验;普通登录页跳过。
请求 payload:
| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
phone | string | ✅ | ^1[3-9]\d{9}$ |
smsCode | string | ✅ | ^\d{4}$(严格 4 位数字) |
clientId | string | ⬜ | 同 1.2(必须与发码/最终提交用同一 clientId) |
响应 data:{ "ok": true }
错误分支:
| code | 场景 | 处置 |
|---|---|---|
40101001 | 码不存在 / 已过期(10min)/ 已被 3 次错误作废 | 提示重新发码,回 1.2 |
40101003 | 码错误(msg=「短信验证码错误」)——会累计尝试次数,第 3 次错码作废 | 提示错误;接近 3 次时建议引导重发 |
前端要点:错误尝试计数跨 sms-verify 与 sms-login 共享(同一个 Redis 计数器)—— 分步场景预验证输错 2 次后,最终提交只剩 1 次机会。
1.4 sms-login —— 手机号验证码登录
前置条件:sms-send 成功 调用时机:登录页点「登录/注册」(新用户自动注册,本接口即 OAuth 主注册通道)
请求 payload:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
phone | string | ✅ | ^1[3-9]\d{9}$ | |
smsCode | string | ✅ | ^\d{4}$ | |
clientId | string | ⬜ | ≤32 白名单 | 会话身份 |
agreementAccepted | literal true | ✅ | 只接受 JSON true | 协议凭证;传 "true"/1 都报 40101008。前端应在未勾选时禁用按钮 |
nickname | string | ⬜ | 1~30 字符 | 仅注册时生效(如「用户8000」),老用户忽略 |
deviceInfo | object | ⬜ | 见下 | consent 留痕 + 设备元数据 |
deviceInfo 子字段(全可选):platform(enum ios/android/mini/h5,App 传 "android")、deviceModel(≤64)、deviceFingerprint(≤128)。
响应 data(新老用户统一结构):
json
{
"tokens": { "access": "<JWT·2h>", "refresh": "<JWT·30d>" },
"expiresAt": { "access": "2026-09-11T12:00:00.000Z", "refresh": "2026-10-11T..." },
"user": { /* 全字段见 1.4.1 */ },
"token_expiring": false
}1.4.1 user 全字段表(toOAuthUserOutput 真源,共 10 字段)
| 字段 | 类型 | 说明 / 前端用途 |
|---|---|---|
id | string (cuid) | 用户主键,业务 API 的 userId |
nickname | string | 注册时可传 nickname,否则系统生成 |
avatarUrl | string | null | 头像(微信渠道有值;手机号注册为 null) |
phone | string | null | 脱敏(138****8000);仅展示,不可当真值回传 |
memberLevel | number | 会员等级(0=免费),控制会员 UI |
onboardingDone | boolean | 路由分流:false → Onboarding 向导;true → 主页。⚠️ V2 实况(先跑通阶段):sms-login 暂以 isNewUser 替代「注册当次」分流,落库阶段接回本字段真分流 |
isNewUser | boolean | V2 扩展:注册当次 true(仅 sms-login 响应携带;后续登录/rotate 均 false/不携带)→ 前端弹「完善基础资料」弹窗(向导 step1) |
profileCompleteLv | number | 资料补充等级(1/2/2+),驱动 ProfileLevelCard |
profileCompletionPct | number | 资料完成度 0~100 |
hasWechatMobile | boolean | 是否绑定微信(App 渠道)—— 控制「绑定/解绑微信」入口显隐 |
hasWechatMini | boolean | 是否绑定微信(小程序渠道) |
未返回字段(openid/unionid/openidMobile/openidMini)为服务端内部标识,前端永远拿不到也不需要。
⚠️ V2 实现实况(2026-09-17):sms-login 当前仅返回
id / nickname / phone / isNewUser(V2 两阶段迁移中;上表avatarUrl / memberLevel / onboardingDone / profileComplete* / hasWechat*随 user 模块落库阶段补齐 — 参见 user 模块updateBasicProfile)。
错误分支(全量,按服务端检查顺序):
| 层 | HTTP | code | 场景 | 处置 |
|---|---|---|---|---|
| Zod | 400 | — | 含 agreementAccepted 非 literal true | 前端禁按钮预防 |
| consent | 200 | 40101008 | 协议未同意 | 提示勾选 |
| 短信码 | 200 | 40101001 | 码不存在/过期/已被 3 次错作废 | 提示重发(回 1.2) |
| 短信码 | 200 | 40101003 | 码错误(msg=「短信验证码错误」) | 提示错误 + 显示剩余尝试提示(3 次错作废) |
| 封禁 | 403 | — | 账号被封禁 | 提示联系客服 |
注意:短信码「错」与「不存在」是两个不同 code(40101003 vs 40101001),UI 文案应区分(错 → 可继续试;不存在 → 必须重发)。
1.5 callback —— 微信登录回调
前置条件:微信 OpenSDK 授权成功拿到 code(一次性,5min) 调用时机:登录页点微信按钮 → SDK 回调
请求 payload:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
code | string | ✅ | 1~128 | 微信授权码 |
source | string | ✅ | "app" | "mini" | App 端固定 "app"(开放平台移动应用凭据) |
clientId | string | ⬜ | ≤32 白名单 | |
agreementAccepted | literal true | ✅ | 同 1.4 | |
nickname / avatarUrl | string | ⬜ | ≤30 / URL ≤512 | 微信侧未返回时前端补传 |
deviceInfo | object | ⬜ | 同 1.4 |
响应 data:与 1.4 完全一致(buildAuthResponse 统一结构)。
unionid 合并(前端无感):同微信 unionid 的 app/mini 双渠道自动合并同一账号 —— 用户先在 App 微信登录、后在小程序微信登录,是同一个账号。
错误分支:
| code | 场景 | 处置 |
|---|---|---|
40101010 | 微信接口失败 / 凭据未配置 | 提示「微信登录暂不可用」,手机号登录兜底 |
40101007 | 绑定失败(code 无效/已用) | 提示重试(重新走 SDK 授权) |
40101008 | 协议未同意 | 同 1.4 |
1.6 rotate —— 刷新 token(全局拦截器核心)
鉴权:Authorization: Bearer <refresh_token>(不是 access) 调用时机:① 任何业务请求 401;② token_expiring=true(access 剩余 <30min)主动续期
请求:{ "action": "rotate", "payload": {} }
服务端验证链:RS256 验签 → 按 cid claim 反查 DeviceBinding(oauth:{cid})→ 校验 status=active → 库内密文解出与传入 token 双向比对 → 用户存在且未封禁 → 签发新双 token 并覆盖库内密文(旧 refresh 自然作废)。
响应 data:与 1.4 的 tokens/expiresAt 同构 + user + token_expiring。
错误分支:
| code | 场景 | 处置 |
|---|---|---|
40101011 | 验签失败 / binding 非 active / 密文缺失 / 用户不存在 | 清空本地会话 → 登录页,禁止重试 |
40101012 | 签名合法但与库内不匹配 = 旧 refresh 二次使用 | ⚠️ 服务端已连坐吊销该设备会话,前端唯一动作:清会话进登录页 |
1.7 logout —— 登出
鉴权:Authorization: Bearer <access_token>
请求 payload:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
scope | string | ⬜ | enum device(默认)| all | |
clientId | string | ⬜ | ≤32 白名单 | scope=device 时必传(见下方语义) |
响应 data:{ "ok": true, "scope": "all", "revoked": 3 }(revoked = 吊销会话数)
scope 语义(精确):
| scope | 服务端动作 | 连坐范围 |
|---|---|---|
device | 吊销 oauth:{clientId ?? 'default'} 这一个会话 | ⚠️ 不传 clientId 时吊销的是 oauth:default —— 如果多设备都用缺省值,会一起被登出。传 clientId 才是「只登本设备」 |
all | 吊销该用户全部 oauth:* 会话 | 所有设备的 OAuth 会话全下线;不影响设备厂商绑定(garmin:/honor: 等非 oauth: vendor) |
前端要点:无论成败(含 401),本地一律清空 token 进登录页(登出语义本地优先)。
1.8 bind-phone / unbind —— 绑定 / 解绑手机号与微信
bind-phone(Bearer access)
微信登录用户补手机号。payload = phone(^1[3-9]\d{9}$)+ smsCode(^\d{4}$)+ clientId(可选)。先走 1.2 发码。 响应:{ "ok": true, "token_expiring": false }
| code | 场景 | 处置 |
|---|---|---|
40101001 | 短信码不存在/过期 | 重发 |
40101003 | 短信码错误(3 次错作废规则同) | 提示 |
| Zod 400 | phone/smsCode 格式错 | 修正表单 |
unbind(Bearer access)
请求 payload:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
target | string | ⬜ | enum "mobile" | "mini";缺省 = mobile+mini 两者全解(⚠️ 不是「按当前账号判断」) |
硬约束:解绑后必须仍保留至少一种登录方式(手机号或任一微信渠道),否则 40101007。 响应:{ "ok": true, "token_expiring": false }
1.9 mySessions —— 登录设备/会话列表(V2.0.40+)
前置条件:Authorization: Bearer <access_token>频率限制:无特殊限制
请求:
json
{ "action": "mySessions", "payload": {} }响应 data(⚠️ 实测结构:sessions 包一层数组):
json
{
"sessions": [
{
"clientId": "android:550e8400-e29b-41d4-a716-446655440000",
"deviceName": "Xiaomi 23049RAD8C",
"lastActiveAt": "2026-09-17T00:23:48.586222",
"expiresAt": "2026-12-16T00:23:48.586222",
"current": true
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
clientId | string | 会话标识(复合格式 类型:设备UUID;存量旧格式无 : 为类型级共享) |
deviceName | string|null | 登录时 deviceName 传入的会话展示名(旧会话可能为 null) |
lastActiveAt | string (ISO) | 最近活跃时间(rotate 或登录时间) |
expiresAt | string|null | 设备信任 90 天滑动窗口到期 |
current | boolean | 当前设备标记(字段名是 current,不是 isCurrent) |
前端要点:
- 设备上限 10 台(LRU 自动淘汰最旧,当前设备豁免)— UI 建议展示该上限提示
- 列表按 lastActiveAt 降序返回
current=true的设备不可(也不应)对自己 revokeSession
1.10 revokeSession —— 踢下线指定会话(V2.0.40+)
前置条件:Authorization: Bearer <access_token>(⚠️ 是 access,不是 refresh)
请求:
json
{ "action": "revokeSession", "payload": { "target": "android:550e8400-..." } }| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
target | string | ✅ | 目标会话的 clientId(权威键;clientId 为兼容旧键,二选一) |
响应 data:{ "revoked": "android:550e8400-..." }
错误分支:
| code | HTTP | 场景 |
|---|---|---|
40001001 | 400 | payload 缺 target(V2.0.41 及之前会误返 40201007 兜底) |
40101015 | 401 | 目标会话不存在或已下线(业务性 401 — 前端 toast 即止,勿触发 rotate/登出逻辑) |
前端要点:
- 被踢设备下次任意请求 → 其 refresh 已 revoke →
40101011/40101012→ 强登出 - 操作者自身会话不受影响(设备级隔离)
- UI 必须二次确认(踢下线是不可逆动作,对端需重新完整登录)
前端要点:hasWechatMobile + hasWechatMini + phone 三个字段决定解绑入口显隐与 target 传法 —— 只绑了一个渠道时解绑它会被拒(40101007),UI 应提前禁用并解释。