做后端开发这些年,几乎每个项目都要和 RESTful API 打交道。早期那会儿我设计接口基本靠感觉,同一个系统里既有 /users/getUserInfo,又有 /user/detail,还有 /users/123,前端同学每天都在猜后端到底给了什么。后来我花了不少时间把接口规范沉淀下来,再结合 Python 生态里常用的工具,总算让前后端协作顺畅了很多。这篇文章就把我整理出的 RESTful API 设计最佳实践完整写出来,重点放在 Python 项目里能直接落地的部分,包括资源设计、状态码与异常、框架选型、认证安全、版本管理、文档测试和性能优化。
写这份内容的时候,我默认你已经装好了 Python 3.10 以上的环境,并且会用 venv 或 uv 管理依赖。如果你还在纠结 Python 怎么装、VSCode 或者 PyCharm 怎么配,建议先花半天时间把基础环境理顺,再回来看接口设计,否则代码示例跑不起来,后面全是纸上谈兵。
1. 先把接口当成“资源”来设计,而不是一堆函数
1.1 为什么“资源思维”是 REST 的起点
很多人对 REST 的理解停留在“URL 短一点、用 HTTP 方法区分动作”,但真正让接口稳定的核心其实是“资源”这个概念。REST 强调把系统中的一切抽象为资源,比如用户、订单、文章、评论,每个资源有唯一的 URI,客户端通过 HTTP 方法对这些资源做操作,而不是直接调用某个函数。
我举个例子:传统接口可能长这样 POST /getUserById,函数味儿很浓。换成资源思维之后就是 GET /users/123。前者描述的是“我要执行一个获取用户的方法”,后者描述的是“我要获取用户这个资源中标识为 123 的那一个”。看起来只是风格差异,但资源思维会逼着你去思考数据的归属、粒度和层级。
这种思维最直接的好处是接口数量和复杂度会降下来。只要资源模型设计得对,你会发现大部分增删改查都能用固定的几个模式覆盖掉,而不是为每个页面单独写一个自定义接口。后端的维护成本、前端的对接成本,都会明显降低。
1.2 资源命名:名词复数、用 HTTP 方法表达动作
在设计 URI 时,我建议统一使用名词复数,比如 /users、/orders、/articles,而不是 /user、/getOrder、/articleList。原因很简单:资源是一类对象的集合,复数更符合集合语义。当你看到 GET /users 时,能直观理解为“获取用户列表”;看到 GET /users/1 时,能理解是“获取用户 1”。
动作交给 HTTP 方法来表达:
- GET:查询资源,不改变状态
- POST:创建资源,或执行一些需要发送数据的复杂操作
- PUT:整体替换资源
- PATCH:局部更新资源
- DELETE:删除资源
我经常遇到的一个争议是“登录用 POST 还是 GET”,答案一定是 POST,因为登录会创建会话资源,而且请求体里有敏感信息。另一个常见场景是“搜索接口要不要用 GET”,如果只是查询条件,用 GET 没问题,参数放 query string;如果有复杂的过滤条件,可以考虑 POST 加特殊 action,但要谨慎,不要把所有接口都变成 POST。
在命名风格上,URL 路径建议用 kebab-case(/user-profiles),JSON 字段建议用 snake_case(user_name),因为 Python 后端几乎都遵守 snake_case,前端如果要转 camelCase 可以在展示层转换。我不推荐在 URL 里出现大写字母,因为大小写在部分服务器和网关里是敏感的,容易造成资源定位不一致。
1.3 子资源与嵌套深度:别把 URL 写成森林
资源之间会有从属关系,比如“用户的订单”“订单的商品”。这种关系可以映射为嵌套 URI,例如 GET /users/123/orders,表示获取用户 123 的订单列表;GET /orders/456/items,表示获取订单 456 的明细项。
嵌套层级我建议最多两层,超过两层就考虑拆开或打平。比如 GET /users/123/orders/456/items/789 这种三层嵌套,看起来精确,但实际使用中又长又难维护,而且客户端经常需要同时知道用户 ID、订单 ID、商品 ID 三个参数才算得出来。更合理的做法是给 item 一个全局唯一 ID,用 GET /items/789 直接定位,或者用 GET /orders/456/items 拿到明细后自己处理。
还有一类东西不适合占一个资源层级,比如“某个操作触发后的临时结果”。拿“导出报表”举例,你当然可以设计成 POST /reports/export,但更好的做法是创建任务资源:POST /export-jobs 创建一个导出任务,GET /export-jobs/123 查询任务状态,GET /export-jobs/123/download 下载结果。这样既符合资源语义,又能支持异步处理,代码结构也清晰。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. HTTP状态码与错误处理:把“发生了什么”告诉调用方
2.1 常用状态码怎么选
处理状态码是我见过分歧最大的地方。有些团队喜欢一股脑返回 200,然后靠响应体里的 code 字段判断成败;有些团队则严格使用状态码。我这里的建议是:以 HTTP 状态码作为第一层判断,响应体里的业务码作为第二层细化。
基础选择逻辑其实不难:
- 200 OK:查询成功、更新成功
- 201 Created:资源创建成功,通常配合
Location响应头返回新资源的 URI - 204 No Content:删除成功或没有返回体但结果成功的操作
- 400 Bad Request:请求语法错误,比如 JSON 解析失败
- 401 Unauthorized:未认证,不关心你是谁
- 403 Forbidden:已认证但没权限,或者 IP 被限制
- 404 Not Found:资源不存在,或者接口路径不存在
- 405 Method Not Allowed:接口路径对了但方法不对,比如只支持 GET 的接口被 POST 了
- 409 Conflict:资源当前状态和请求冲突,比如重复创建、唯一键冲突
- 422 Unprocessable Entity:请求体格式正确但语义校验失败,比如用户名为空、邮箱格式不对
- 429 Too Many Requests:触发限流
- 500 Internal Server Error:服务端未处理的异常
- 503 Service Unavailable:服务暂时不可用,比如依赖数据库挂了
我踩过的一个坑是 400 和 422 混用。最开始的接口只要校验失败就返回 400,搞得前端分不清是“参数根本缺了”还是“参数类型不对”。后来统一成:JSON 解析失败、缺少必要字段这类结构性问题用 400,字段格式和业务规则不满足用 422。这样前端拿到 400 就知道是请求本身构造错了,拿到 422 就知道是用户输入的内容需要提示。
2.2 统一错误响应体
只给状态码不够,客户端还需要知道具体哪里错了。我建议所有错误响应使用统一结构,字段固定:
json复制{
"error": {
"code": "USER_NOT_FOUND",
"message": "User with id 123 does not exist.",
"details": [
{
"field": "email",
"message": "invalid email format"
}
],
"request_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
}
code 是给程序判断用的机器码,message 是给人看的总结,details 是字段级别的具体错误,request_id 是日志追踪 ID。request_id 特别重要,生产环境下出现一个问题,如果客户端能把这个 ID 带回给后端,我们查日志会快很多。在 FastAPI 里我一般用中间件给每个请求生成一个 UUID,同时注入到响应头和错误体中。
关于异常处理,我建议不要在业务代码里到处 try-except 后手动构造错误响应。更优雅的做法是定义异常类型,然后在全局异常处理器里统一转换成响应。这样业务代码读起来很干净,错误格式也保持一致。
python复制# exceptions.py
class BizError(Exception):
def __init__(self, code: str, message: str, status_code: int = 400, details: list | None = None):
self.code = code
self.message = message
self.status_code = status_code
self.details = details or []
python复制# main.py 中的全局异常处理
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(BizError)
async def biz_error_handler(request: Request, exc: BizError):
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"details": exc.details,
"request_id": getattr(request.state, "request_id", None),
}
},
)
这个模式在 Flask、Django 里也一样,只是注册异常的方式不同。关键是:业务代码里 raise BizError("ORDER_NOT_PAYED", "order is not payed", status_code=409),然后全局处理器负责格式化。
2.3 参数校验与 422
Python 后端做参数校验,我首推 Pydantic。FastAPI 天然集成 Pydantic,Django 可以用 DRF 的 Serializer,Flask 可以单独引入 Pydantic 或 marshmallow。校验结果如果失败,正确的状态码是 422,并且把每个字段的错误信息放进 details。
Pydantic 的好处是既能做运行时校验,又能生成 OpenAPI 文档。比如一个创建用户的请求体:
python复制from pydantic import BaseModel, EmailStr, Field
class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: EmailStr
age: int = Field(..., ge=0, le=120)
这样写完之后,FastAPI 会自动对请求体做校验,错误就是结构化 JSON。你不需要自己去写一堆 if not name: raise ...。我在实际项目里会尽量把校验规则放在 schema 层,业务层只关注核心逻辑,这样代码量会大幅下降,而且接口契约一目了然。
另外要注意:对外文档不要暴露数据库模型。很多人图省事直接用 ORM 模型当响应模型,结果把 password_hash、is_admin 这类字段全返回出去了。正确做法是单独定义 response schema,用 model_config = {"from_attributes": True} 做转换,做到“内部模型”和“对外契约”完全隔离。
3. Python 框架选型与项目目录落地
3.1 FastAPI、Flask、Django REST Framework 怎么选
聊 Python 的 RESTful API,一定绕不开框架选择。我的观点是:没有最好的框架,只有最合适的场景。下面这张表是我自己判断时的依据:
| 框架 | 性能 | 开发效率 | 类型提示支持 | 自带功能 | 适合场景 |
|---|---|---|---|---|---|
| FastAPI | 高(异步支持) | 高 | 优秀 | 较少,基于 Starlette | 新项目、微服务、前后端分离 |
| Flask | 中 | 中 | 一般 | 极少,靠扩展 | 小型服务、已有 Flask 项目 |
| Django REST Framework | 中 | 高 | 一般 | 完整,自带 Admin、ORM、认证等 | 数据模型复杂、重后台产品 |
如果让我给新项目提建议,我大概率会推荐 FastAPI。原因有三个:第一,原生 asyncio 支持,在高并发场景下明显占优;第二,类型提示写完后,Swagger 文档自动生成,再也不用熬夜补接口文档;第三,依赖注入和 Pydantic 的组合让代码测试起来特别舒服。
但如果你所在团队已经重度使用 Django,那也没必要强行换 FastAPI。Django REST Framework 的 Serializer、ViewSet、Router 体系非常成熟,尤其是后台管理配合 Admin 非常顺手。Flask 则更适合极简场景,比如做一个几十行的回调服务,用 Flask 一个文件就能搞定。
3.2 一个可落地的 FastAPI 项目结构
项目结构我建议按模块划分,而不是按“controller/service/dao”三层硬切。接口往往围绕业务资源聚合,按业务模块切会让可维护性高很多。
text复制my_api/
├── app/
│ ├── main.py # 应用入口,注册路由、中间件、异常处理
│ ├── config.py # 配置类,读取环境变量
│ ├── api/
│ │ ├── __init__.py
│ │ └── v1/
│ │ ├── router.py
│ │ ├── users.py
│ │ └── orders.py
│ ├── models/ # ORM 模型
│ ├── schemas/ # Pydantic 请求/响应模型
│ ├── services/ # 业务逻辑层
│ ├── core/
│ │ ├── security.py # JWT、密码哈希等
│ │ └── deps.py # 通用依赖
│ └── common/
│ ├── exceptions.py
│ └── response.py
├── tests/
├── requirements.txt
└── pyproject.toml
注意 api/v1 这一层,直接在 URL 前缀里带上版本号。比如所有路由都以 /api/v1 开头。这样以后出了不兼容的接口,可以新增 v2 目录,老客户端继续走 v1,不用一起发布。
路由注册时,我习惯在 router.py 里统一 include:
python复制# app/api/v1/router.py
from fastapi import APIRouter
from app.api.v1 import users, orders
api_router = APIRouter()
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(orders.router, prefix="/orders", tags=["orders"])
然后在 main.py 中挂载:
python复制from fastapi import FastAPI
from app.api.v1.router import api_router
app = FastAPI(title="My API", version="1.0.0")
app.include_router(api_router, prefix="/api/v1")
这样最终的接口路径就是 /api/v1/users、/api/v1/orders。我从实践中发现,加上 /api 前缀能很好地区分静态资源和代理规则,也可以避免和前端路由冲突。
3.3 中间件与依赖注入的实践
中间件适合处理横切关注点,包括请求日志、CORS、请求 ID 注入。依赖注入则适合做认证、数据库会话管理和通用参数提取。
在 FastAPI 里通过 Depends 可以优雅地复用逻辑。比如所有需要登录的接口都要求当前用户:
python复制# app/core/deps.py
from fastapi import Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
bearer_scheme = HTTPBearer()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Security(bearer_scheme)):
token = credentials.credentials
# 解析 token,取出 user_id,加载用户
user = await authenticate_token(token)
if user is None:
raise HTTPException(status_code=401, detail="Invalid token")
return user
业务接口只要写上 user: User = Depends(get_current_user),就能把当前用户作为参数拿进来,非常干净。依赖注入的另外一个用法是数据库会话管理:用 Depends(get_db) 生成 session,接口结束后自动关闭,避免连接泄漏。
中间件我一般会写一个请求日志中间件,把 method、path、耗时、状态码、request_id 都记录下来。生产环境排查问题时,没有日志几乎等于瞎眼。这个中间件实现起来不复杂,但收益极高,强烈建议每个项目都加。
4. 认证与安全:API 不只是业务逻辑
4.1 Token 认证还是 Session 认证
很多接口在还没上线前就有安全隐患。你需要先决定认证方案。这里我不展开写 OAuth2 的完整细节,只说说最常用的两类。
Session 认证适合传统的服务端渲染项目,服务端存 session,客户端用 cookie 自动携带。问题在于水平扩展时要把 session 共享,否则用户请求打到另一台机器就被认为未登录了。Token 认证(尤其是 JWT)适合前后端分离和移动端接口,服务端不保存会话状态,靠签名验证,天然适合横向扩展。
JWT 的缺点也有:token 一旦签发,在过期时间内无法主动失效。如果你的产品需要“踢人下线”或者“修改密码后立刻失效”,JWT 需要引入 token 黑名单或缩短过期时间,复杂度会上升。所以没有绝对好坏,看业务场景。
对大多数 Python API 项目,我建议先用简单的 Bearer Token,配合 HTTPOnly Cookie 存放 token 可以降低 XSS 风险。笔者这里说的是“Access Token 短期 + Refresh Token 长期”的组合,刷新端点负责更换 token,这样即使 access token 泄漏,危害也能限制在几分钟内。
4.2 在 FastAPI 里实现一套最小可用的 JWT
下面给一个我平时用来做原型的最小 JWT 实现。密码哈希用 passlib 的 bcrypt,token 用 python-jose 或 PyJWT。
python复制# app/core/security.py
from datetime import datetime, timedelta, timezone
import jwt
from passlib.context import CryptContext
SECRET_KEY = "change-me-please"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict, expires_minutes: int = ACCESS_TOKEN_EXPIRE_MINUTES) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=expires_minutes)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def decode_token(token: str) -> dict:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
登录接口大概是这样:
python复制# app/api/v1/auth.py
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = get_user_by_username(form_data.username)
if user is None or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(status_code=401, detail="Incorrect username or password")
token = create_access_token({"sub": str(user.id)})
return {"access_token": token, "token_type": "bearer"}
需要强调的一点:SECRET_KEY 绝对不能写死在代码里。我见过太多项目中把密钥提交到 Git 仓库,结果被爬虫扫出来。应该用环境变量或密钥管理服务来注入。另外 sub 字段我建议用用户唯一 ID,并且要转成字符串,因为 JWT 规范要求 sub 是字符串。
4.3 CORS、密码存储与限流
CORS 是浏览器安全机制,不是后端防攻击墙。很多开发者为了省事,直接用 allow_origins=["*"],这在不需要 cookie 的纯 token 场景下问题不大,但如果使用了 cookie 认证,就必须指定白名单。否则任何网站都能向你的接口发起请求,浏览器还会把带凭证的响应读走。
密码存储只提一条:不要用 MD5、不要用 SHA1。用加盐的慢哈希算法,比如 bcrypt、argon2。我用 bcrypt 是因为 passlib 兼容性好,但 argon2 更安全。生产环境请保证合理的 cost 参数,别设置太低。
限流是 API 安全的第三道防线。FastAPI 社区常用 slowapi,可以针对 IP、用户维度做限制。对于未认证的登录接口,尤其要限制失败次数,防止暴力破解。我见过最粗暴但仍有效的做法:同一个 IP 一分钟内超过 10 次登录失败就封一小时,还要在日志里告警。限流策略不需要一开始做得很精细,但基本的 每分钟多少次 一定要加上。
5. 版本管理、文档、测试与性能优化
5.1 版本策略:URL 前缀是最稳妥的方案
接口一定会变,变的时候怎么管理版本决定了你晚上能不能睡好觉。我见过三种主流方案:
- URL Path:
/api/v1/users - Query Parameter:
/api/users?version=1 - Custom Header:
X-API-Version: 1
我个人强烈推荐 URL Path。原因很实在:一眼可读、方便在代理层做路由、也方便分享给前端联调。Query 参数版本容易被忽略,Header 版本在调试工具里看不直观。唯一需要注意的是,不要在 v1 和 v2 之间反复横跳,版本语义应该是“不兼容变更才升级主版本号”,小改动直接向后兼容即可。
另外一个经验:不要在每个接口上都传版本号,而是整个模块统一版本。比如 v1 是完整的 /api/v1/*,v2 是完整的 /api/v2/*。这样路由清晰,代码也容易隔离。
5.2 自动生成文档:FastAPI 的 Swagger 是个大杀器
FastAPI 最吸引人的地方之一就是自动生成 OpenAPI 文档。默认访问 /docs 就是 Swagger UI,/redoc 是 ReDoc。我接手过的项目里,很多接口文档都是靠 Word 或者手写 Markdown,一旦代码改动,文档立刻过期。用 FastAPI 之后,文档和代码同步生成,及时性完全不一样。
我还会做两个优化。第一,在路由装饰器上写清楚注释和响应模型,这样文档里就有详细的字段说明:
python复制@router.get("/users/{user_id}", response_model=UserRead, summary="获取用户详情")
def get_user(user_id: int):
...
第二,对响应错误也补充 responses 配置,让调用方知道可能返回哪些状态码。这样生成的文档对前端同学友好得多,许多无谓的“这个接口会不会返回 404”的沟通都能省掉。
如果你在用 Flask,可以考虑接入 flasgger;Django 则用 drf-spectacular。核心思想一样:从代码生成文档,而不是维护一个独立文档站点。
5.3 用 pytest 给接口兜底
没有自动化测试的 API 服务,总有一天会让你在深夜被电话叫醒。我最低限度的要求是:所有核心业务接口至少有两个测试,一个测正常路径,一个测主要异常路径。
FastAPI 自带 TestClient,配合 pytest 用起来非常顺手:
python复制from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_get_user_success():
resp = client.get("/api/v1/users/1")
assert resp.status_code == 200
assert resp.json()["id"] == 1
def test_get_user_not_found():
resp = client.get("/api/v1/users/99999")
assert resp.status_code == 404
assert resp.json()["error"]["code"] == "USER_NOT_FOUND"
测试里要尽量避免依赖真实数据库。我的习惯是测试环境用 SQLite 或 PostgreSQL 的临时 schema,启动时迁移一下,测完就销毁。外部服务比如第三方 API、消息队列,尽量用 mock 或本地测试替身。这样测试跑得快,也不会因为外部网络波动挂掉。
除了接口级测试,还可以用 schemathesis 做基于 OpenAPI 的自动测试,它会根据 schema 生成随机请求,找出参数校验和数据处理上的漏洞。虽然不能完全替代手写测试,但能覆盖很多边界情况。
5.4 分页、过滤与异步优化
接口性能优化首先要解决“一次返回太多数据”的问题。最简单的分页是 offset/limit,适合数据量小、业务简单的场景。但数据量大了以后,深分页会有明显的性能问题,offset 越大,数据库扫描的成本越高。这时候用 cursor-based 分页更合适:请求参数带上 cursor,例如 GET /messages?cursor=20260601T120000Z&limit=20,返回结果里附带下一个 cursor。这种分页对实时变化的数据特别有效,能避免新增记录导致的前后页重复。
过滤条件我建议放在 query 参数里,比如 GET /orders?status=paid&start_date=2026-01-01&end_date=2026-06-01。字段增强如排序可以用 ordering=created_at,但要注意白名单校验,避免用户在排序字段里注入奇怪的东西。
Python 异步是另一个优化重点。FastAPI 天然支持异步路由,但如果你使用的是同步 def,FastAPI 会把它丢到线程池执行,高并发下性能会受限。对于 IO 密集型操作(查询数据库、调用外部 HTTP 服务),优先写成 async def,配合异步 ORM 或 httpx 异步客户端。如果是 CPU 密集型计算,异步帮不上忙,需要考虑缓存或 worker 扩展。
说到缓存,对于读多写少的接口(比如用户基本信息、商品详情),加一层 Redis 缓存,性价比极高。我最常做的方式是:先查缓存,缓存没有就查数据库,再把结果写入缓存。缓存更新时机可以配合写操作直接删除对应 key,比维护复杂过期策略简单得多。Redis 里注意设置 TTL,防止数据老化和冷数据堆积。
6. 实际项目中的踩坑记录与调整建议
6.1 时间字段的时区问题必须前置约定
时间格式踩坑的代价非常大。我早期设计的接口把时间字段返回成字符串,但有的用本地时间,有的用 UTC,前端显示的时候不统一,用户看到的时间差了好几个小时。后来定下规则:接口统一使用 ISO 8601 格式,且全链路统一使用 UTC 时间。存储层如果用的 PostgreSQL,timestamp with time zone 会自动处理;响应时可以用 Pydantic 的 datetime 类型转为 ISO 格式,客户端按自己的时区展示。这条规则一定要写在接口规范里,而不是等出问题再补。
6.2 集合接口不加分页引发的连锁故障
有一个项目上线初期数据量小,列表接口直接返回全量数据。后来用户量涨起来,单个列表请求要查一万条记录,数据库 CPU 直接拉满,接口响应从 50ms 涨到 8 秒,最后把服务打挂。修复方式很简单:所有列表接口默认分页。即使产品当时只显示 20 条,接口也要支持分页参数,否则后面重构成本极高。我建议响应体统一为 { "items": [...], "total": 100, "limit": 20, "offset": 0 } 或者 cursor 模式,这样前端才会养成处理分页的习惯。
6.3 接口变更如何平稳下线
接口删除是一件需要谨慎处理的事。我经历过的教训是:直接下线旧接口导致第三方系统半夜告警。后来我规定的流程是:先在文档中标记 deprecated,v1 保留至少三个月的过渡期;在接口层记录调用日志,统计低频调用方,主动联系确认;最后再在某个版本里移除。如果是内部系统,这个周期可以短一些,但一定不能默默删除。对于不兼容的字段变更,最好在一段时间内同时返回新旧字段,例如 name 改为 full_name,可以先两个都返回,再逐步下线旧的。
这些小问题并不复杂,但如果在项目初期就主动规避,能省掉大量后期擦屁股的时间。我希望你从第一个接口开始就把资源设计、错误格式、版本管理这些底子打好,后面再怎么加需求都不会怕。
最后再分享一个经验:接口规范不是一次性定死的东西,它需要跟着业务成长。每当你发现一个接口写起来特别别扭,或者前端老是问“这个字段什么意思”的时候,就应该停下来审视是不是规范需要优化了。把每次踩坑后的思考补进自己的最佳实践清单里,你的 API 会越来越顺手。
