后端工程(01):API 设计规范——版本管理、错误码、幂等、限流、OpenAPI
更新时间:2026-09-01。本文是
backend/engineering/后端工程第 01 篇,在 工程索引下。API 设计是后端工程的基本功。好的 API 设计让调用方清晰、可测试、可维护;坏的 API 设计会导致不断的兼容性问题、重复调用、一致性问题。
本文要回答的问题
- API 版本管理怎么做?什么时候需要版本升级?
- 统一错误码怎么设计?业务错误码和 HTTP 状态码怎么配合?
- 幂等设计怎么做?哪些 API 需要幂等?
- 限流设计怎么做?常见限流算法?
一、版本管理
版本策略
| 策略 | 例子 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径 | /v1/users, /v2/users | 简单,直观,缓存友好 | 版本扩散到 URL,每个版本都要维护 |
| 请求头 | Accept: application/vnd.myapp.v1+json | URL 干净 | 不直观,缓存难 |
| 查询参数 | /users?version=1 | 简单 | 和路由分离,容易写错 |
| 无版本 | 只改代码,不做版本 | 维护简单 | 不兼容变更会影响调用方 |
推荐: URL 路径版本(/v1/),简单直观,调试方便,缓存友好。
什么时候需要版本升级?
- 不兼容变更:请求/响应格式变化、字段删除、语义变化
- 兼容变更:新增字段、新增 API → 不需要版本升级,调用方可以平滑升级
版本生命周期
开发 → 稳定 → 维护 → 弃用 → 下线规则:
- 弃用后至少保留 6 个月
- 提前通知调用方迁移
- 监控弃用版本调用量
二、统一错误码设计
错误码结构
json
// 响应格式
{
"code": "VALIDATION_ERROR", // 业务错误码
"message": "参数错误", // 可读错误信息
"details": { // 具体错误详情
"field": "email",
"reason": "格式不正确"
},
"requestId": "req-abc-123" // 请求 ID,用于排查
}错误码分类
| 分类 | 范围 | 说明 |
|---|---|---|
| 成功 | 0xxx | 0 表示成功 |
| 参数错误 | 1xxx | 参数校验失败、格式不对、缺少必填 |
| 业务错误 | 2xxx | 业务规则失败(余额不足、库存不够) |
| 权限错误 | 3xxx | 未认证、权限不足 |
| 资源错误 | 4xxx | 资源不存在、资源冲突 |
| 系统错误 | 5xxx | 服务内部错误、数据库错误 |
HTTP 状态码 vs 业务错误码
HTTP 状态码 = 通用状态
业务错误码 = 具体业务错误
// 例子:业务逻辑错误(余额不足)
{
"code": "INSUFFICIENT_BALANCE", // 业务错误码
"message": "余额不足"
}
// HTTP 状态码:200 OK (请求正常到达,业务处理完成)
// 不是 400 或 500
// 例子:参数校验错误
{
"code": "VALIDATION_ERROR",
"message": "参数不合法"
}
// HTTP 状态码:400 Bad Request规则:
- 2xx → 请求成功处理
- 4xx → 客户端错误(参数错、权限错、不存在)
- 5xx → 服务端错误(服务内部错)
- 业务错误(如余额不足)依然 200,用业务码区分
三、幂等设计
什么是幂等?
多次调用和一次调用结果相同,不会产生副作用。
哪些 API 需要幂等?
| API | 是否需要幂等 | 说明 |
|---|---|---|
| GET | 是 | 读取,天生幂等 |
| PUT | 是 | 完整替换,天生幂等 |
| DELETE | 是 | 删除,重复删除结果一样 |
| POST | 默认不幂等 | 创建订单,重复创建多个订单 → 需要幂等设计 |
| PATCH | 不一定 | 绝对修改幂等,增量修改不幂等 |
幂等实现方案
1. 幂等键(Idempotency-Key)
http
# 请求头
POST /orders
Idempotency-Key: uuid-xxxx-xxxx-xxxx服务端:
- 幂等键存在 → 返回之前的结果,不重复执行
- 幂等键不存在 → 执行,存储结果和键
- 幂等键过期后可以删除
适合:支付、创建订单等需要幂等的场景。
2. 乐观锁
sql
-- 更新订单状态,version 作为版本号
UPDATE orders
SET status = 'paid', version = version + 1
WHERE id = 123 AND version = 1;
-- 如果影响行数 0 → 说明已经被更新过 → 不更新适合:更新操作,基于版本号判断。
3. 唯一约束
sql
-- 业务唯一键(如 request_id)唯一约束
INSERT INTO orders (id, request_id, ...)
VALUES (..., 'uuid-xxxx', ...)
ON CONFLICT (request_id) DO NOTHING;适合:创建操作,数据库层面保证不重复。
四、限流设计
为什么需要限流?
- 防止恶意刷接口,保护服务稳定性
- 防止突发流量打垮服务
- 公平使用资源,付费用户更多配额
常见限流算法
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定窗口 | 每个窗口计数,超过限流 | 简单 | 临界问题,窗口交界处流量翻倍 |
| 滑动窗口 | 滑动统计,精确 | 精确 | 存储量大,复杂度高 |
| 令牌桶 | 按速率放令牌,桶存一定令牌 | 支持突发 | 需要存令牌 |
| 漏桶 | 按速率流出,溢出丢弃 | 平滑突发 | 不支持突发 |
推荐: 令牌桶(允许一定突发,平滑处理),单机用 golang.org/x/time/rate 或 guava RateLimiter。
限流维度
1. 按用户:每个用户 QPS 限制
2. 按 IP:每个 IP 限制
3. 按接口:特定接口限流
4. 全局限流:整个服务限流限流响应
json
// 限流后响应
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
{
"code": "RATE_LIMITED",
"message": "请求太多,请稍后再试",
"retryAfter": 60
}五、API 文档化:OpenAPI
yaml
# OpenAPI 3.0 规范
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{id}:
get:
summary: 获取用户信息
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
200:
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string工具:
- FastAPI:自动生成 OpenAPI
- SpringFox:Spring Boot 自动生成
- Swagger UI:可视化界面在线调试
- Redoc:美观的文档展示
六、最佳实践总结
| 实践 | 建议 |
|---|---|
| 版本管理 | URL 路径 /v1/,不兼容才升版本 |
| 错误码 | 业务码 + HTTP 状态码,统一结构 |
| 幂等 | POST 创建操作必须支持幂等键 |
| 限流 | 按用户/IP/接口限流,返回 429 + Retry-After |
| 文档 | 自动生成 OpenAPI,不要手写文档 |
| 分页 | 游标分页适合大数据,偏移量分页适合小数据 |
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 版本过多 | 维护多个版本,人力不够 | 及时下线旧版本 |
| HTTP 状态码乱用 | 业务错误返回 500,调用方难以区分 | 业务错误用业务码,HTTP 状态码保持 200 |
| 不做幂等 | 重复支付产生多个订单 | 幂等键 + 数据库唯一约束 |
| 限流算法选固定窗口 | 临界问题流量翻倍 | 用令牌桶更稳定 |
相关与延伸
下一篇:鉴权体系——Session/Cookie、JWT、OAuth2、OIDC、SSO;REST API 设计,见 REST API 设计。
一句话总结
API 设计规范:版本管理推荐 URL 路径 /v1/,不兼容变更才升级;统一错误结构 {code, message, details, requestId},HTTP 状态码对应通用状态,业务错误用业务码;创建操作必须幂等,幂等键 + 乐观锁 + 唯一约束三种方案;限流常用令牌桶,按用户/IP/接口限流,超限返回 429 + Retry-After;文档自动生成 OpenAPI,不需要手写。