首页 > 教程攻略 > ai教程 >传统 REST 接口设计:为什么它仍然是后端系统最稳的基本功

传统 REST 接口设计:为什么它仍然是后端系统最稳的基本功

来源:互联网 时间:2026-06-15 07:17:25

过去几年,GraphQL、gRPC、tRPC、BFF、AI Agent 接口一个个轮番登场,热闹得很。但真到了大多数业务系统里,REST 依然是那个最常见、最容易落地、协作成本最低的选择。

原因非常简单——REST 足够朴素。

它直接跑在 HTTP 上,不需要客户端和服务端去约定什么复杂的协议,也不用团队一上来就搭好整套 IDL、网关和代码生成体系。前端、移动端、后端、测试、运维,谁看谁懂。只要接口文档把 URL、Method、参数、返回值和状态码写清楚,大家基本就能愉快地合作了。

但也正因为 REST 看起来简单,很多项目最后会写成“披着 HTTP 外衣的随意 RPC”。所有接口都写成 POST /doSomething,状态码永远返回 200,错误都塞在 message 里,资源命名随心所欲,分页和过滤各搞一套。短期来看跑得挺顺,但时间一长,维护起来简直噩梦。

所以我们今天不聊课本那套,就说说传统业务系统里真正实用的 REST 设计习惯。

REST 的核心:围绕资源设计

REST 最核心的思想,不是用 JSON 传数据,也不是通过 HTTP 调接口,而是围绕资源来建模。资源可以是用户、订单、商品、文章、评论、文件、任务。设计接口时,URL 表达资源是什么,HTTP Method 表达对这个资源做什么动作。

比如订单资源:

GET /orders        查询订单列表
POST /orders       创建订单
GET /orders/1001   查询单个订单
PUT /orders/1001   整体更新订单
PATCH /orders/1001 部分更新订单
DELETE /orders/1001 删除订单

你看,看到接口就大概知道在操作什么资源、做什么动作,清晰明了。

相比之下,下面这种写法就很不 REST 了:

POST /getOrderList
POST /createOrder
POST /updateOrderStatus
POST /deleteOrder

不是说不能用,而是把动作都塞进 URL 里,HTTP Method 完全失去了语义。接口少的时候无所谓,系统一复杂,命名就乱成一锅粥了。

一个典型 REST 请求流程

image.png

一个合格的 REST 接口,绝不只是 Controller 里能返回数据那么简单。在请求流程的每一层,都要有清晰的职责边界:网关负责通用流量,Controller 处理协议和参数,Service 执行业务逻辑,Repository 做数据访问,DTO 负责对外表达。

HTTP Method 别乱用

传统 REST 里最容易混乱的,就是 Method。

常见约定如下:

Method含义是否应该幂等
GET查询资源
POST创建资源或提交动作
PUT整体替换资源
PATCH局部更新资源通常是
DELETE删除资源

幂等的意思很简单:同一个请求执行一次和执行多次,最终结果是一样的。

比如:

DELETE /orders/1001

执行一次是删除订单,再执行一次,仍然是“订单不存在”,最终状态没有变化,所以它是幂等的。

而:

POST /orders

每执行一次都可能创建一个新订单,所以不是幂等的。

实际业务里,有些动作很难完全归类为一个资源,比如支付订单、取消订单、提交审批。这时候就把动作建模成子资源或者业务操作:

POST /orders/1001/payment
POST /orders/1001/cancellation
POST /approval-tasks/2001/submission

千万别为了形式上的完美,硬把所有业务都拧成一个 PUT /orders/1001。工程设计既要讲语义,也要讲可读性。

状态码不要永远是 200

很多系统喜欢这么干:响应体里 code 是 500,message 写着 server error,但 HTTP 状态码却永远是 200 OK。这对调试、网关、监控、SDK、缓存都很不友好,简直就是给自己挖坑。更合理的做法是:让 HTTP 状态码表达协议层结果,让响应 body 表达业务细节。

常用状态码:

状态码含义
200请求成功
201创建成功
204成功但无响应体
400参数错误
401未登录或 token 无效
403已登录但无权限
404资源不存在
409资源冲突
422语义校验失败
429请求过多
500服务端异常

错误响应可以统一成这样:

{
    "error": {
        "code": "ORDER_STATUS_INVALID",
        "message": "当前订单状态不允许取消",
        "requestId": "req_01HZX8K7"
    }
}

code 给程序判断,message 给人看,requestId 给排障用。

查询、分页、排序,统一约定

列表接口是 REST 系统里最容易跑偏的地方。

建议大家统一使用 query string:

GET /orders?status=paid&createdAfter=2026-05-01&page=1&pageSize=20&sort=-createdAt

常见约定:

page / pageSize:传统分页
limit / offset:偏移分页
cursor / limit:游标分页
sort=-createdAt:按创建时间倒序
sort=createdAt:按创建时间正序

如果数据量大,或者列表会频繁新增,游标分页比 page 分页更稳:

GET /orders?cursor=eyJpZCI6MTAwMX0=&limit=20

返回:

{
    "items": [],
    "nextCursor": "eyJpZCI6MTAyMX0=",
    "hasMore": true
}

千万别让每个接口各自发明一套分页字段。统一约定能直接降低前后端沟通成本。

接口版本管理必须提早规划

一旦接口被客户端用上了,就不能随意改动了。

常见的版本方式有三种:

/api/v1/orders
Accept: application/vnd.company.v1+json
?apiVersion=1

业务系统最常用的是 URL 版本:

GET /api/v1/orders

它不一定最优雅,但最直观,网关、文档、测试都容易处理。

版本管理的重点不是路径怎么写,而是不要破坏已有客户端。新增字段通常是兼容的,删除字段、改变字段含义、改变枚举值、改变错误结构,都是高风险变更。

鉴权和权限,不是一回事

REST 接口里经常混淆两个概念:

Authentication:你是谁
Authorization:你能做什么

前者通常通过 token、session、API key、OAuth2 完成。后者要结合角色、资源归属、租户、数据权限去判断。

比如:

GET /orders/1001
Authorization: Bearer 

服务端不止要验证 token 是否有效,还要判断当前用户有没有权限访问这个订单。很多系统只做登录校验,不做资源级权限校验,结果只要用户猜到 ID,就能越权访问别人数据,这是重大安全漏洞。

REST 的优点和它的天花板

REST 的优点很明显:

  • 基于 HTTP,生态成熟;
  • 对人友好,调试方便;
  • 对缓存、袋里、网关支持好;
  • 前后端协作成本低;
  • 适合 CRUD 和大多数业务接口。

但它也有自己的天花板。

当客户端需要灵活选择字段、组合多个资源时,GraphQL 可能更合适。

当服务间追求高性能、强类型、低延迟通信时,gRPC 是更好的选择。

当系统内部动作强过程化,比如复杂工作流、批处理任务、命令调度时,RPC 风格也未必比 REST 差。

REST 不是银弹,它最适合稳定、清晰、资源导向的业务接口。

实践建议

  • URL 用名词,不要用动词。

    GET /users/1 而不是 GET /getUser
  • 状态码要有语义,不要全部返回 200。

  • 请求和响应 DTO 要稳定,不要直接暴露数据库实体。

  • 分页、排序、错误结构、时间格式要统一。

  • 敏感操作要考虑幂等。

    比如支付、退款、创建订单,可以使用 Idempotency-Key
  • POST /payments
    Idempotency-Key: 8f0b2b4a-7f2e-4d8a
  • 接口文档要和代码一起维护。

    OpenAPI / Swagger 不一定完美,但比口口相传可靠。

总结

传统 REST 之所以还在大量系统里大规模使用,不是因为它够新,而是因为它够稳。它把接口设计约束在一个非常简单的模型里:资源用 URL 表达,动作由 HTTP Method 表达,结果由状态码表达,细节由 JSON body 表达。

真正写好 REST,不靠什么复杂框架,而靠一致性。命名一致,状态码一致,错误结构一致,分页一致,权限判断一致,系统就会越来越好维护。

新技术当然值得学,但 REST 依然是后端工程师绕不开的基本功。很多系统的问题,不是 REST 过时了,而是从一开始,就没认真设计过 REST。