API 对接规范文档
适用对象:通过
/api/*接入 WordForge 后端的 end-user 客户端(Web、iOS、Android、wordforge-web 等)。 本文档描述全局约定,包括认证机制、请求格式、响应结构、错误处理、限流规则等。不在本文范围:内嵌的 admin GUI(
/admin/*路由)走独立的/api/admin/*+ Admin Token,属于后端的运维管理面,不属于"客户端"范畴。
1. 基础约定
| 项目 | 说明 |
|---|---|
| 基础路径 | /api |
| 数据格式 | JSON(Content-Type: application/json) |
| 字段命名 | camelCase(服务端统一使用 serde(rename_all = "camelCase")) |
| 时间格式 | ISO 8601 UTC,例:2024-01-15T08:30:00Z |
| ID 格式 | UUID v4 字符串 |
| 最大请求体 | 2 MiB(超出返回 413) |
| CORS | CORS_ORIGIN 环境变量配置,默认 http://localhost:5173 |
2. 认证机制
2.1 令牌体系
系统采用双令牌架构:
| 令牌 | 类型 | 有效期(默认) | 用途 |
|---|---|---|---|
| Access Token | HS256 JWT | 24 小时 | 访问所有受保护 API |
| Refresh Token | HS256 JWT | 7 天 | 刷新 Access Token |
| Admin Token | HS256 JWT | 2 小时 | 访问管理员 API |
JWT Claims 结构:
{
"sub": "<userId>",
"token_type": "user" | "refresh" | "admin",
"iat": 1705300200,
"exp": 1705386600,
"jti": "<uuid>"
}2.2 Token 传递方式
客户端携带 Access Token 的两种方式(优先级从高到低):
HTTP Header(推荐)
Authorization: Bearer <accessToken>Cookie(备选)
Cookie: token=<accessToken>
Refresh Token 传递方式:
Authorization: Bearer <refreshToken>(刷新接口专用)Cookie: refresh_token=<refreshToken>
2.3 Token 获取与刷新流程
注册/登录 → 返回 accessToken + 写入 Cookie(token, refresh_token)
↓
使用 accessToken 访问 API
↓
accessToken 过期 → POST /api/auth/refresh(携带 refresh_token)
↓
获得新的 accessToken(refresh_token 同时轮换,一次性使用)关键安全机制:
- Refresh Token 一次性使用:每次刷新后旧 token 立即失效,防止重放攻击
- Token 以 SHA-256 哈希形式存储在数据库,原始 token 不落库
- 登录失败 5 次后账号锁定 15 分钟
- 修改密码/登出后,该用户所有 session 立即失效
3. 请求头规范
3.1 必须携带的请求头
| Header | 适用场景 | 说明 |
|---|---|---|
Content-Type: application/json | 所有 POST/PUT/PATCH 请求 | 请求体为 JSON 时必须 |
Authorization: Bearer <token> | 所有受保护端点 | 或通过 Cookie 传递 |
3.2 可选但推荐的请求头
| Header | 类型 | 说明 |
|---|---|---|
x-device-id | string | 设备唯一标识(UUID),用于设备追踪和封禁管理 |
x-device-platform | string | 设备平台,如 web / ios / android。对 /api/telemetry 端点为必填(缺失或为 unknown 即 400 MISSING_OS),其余端点缺省按 unknown 处理 |
注意: 携带
x-device-id的请求,若对应设备被管理员封禁,将直接返回 403。
遥测端点的硬识别要求(自 v1.1.2-beta.4 / m038 起):
/api/telemetry对四要素做上线即生效、不受 strict-mode 开关控制的必填校验——平台 / 版本走 header(x-device-platform、x-app-version),时区 / 型号走 payload(device.timezone、device.model)。详见 §11 遥测载体契约。
3.3 服务端响应头
服务端会在响应中自动追加以下限流信息头:
| Header | 说明 |
|---|---|
ratelimit-limit | 当前窗口最大请求数 |
ratelimit-remaining | 当前窗口剩余可用次数 |
ratelimit-reset | 窗口重置时间(Unix 时间戳) |
retry-after | 当前限流窗口总秒数(仅 429 响应) |
4. 统一响应结构
4.1 成功响应
{
"success": true,
"data": <T>
}
/health*健康检查接口不使用统一success/data包装:/health、/health/database、/health/metrics直接返回各自的健康数据;/health/live和/health/ready可能只返回 HTTP 状态码。
4.2 分页响应
{
"success": true,
"data": {
"data": [ ... ],
"total": 150,
"page": 1,
"perPage": 20,
"totalPages": 8
}
}4.3 错误响应
{
"success": false,
"code": "AUTH_UNAUTHORIZED",
"message": "令牌无效或已过期",
"traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}当前中间件返回的
CLIENT_BANNED和MAINTENANCE错误体不包含success: false,结构为{"code":"...","message":"...","traceId":"..."}。客户端应优先按 HTTP 状态码和code判断错误类型。
5xx 错误的
message统一返回"服务器内部错误",具体原因仅记录在服务端日志中。
5. 错误码规范
5.1 HTTP 状态码与错误码对照
| HTTP 状态 | 错误码 | 触发场景 |
|---|---|---|
| 400 | INVALID_REQUEST_BODY | JSON 解析失败或 Content-Type 缺失 |
| 400 | VALIDATION_ERROR | 字段校验失败(来自 Store 层) |
| 400 | MISSING_OS | /api/telemetry 缺少 x-device-platform 头或其值为 unknown(自 m038 起硬校验) |
| 400 | MISSING_APP_VERSION | /api/telemetry 缺少 x-app-version 头(自 m038 起硬校验) |
| 400 | MISSING_TIMEZONE | /api/telemetry payload 缺少 device.timezone(自 m038 起硬校验) |
| 400 | MISSING_DEVICE_MODEL | /api/telemetry payload 缺少 device.model(自 m038 起新增必填) |
| 403 | DEVICE_NOT_REGISTERED | /api/telemetry 设备未注册(需先正常登录使用后再上报) |
| 403 | DEVICE_OWNERSHIP_MISMATCH | /api/telemetry 设备归属与当前账号不符 |
| 400 | 业务错误码 | 各模块自定义前缀码,如 AUTH_* / LEARNING_* / WORDS_* / WORDBOOK_* / BATCH_TOO_LARGE 等 |
| 401 | AUTH_UNAUTHORIZED | 未携带 token 或 token 失效 |
| 403 | FORBIDDEN | 无权限、账号被封禁、注册关闭或用户数量达到上限 |
| 403 | CLIENT_BANNED | 设备被封禁(由中间件拦截) |
| 404 | NOT_FOUND | 资源不存在 |
| 409 | CONFLICT | 资源冲突(如邮箱已注册) |
| 413 | PAYLOAD_TOO_LARGE | 请求体超过 2 MiB |
| 429 | RATE_LIMITED | 触发通用限流或账号因登录失败锁定 |
| 429 | AUTH_RATE_LIMITED | 触发认证端点限流 |
| 500 | INTERNAL_ERROR | 服务端内部错误(message 统一脱敏为 服务器内部错误) |
| 503 | MAINTENANCE | 服务器维护中(响应体结构见 §9,客户端应按 HTTP 503 或 code=MAINTENANCE 识别) |
注意:账号被管理员封禁返回
403 FORBIDDEN;账号因连续登录失败 5 次被临时锁定则返回429 RATE_LIMITED,两者不同。
5.2 认证错误消息参考
| 消息 | 含义 |
|---|---|
"缺少认证令牌" | 未携带 Authorization 头或 token cookie |
"令牌无效或已过期" | JWT 签名错误或已超期 |
"令牌类型无效" | 使用了错误类型的 token(如用 refresh token 访问 API) |
"会话不存在或已过期" | Token 已被撤销或数据库中无对应记录 |
"用户已被封禁" | 账号被管理员封禁 |
"认证尝试次数过多,请稍后再试" | 认证接口触发限流 |
6. 限流规则
6.1 通用限流(所有 /api/* 端点)
| 参数 | 默认值 | 说明 |
|---|---|---|
| 时间窗口 | 900 秒(15 分钟) | 固定窗口(窗口到期后计数归零) |
| 最大请求数 | 500 次 | 每 IP 每窗口 |
| 超出后 HTTP 状态 | 429 | |
| 超出后错误码 | RATE_LIMITED |
6.2 认证端点限流(更严格)
适用端点:/api/auth/register、/api/auth/login、/api/auth/refresh、/api/auth/forgot-password、/api/auth/reset-password、/api/auth/verify-reset-token
| 参数 | 默认值 |
|---|---|
| 时间窗口 | 60 秒 |
| 最大请求数 | 10 次 |
| 超出后错误码 | AUTH_RATE_LIMITED |
6.3 IP 识别规则
- 服务器配置
TRUST_PROXY=true时,优先读取x-forwarded-for(取第一个 IP),其次读取x-real-ip - IPv6 地址聚合至
/64前缀参与限流计数
7. 分页约定
所有列表接口统一使用如下查询参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
page | number | 1 | 页码,从 1 开始 |
perPage | number | 20(/api/v1 记录类为 50) | 每页条数,最大 100 |
分页响应结构见第 4.2 节。响应中的 page 为请求传入的页码,可能大于 totalPages。
8. 批量操作约定
- 批量接口(
/batch、/batch-get等)的数组长度上限由服务器max_batch_size配置决定(默认 500) - 批量操作部分失败时:HTTP 200,响应体包含
errors数组指出失败条目 - 批量操作全部成功时:创建类返回 201,查询/更新类返回 200
9. 维护模式
服务器维护期间,除以下端点外均返回 503 MAINTENANCE:
/api/admin/*/api/status/api/realtime/*/api/telemetry
响应体:
{
"code": "MAINTENANCE",
"message": "服务器维护中,请稍后重试",
"traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}维护响应不包含
success字段。traceId由请求 ID 中间件自动注入。客户端判断时应优先识别 HTTP 状态码 503 或code字段。
10. 数据类型约定
| 类型 | 格式 |
|---|---|
| ID | UUID v4 字符串,如 "f47ac10b-58cc-4372-a567-0e02b2c3d479" |
| 时间戳 | ISO 8601 UTC,如 "2024-01-15T08:30:00Z" |
| 浮点数 | IEEE 754 f64 |
| 概率/比率 | 0.0 ~ 1.0 的 f64 |
| 单词学习状态枚举 | "new" / "learning" / "reviewing" / "mastered" / "forgotten"(lowercase;v0.6.0-beta.4 P3#7 起;WordLearningState.state 字段)注意: WordMasteryDecision.masteryLevel 是不同枚举,仍为 "NEW"/"LEARNING" 等 SCREAMING_SNAKE_CASE |
| 会话状态枚举 | "active" / "completed" / "abandoned" |
| 单词本类型枚举 | "system" / "user"(字段名为 type) |
| 学习记录类型枚举 | "learning" / "review" / "all"(字段名为 recordType,写入端可选;缺省落库 "all") |
| 统计分类查询 | ?category=learning|review|all,缺省或 all 等同不过滤;用于 /api/records/statistics、/api/records/statistics/enhanced、/api/word-states/stats/overview |
11. 遥测载体契约(POST /api/telemetry)
自 v1.1.2-beta.4(迁移 m038) 起,遥测端点对设备四要素做上线即生效、不受
strict_mode开关控制的硬校验,缺任一即返回 4xx。客户端发版前务必补齐以下载体,否则上报全部被拦。
11.1 请求头(必填)
| Header | 校验 | 缺失/非法时 |
|---|---|---|
x-device-platform | 非空且 ≠ unknown(web / ios / android) | 400 MISSING_OS |
x-app-version | 非空(客户端版本号) | 400 MISSING_APP_VERSION |
x-device-id | 设备 UUID(用于归属核验) | 归属态判定见 §11.3 |
11.2 请求体 payload.device(必填字段)
| 字段 | 类型 | 校验 | 缺失/为空时 |
|---|---|---|---|
device.timezone | string | 非空(IANA 时区,如 Asia/Shanghai) | 400 MISSING_TIMEZONE |
device.model | string | 非空(设备型号;Web 端无真型号可落 browser on OS 派生标识或 web-admin 占位) | 400 MISSING_DEVICE_MODEL |
device.language | string | session_start 时受 strict-mode 软门控 | strict 开启时 MISSING_LANGUAGE |
device.screenWidth/screenHeight/pixelRatio/cpuCores | number | session_start 时受 strict-mode 软门控(均 > 0) | strict 开启时 MISSING_DEVICE_FINGERPRINT |
device.model为 m038 新增必填字段。device.timezone已由四要素硬校验接管,不再仅受 strict-mode 软门控(旧文档表述已过期)。
11.3 设备归属三态核验
四要素校验通过后,按 x-device-id 对应的设备 owner 做三态判定:
| owner 态 | 处理 |
|---|---|
| 设备未注册(无记录) | 403 DEVICE_NOT_REGISTERED |
| 已注册但归属为其他账号 | 403 DEVICE_OWNERSHIP_MISMATCH |
| 已注册且归属当前账号 | 放行 |
| 已注册但未认领(owner 为 NULL) | claim 放行(不误伤老匿名设备) |
11.4 心跳与遥测的协议依赖
POST /api/telemetry 除写入历史数据外,还充当设备心跳:每条通过归属核验的上报都会刷新该设备的心跳时间戳(不按 eventType 区分,且不受采样 / 限流影响——被采样丢弃的周期上报仍刷新心跳)。服务端 watchdog 每 5 秒扫描一次,对有活跃 SSE 连接的设备做心跳判定:
- 距上次心跳 > 10 秒计 1 次 miss;
- 连续 5 次 miss 即向该设备推送 SSE
data_corrupted事件(客户端应据此走重拉数据流程),随后 miss 计数归零; - 无活跃 SSE 连接的纯遥测设备不参与该判定,其心跳记录在 300 秒无新上报后被回收。
由此对客户端构成硬性约束:有活跃 SSE 连接期间,periodic 遥测必须以 ≤ 10 秒周期上报(建议 5–8 秒留网络抖动余量),否则累计 miss 会触发 data_corrupted。完整的心跳节奏、各 eventType 触发时机与 telemetry_request 即时上报约定见《客户端上传数据格式说明》§12.2。