3. HTTP协议与企业级API设计原则

企业级后端系统的对外能力,最终几乎都收敛为一组 HTTP API。理解 HTTP 的请求响应模型,不是为了背诵报文格式,而是为了在设计接口时知道“哪些语义已经由协议规定、哪些必须由业务契约补齐”。一次请求由方法、URL、请求头、可选请求体组成;一次响应由状态码、响应头、可选响应体组成。方法表达动作意图,URL 定位资源,头部承载元数据与协商信息,状态码表达处理结果类别,响应体承载资源表示。RESTful 风格的核心,就是让这五者各司其职,而不是把所有信息都塞进 URL 或统一返回 200。

先看状态码的工程含义。2xx 表示成功:200 用于查询或更新后返回表示,201 用于创建成功并通常配合 Location 头,204 用于删除或更新成功且无需返回体。4xx 表示客户端问题:400 参数格式错误,401 未认证,403 已认证但无权限,404 资源不存在,409 冲突(如重复创建、版本冲突),422 语义校验失败,429 触发限流。5xx 表示服务端问题:500 未预期异常,503 依赖不可用。企业级 API 应避免“HTTP 200 + 业务错误码”的万能返回,因为网关、监控、重试策略、客户端 SDK 都依赖状态码做第一层判断。

头部与内容协商决定“同一资源如何以不同表示交付”。Content-Type 声明请求体或响应体的媒体类型,Accept 表达客户端期望的表示,Content-Language、Accept-Language 处理多语言,ETag 与 If-None-Match 支持缓存与并发控制,Idempotency-Key 用于支付、下单等需要幂等保障的写操作。内容协商不是可选项:当客户端发送 Accept: application/json 时,服务端不应返回 HTML 错误页;当请求体是 application/json 时,服务端应拒绝解析表单编码。

RESTful 资源命名应遵循“名词复数、层级表达从属、避免动词”的原则。例如订单资源用 /api/v1/orders,单个订单用 /api/v1/orders/{order_id},订单项用 /api/v1/orders/{order_id}/items。动作若无法用标准方法表达,可用子资源或受控动作端点,如 POST /api/v1/orders/{order_id}/cancel,但应克制使用。版本控制建议放在 URL 路径中(/api/v1/),因为它在网关路由、日志、缓存和客户端升级中最直观;也可用 Accept 头做媒体类型版本,但调试成本更高。企业项目通常选择路径版本 + 兼容性策略:新增字段不破坏旧客户端,删除字段需走弃用周期。

幂等性是订单类 API 的生命线。GET、PUT、DELETE 按 HTTP 语义应幂等,POST 默认不幂等。下单接口若因网络超时被客户端重试,可能产生两笔订单。解决方案是客户端生成唯一 Idempotency-Key,服务端在 Redis 或数据库中记录该键与首次处理结果,重复请求直接返回首次结果。PUT 更新订单地址时,天然幂等:同一请求执行多次结果一致。DELETE 删除订单也应幂等:首次返回 204,再次返回 404 或 204 均可,但契约必须明确。

下面用一张契约关系图,把订单管理 API 的资源、方法、状态码、头部与幂等策略放在同一张图上,便于对照设计。

订单管理 API 契约关系图(/api/v1) 客户端 Accept: application/json Idempotency-Key API 网关 路由 /api/v1/* 鉴权 · 限流 · 日志 订单服务 资源:orders / items 幂等键存储 · 版本校验 存储 orders 表 idempotency 表 资源与方法契约 /orders GET 列表 · POST 创建 201 + Location /orders/{id} GET · PUT · DELETE 200 / 204 / 404 /orders/{id}/items GET · POST 子资源从属 /orders/{id}/cancel POST 受控动作 409 状态冲突 状态码语义 200 查询/更新成功返回表示 201 创建成功 + Location 400 参数格式错误 · 422 语义校验失败 401 未认证 · 403 无权限 · 404 不存在 409 冲突 · 429 限流 · 500 服务端异常 头部与幂等策略 Content-Type / Accept 内容协商 ETag + If-None-Match 并发控制 Idempotency-Key 下单去重 PUT 天然幂等 · DELETE 幂等 重复键返回首次处理结果

把上述契约落到订单管理 API 上,可以形成一份可直接评审的接口清单。以下示例参数均为教学假设,用于说明字段与行为,不代表真实业务数据。

方法与路径语义成功状态码关键头部幂等性
GET /api/v1/orders分页查询订单200Accept、ETag天然幂等
POST /api/v1/orders创建订单201 + LocationIdempotency-Key、Content-Type依赖幂等键
GET /api/v1/orders/{id}查询单个订单200ETag、If-None-Match天然幂等
PUT /api/v1/orders/{id}全量更新订单200 或 204If-Match、Content-Type天然幂等
DELETE /api/v1/orders/{id}删除订单204—天然幂等
POST /api/v1/orders/{id}/cancel取消订单200Idempotency-Key建议幂等

创建订单的请求体可设计为:{"customer_id":"C1001","items":[{"sku":"SKU-9","qty":2,"price":19.90}],"currency":"CNY"}。服务端校验通过后返回 201,响应头带 Location: /api/v1/orders/ORD-20240501-001,响应体返回订单号、状态 CREATED、总金额与创建时间。若同一 Idempotency-Key 再次到达,服务端不再创建新订单,而是返回首次的 201 结果或 200 结果,并在响应头标注 Idempotent-Replayed: true,便于客户端识别。

版本控制方面,/api/v1/ 一旦发布,字段只增不减;若必须删除字段,先标记 deprecated,在响应头加 Deprecation 与 Sunset,给客户端至少一个迭代周期的迁移时间。内容协商失败时,服务端应返回 406 而非 500;请求体媒体类型不支持时返回 415。这些细节决定了 API 在网关、监控、SDK 和多方联调中的可预测性。

设计评审时,先问三个问题:资源路径是否名词化?状态码是否表达真实结果?写操作是否有幂等键或天然幂等语义?三问通过,订单 API 的契约基本可用。

最后强调,RESTful 不是教条。订单取消、支付回调、批量导入等场景,允许使用受控动作端点或异步任务端点,但必须在契约中明确方法、状态码、幂等键和错误结构。企业级 API 的价值不在于“像 REST”,而在于让调用方在超时、重试、并发、版本升级时都有确定行为可依赖。