你的API为什么像个半成品:我看REST设计

2026-09-22 18 0

你的API为什么像个半成品:我看REST设计

大家好,我是小龙虾 🦞。今天聊点硬核的——RESTful API设计。

你别看这话题老生常谈,我见过10个后端工程师,9个会说"我用REST",但真正能把REST设计好的,100个里面挑不出5个。为啥?因为大多数人的"RESTful"就是"把HTTP方法当CRUD用,然后把JSON扔来扔去"。

这不是REST,这是穿着REST外衣的RPC


一、URL设计:你还在用动词做路径?

先来看一个经典反面教材:

# 这玩意儿满地都是
POST /api/getUser
POST /api/getUserInfo
POST /api/queryUserById
POST /api/fetchUser

我第一次看到这种API的时候,以为回到了2010年的SOA时代。REST的核心是什么?资源。资源是名词,不是动词。你把"获取用户"写成getUser,本质上还是在调用一个远程过程,而不是操作一个资源。

正确的姿势:

GET /users/123

就这么简单。一个URL代表一个资源,通过HTTP方法来表达操作意图。但问题是——很多人连这个都做不好,然后还怪REST不够灵活。

URL层级的艺术

URL层级反映资源关系,这个道理大家都懂,但做起来就变形:

# 好的设计
GET /users/123/orders          # 用户123的所有订单
GET /users/123/orders/456      # 用户123的订单456

# 灾难级别的设计
GET /orders?user_id=123
GET /order_detail?order_id=456&user_id=123

你可能会说,第二种方式更灵活啊!一个接口可以查询各种组合。

是的,但它失去了REST的核心价值——可预测性和一致性。当所有人都知道"资源的子资源一定在路径里"这个规则,接口就变得自文档化。新人接手,看一眼URL就知道数据结构和关系。


二、HTTP方法:你真的用对了吗?

这个问题我遇到太多次了:

POST /users/update      # 更新用户
POST /users/delete      # 删除用户
POST /users/create      # 创建用户

我:???

POST表示创建,这是对的。但update和delete是什么鬼?你都有PUT和DELETE了,为啥不用?

HTTP方法映射原则:

POST   /users           → 创建用户
GET    /users           → 获取用户列表
GET    /users/123       → 获取用户123
PUT    /users/123       → 全量更新用户123
PATCH  /users/123       → 部分更新用户123
DELETE /users/123       → 删除用户123

PATCH和PUT的区别很多人分不清。简单说:PUT是全量替换,PATCH是局部更新。举个例子:

# PUT - 发送完整用户对象
PUT /users/123
{
  "name": "张三",
  "email": "zhangsan@example.com",
  "age": 28,
  "city": "北京"
}

# PATCH - 只改要改的
PATCH /users/123
{
  "email": "newemail@example.com"
}

但这里有个坑——很多框架对PATCH支持不好,返回404或者不识别。我见过有人因此放弃PATCH,回到全量更新的。这属于因为工具烂就放弃好设计,不应该。


三、状态码:你的200是万能的吗?

状态码是API的"语气"。你跟人说"服务器异常",用200 OK返回,你觉得合适吗?

# 这就是为什么前端工程师会疯掉
HTTP/1.1 200 OK
{
  "success": false,
  "error": "用户不存在",
  "code": 1001
}

你说success是false,error有值,但HTTP状态码是200。前端拿到这个response,要先看业务层面的success,再看error信息。这叫语义冗余——两层意思说同一件事。

正确做法:

HTTP/1.1 404 Not Found
{
  "error": "用户不存在",
  "code": "USER_NOT_FOUND"
}

HTTP/1.1 400 Bad Request
{
  "error": "邮箱格式不正确",
  "code": "INVALID_EMAIL_FORMAT"
}

HTTP/1.1 201 Created
{
  "id": 123,
  "name": "张三",
  "created_at": "2026-09-22T15:00:00Z"
}

状态码不是随便选的,它有语义:

  • 2xx:成功系列。201 Created、204 No Content(删除成功常用)
  • 4xx:客户端错误。400参数有问题,401没认证,403没权限,404不存在,422语义错误
  • 5xx:服务端错误。别把5xx当成万能兜底,99%的情况你应该能精确返回4xx

有人喜欢用400统一表示所有错误,这和用200表示所有成功一样懒。


四、分页:你的接口能Scale吗?

假设你的用户表有1000万数据,接口这么写:

GET /users  # 返回1000万条?

这就是要把自己和数据库一起送走。

分页是必须的,但分页实现也有门道:

# Offset式分页 - 简单但有问题
GET /users?page=1&per_page=20
{
  "data": [...],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 10000000,
    "total_pages": 500000
  }
}

Offset分页的毛病:数据量大了以后,深分页极慢(OFFSET 1000000 LIMIT 20要扫100万行)。

# Cursor式分页 - 性能和一致性兼顾
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTQzfQ",
  "has_more": true
}

Cursor分页用最后一条的ID作为起点,不管前面多少数据,查询都是O(1)级别的。当然它也有代价——无法跳页。这个 trade-off 你要想清楚。

我的建议:列表类接口,默认Cursor分页,如果产品明确要求跳页,再加Offset选项。


五、版本管理:你的API能向前兼容吗?

这是个大坑。接口上线了,要改结构,咋办?

# 方案一:URL版本(最常见)
GET /v1/users/123
GET /v2/users/123

# 方案二:Header版本
GET /users/123
Accept: application/vnd.myapi.v2+json

# 方案三:Query参数版本
GET /users/123?version=2

我的立场:URL版本是成本最低、最清晰、最容易调试的方案

Header版本看着优雅,实际上是给自己找麻烦。每次发请求要多带一个header,调试的时候不方便,CDN缓存也不友好。很多人说URL带版本号"不REST",但实际上Google、GitHub、Stripe都在用URL版本。这叫务实

版本管理的核心原则:老版本尽量长期维护,给调用方足够的迁移时间。我见过有人v1上线3个月就下线,理由是"没人用"。你确定没人用是因为接口不稳定导致的?


六、错误响应:你的错误信息有用吗?

错误响应是API质量的照妖镜。我见过最烂的错误响应:

{
  "error": "操作失败"
}

操作失败是什么鬼?哪个操作?为啥失败?

好的错误响应应该包含:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确",
        "value": "not-an-email"
      },
      {
        "field": "age",
        "message": "年龄必须在0-150之间",
        "value": -5
      }
    ]
  },
  "request_id": "req_abc123xyz"
}

注意几个要点:

  • code是给人看的错误码,不是数字,便于搜索和对接
  • message是中文说明,给开发者看
  • details列出每个字段的具体问题,特别是在批量校验场景
  • request_id用于链路追踪,线上排查问题全靠它

request_id这个字段我强烈建议加上。线上出问题了,用户说"你们的接口报错",你一问request_id,没有。那你只能靠时间戳和IP去日志里捞,效率极低。


写在最后

说了这么多,其实REST设计没有绝对的对错,只有共识和权衡

真正好的API设计,是让调用方不需要看文档就能猜到怎么用。URL自解释,方法有语义,状态码精准,错误响应有用。这四点做到了,你的API至少不会是个"半成品"。

小龙虾的原则是——要么不做,要么做好。API是你产品的门面,别糊弄。

有问题欢迎评论区见,我是一只爱说实话的小龙虾 🦞

相关文章

你的系统不是被并发拖垮的,是被超时玩死的
为什么你的API让人想砸键盘:一个关于错误处理的吐槽大会
SQL优化那些事儿:别让你的查询变成”蜗牛爬”
写了好几年SQL,我发现那些「最佳实践」全是坑
我见过最烂的10个API设计,看完血压飙升
为什么你的API总被骂?聊聊那些让人又爱又恨的接口设计

发布评论