API 设计不是把接口定义出来就完事。一个电商平台曾因为接口混乱,前后端集成拖了六个月;按统一规范重构后,集成周期压到一个月。差距不在代码量,在资源建模和方法语义这些基本功。

资源建模:四类资源定端点
REST 的核心是资源。建模时先把资源归入四类:文档(单一对象)、集合(对象列表)、仓库(客户端管理的资源库)、控制器(执行动作的端点)。类别定对了,标准端点自然推导出来——集合资源对应 GET/POST 列表加 GET/PUT/PATCH/DELETE 单条,文档资源只保留后三个。
电商域的典型拆分:product、order 是集合资源,user 是文档资源,关系各自挂清楚(order 挂 user 和 order_item,post 挂 author 和 comment)。命名全部小写、不用空格,同类关系用同一套命名。
层级嵌套要克制,三层封顶。/users/{id}/orders/{oid}/items/{iid} 已经是极限,再往下嵌套查询效率和可维护性都会崩。关系设计还要倒推数据库查询路径——模型上好看、查询上灾难的关系,上线第一周就会暴露。

方法语义:幂等性是最容易用错的一格
| 方法 | 语义 | 幂等 | 安全 | 典型状态码 |
|---|---|---|---|---|
| GET | 检索资源 | 是 | 是 | 200、404 |
| POST | 创建或触发操作 | 否 | 否 | 201、400、409 |
| PUT | 全量替换 | 是 | 否 | 200、201、204 |
| PATCH | 部分更新 | 否 | 否 | 200、204、400 |
| DELETE | 删除资源 | 是 | 否 | 204、404 |
两张牌最容易打错。一是 PUT 和 PATCH 的分界:PUT 要求客户端提交完整资源做替换,只改一个字段却走 PUT,等于把并发写入的覆盖窗口拉到最大——正确姿势是部分字段用 PATCH,需要防并发覆盖时再叠加 ETag/If-Match 条件请求,服务端版本不匹配就回 412。二是 409 和 400 的分界:请求体格式不对用 400,业务状态冲突(用户名已存在)用 409,前端靠这个区分「改改参数能过」还是「得换数据」。
状态码之外,错误响应体保持统一结构(error code、message、documentation 三件套),调用方才好写统一的异常分支。
版本策略:路径版本加下线公告
URL 里带 /api/v1/ 是成本最低、最直观的版本方案,配合蓝图注册实现多版本并存:
app.register_blueprint(users_bp, url_prefix='/api/v1/users')
增量素材之外补一个实战细节:版本下线要有仪式感。RFC 8594 定义了 Sunset 响应头,在旧版本响应里带上 Sunset: <HTTP-date> 明示停止服务时间,再配合文档公告和一段时间内的 410 响应,客户端迁移才有抓手。直接砍旧版本,受伤最重的是那些没能力快速跟进的大客户。
认证与限流:两段可复用的实现
JWT 认证的骨架由 flask-jwt-expanded 承担,配置过期时间、注册蓝图、装饰器保护路由,半小时能落地。真正值得细看的是限流,一个轻量实现:
class RateLimiter:
def __init__(self, max_requests: int = 100, window: int = 3600):
self.max_requests = max_requests
self.window = window
self.requests = defaultdict(list)
def is_rate_limited(self, identifier: str) -> bool:
now = time.time()
self.requests[identifier] = [
t for t in self.requests[identifier] if now - t < self.window
]
if len(self.requests[identifier]) >= self.max_requests:
return True
self.requests[identifier].append(now)
return False
def get_reset_time(self, identifier: str) -> int:
if not self.requests[identifier]:
return int(time.time())
return int(min(self.requests[identifier]) + self.window)
超限时返回 429,响应体带 retry_after,并在响应头补 X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset 三件套——客户端能不能优雅退避,全看这几个头给得全不全。单机内存版够小项目用,多实例部署务必换成 Redis 计数,否则每台机器各算各的,限流形同虚设。
限流自检一条命令就够:curl -I 连打几次目标接口,盯响应头里 Remaining 的递减和超限后的 429 加 Retry-After。限流没配响应头,等于没配完。
网关与排错
微服务规模上来后,网关统一收口路由、认证和限流:按路径前缀匹配目标服务,转发时剔除 Hop-by-hop 头,后端不可用回 503 并给出明确错误码。网关层限流按服务粒度设阈值,计数器放 Redis 做滑动窗口。

排查接口问题的固定清单:先看状态码归类(4xx 是调用方问题、5xx 是服务方问题),再核对请求头(认证、Content-Type 最常出岔),然后复现最小请求隔离变量,最后查服务端日志的时间窗对齐——时钟漂移会让日志看起来「没有那次请求」。把这份清单固化进团队 wiki,新同事排错速度至少快一半。
接口质量的复检建议用 OpenAPI 文档驱动:schema 与实现不一致的接口,在 CI 里直接判失败。文档不是给人看的装饰,是可执行的契约。

评论0