---
url: /docs/api-docs/v1/oauth/errors.md
---
# OAuth 错误码矩阵（HTTP 视角 · 40101001 ~ 40101012）

> 📍 来源：OAuth 文档 `errors.html`
> 与 [🔵 API Docs → 错误码矩阵（40101xxx）](/api-docs/v1/oauth/error-codes) **互补**：本文按 HTTP 视角，彼文按业务码视角。

***

## 1. 业务错误统一包络

> **业务错误统一 HTTP 200 + `code: 40101xxx`**。协议层错误（4xx/5xx）走 HTTP 状态码。
>
> 完整枚举定义真源：`server/src/modules/oauth/oauth.errors.ts`（12 工厂，由本文档与该真源双向收敛）。

## 2. 错误码全量矩阵（按 code 升序）

| code | msg（默认） | HTTP | 触发场景 | 前端处置 |
|---|---|---|---|---|
| `40101001` | token 无效 | **401** | access/refresh 解析失败、签名不匹配、**refresh 被轮换作废（重放）** | 区分场景：业务请求 401 → 走 rotate 单飞；rotate 自身 401 → **立即登出，禁止重试** |
| `40101002` | token 已过期 | **401** | access 超过 2h | 同上，走 rotate |
| `40101003` | 图形验证码错误 | **400** | captchaCode 不匹配 | 提示错误 + **重新拉 captcha**（旧码已消费） |
| `40101004` | 图形验证码已过期 | **400** | >120s 未使用 | **静默刷新** captcha，不打扰用户 |
| `40101005` | 短信发送过于频繁 | **429** | 限流三层任一触发（msg 区分：IP / 60s 间隔 / 日上限） | 按 msg 文案提示；按钮倒计时遵循 [限流规则](/api-docs/v1/oauth/rate-limits) |
| `40101006` | 短信发送失败 | **502** | 短信通道故障 / SMS 未配置 | 提示稍后再试；**需重新拉 captcha** |
| `40101007` | 微信绑定失败 | **400** | code 无效、解绑后无剩余登录方式 | 提示重试；解绑场景检查前置条件 |
| `40101008` | 需先同意用户协议与隐私政策 | **412** | `agreementAccepted !== true` | **前端预防**：未勾选协议时禁用登录按钮，不该让此错误发生 |
| `40101009` | 手机号格式不正确 | **400** | 非 11 位大陆手机号 | 输入框即时校验，理论不该发生 |
| `40101010` | 微信接口调用失败 | **502** | 微信侧故障 / **凭据未配置** | 提示「微信登录暂不可用」，留手机号登录兜底 |
| `40101011` | 授权范围（scope）不合法 | **400** | logout scope 非 device/all | 前端枚举约束，理论不该发生 |
| `40101012` | OAuth 服务内部错误 | **500** | 服务端异常 | 提示「系统繁忙」，可安全重试 |

## 3. 处置策略三分法（前端统一封装建议）

```typescript
type UiAction = 'silent-refresh-captcha' | 'rotate-then-retry' | 'force-logout' | 'toast' | 'bug';

function toUiAction(code: number): UiAction {
  if (code === 40101003 || code === 40101006) return 'silent-refresh-captcha';
  if (code === 40101001 || code === 40101002) return 'rotate-then-retry'; // rotate 自身失败→force-logout
  if (code === 40101004) return 'silent-refresh-captcha';
  if (code >= 40101005 && code <= 40101012) return 'toast';               // 提示类
  return 'bug'; // 未知码：上报埋点，勿死循环
}
```

| UiAction | 适用错误码 | 前端动作 |
|---|---|---|
| **silent-refresh-captcha** | 40101003 / 40101004 / 40101006 | 静默调用 `captcha` action 刷新图形码，**不打断用户** |
| **rotate-then-retry** | 40101001 / 40101002 | 业务请求 401 → 走 rotate 单飞；rotate 自身 401 → 立即登出 |
| **toast** | 40101005 / 40101007 / 40101008 / 40101009 / 40101010 / 40101011 / 40101012 | uni.showToast(msg) |
| **bug** | 未知码 | 上报埋点，**勿死循环重试** |

## 4. 协议层错误（非 40101xxx）

| HTTP | 场景 | 处置 |
|---|---|---|
| **400**（`path: message` 格式） | Zod 校验失败（字段缺失/超长/格式错） | 开发期修复，按 `msg` 定位字段 |
| **404** | action 不存在 / 路径错误 | 检查统一入口 URL 与 action 拼写 |
| **429** | 触发全局限流 | 退避重试 |
| **5xx** | 网关/服务异常 | 退避重试 + 上报 |

## 5. ⚠️ 与 error-codes.md 的差异

| 维度 | 本文件（errors.html 视角）| error-codes.md（API Docs 视角） |
|---|---|---|
| **HTTP 状态码** | 401 / 400 / 429 / 502 / 412 / 500 | 统一 **200**（业务码视角）|
| **错误码范围** | 40101001 ~ 40101012（12 个）| 40101001 ~ 40101012 + 通用 400/403/500 |
| **面向读者** | 前端工程师（联调时排查 HTTP 错）| 后端 + 全栈（理解业务码体系）|
| **404/5xx 协议层** | 包含 | 不包含（仅业务码）|

**Why 两份**：OAuth 文档站（v2 生产版）按 HTTP 状态码视角组织；项目自己的 [error-codes.md](/api-docs/v1/oauth/error-codes) 按业务码视角组织。两者**互补**而非冲突。

## 6. 错误码一致性原则

* **错误码 string 值**必须稳定（`"40101003"` 不是数字 40101003）
* **HTTP 状态码**根据 §2 矩阵确定（业务错误 200 vs 协议错误 4xx/5xx）
* **msg 文案**前端可直接显示，无需翻译
* 任何错误码变动需同步更新本文 + [error-codes.md](/api-docs/v1/oauth/error-codes) + [🟢 Internal Wiki → 错误码体系](/internal-wiki/architecture/error-codes)
