写API接口这事儿,有人能写成诗,有人能写成恐怖片

2026-09-07 9 0

做后端开发久了,什么妖魔鬼怪都见过。有人的接口返回值是个谜,有人的接口文档是科幻小说,有人的接口状态码用200表示「服务器炸了」。今天咱们就来聊聊API设计里那些让人又爱又恨的事儿。

一、状态码:这玩意儿真不是随便写的

先说个真实案例。之前对接过一个第三方支付接口,退款接口返回的状态码是200,意思是「退款申请已提交」。但你不知道这笔钱到底退没退成功。提交了,不代表成功了啊!这是薛定谔的退款——只有你去查才知道结果。

正确的做法是什么?要么返回202 Accepted(表示请求已接受,正在处理),要么就老老实实返回真实的业务状态码。HTTP状态码是给HTTP层用的,业务状态码应该放在响应体里。

// 错误的示范
HTTP 200 OK
{"code": "FAIL", "message": "退款失败"}

// 正确的示范
HTTP 202 Accepted
{"code": "SUCCESS", "message": "退款申请已提交", "refund_id": "RF123456"}

记住这个原则:HTTP状态码表示「这个请求在HTTP层面怎么样了」,业务状态码表示「你让我干的活怎么样了」。别把俩玩意儿混着用。

二、RESTful:是个好东西,但别魔怔了

RESTful API这概念火了十几年了,烂大街了都。很多新人以为只要用GET/POST/PUT/DELETE就算是RESTful了,其实差得远。

但我见过更离谱的是——为了RESTful而RESTful。有个同事为了遵循「统一接口」原则,把用户修改密码做成了PUT /users/{id}/password。听起来挺像那么回事儿是吧?但你想过没有,密码这种敏感操作,GET请求都不应该出现,你PUT过去是打算在URL里明文传输密码?

// 这是什么鬼设计
PUT /users/123/password
Content-Type: application/x-www-form-urlencoded
password=newPassword123

真实场景里,RESTful就是个参考范式,不是圣经。有些接口你就是得用POST,有些操作你就是得打破所谓的「无状态」原则。灵活点,兄弟。

三、错误处理:别让调用方猜谜

我见过最离谱的错误处理是这样的:所有错误都返回200,错误信息藏在message字段里,message内容是「操作失败请联系管理员」。联系管理员?管理员欠你钱吗?

好的错误响应应该长这样:

{
  "code": "INSUFFICIENT_BALANCE",
  "message": "账户余额不足,当前余额 88.00 元,单笔消费 150.00 元",
  "details": {
    "current_balance": 88.00,
    "required_amount": 150.00,
    "shortage": 62.00
  },
  "request_id": "req_abc123xyz",
  "help": "https://api.example.com/docs/errors#INSUFFICIENT_BALANCE"
}

你看,调用方拿到这个错误,立刻知道怎么回事儿,不需要任何猜测。错误码要精确,错误信息要说人话,附带必要的数据让调用方能做决策。

四、版本管理:别让老代码成为你的噩梦

接口版本这事儿,很多人不重视,觉得改个返回值结构无所谓。结果某天线上突然就开始报错了,一查才发现:上周你改了用户信息的返回结构,把phone字段从字符串改成了对象,但老用户App还在按字符串解析,直接崩了。

版本管理的策略就那么几种:

// 路径版本(最直观)
GET /api/v1/users/123
GET /api/v2/users/123

// Header版本(看着专业但调试麻烦)
GET /api/users/123
Api-Version: 2024-01-01

// Query参数版本(最不推荐)
GET /api/users/123?version=2

我的建议是路径版本,简单直接。调用方想用哪个版本,URL里写得清清楚楚。而且这种设计天然支持灰度发布和A/B测试。

五、分页:这事儿说简单也简单,说复杂也复杂

分页接口你们肯定写过。但你注意过这些问题吗?

第一,页码从0开始还是从1开始?这个没有标准答案,但要写清楚,而且要一致。我见过一个项目,前5个接口页码从0开始,后3个从1开始,调用方每天都在debug。

第二,当数据量大的时候,OFFSET分页会越来越慢。1000万条数据,OFFSET 9999990,这种查询能跑几十秒。解决方案是游标分页(Cursor Pagination),基于ID或时间戳做条件查询。

// OFFSET分页(适合小数据量)
GET /api/articles?page=1&page_size=20

// 游标分页(适合大数据量)
GET /api/articles?cursor=eyJpZCI6IjEwMDAwMCIsImxpIjogIjIwMjQtMDEtMDEifQ&page_size=20
// 返回
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6IjEwMDIwIiwibGkiOiAiMjAyNC0wMS0wMiJ9",
    "has_more": true
  }
}

六、安全:别让自己的接口变成后门

这条本来想展开写,但感觉一两段说不清楚,就提几个点:

  • 所有接口都要有权限校验,别觉得内部接口就可以裸奔
  • 敏感数据要加密存储,密码要加盐哈希
  • 请求要有频率限制,防止被人刷接口
  • CORS配置要合理,别*满天飞
  • 请求ID要记录,方便排查问题和溯源

最后说两句

写接口这事,技术含量不高,但做好很难。什么叫好的API?调用方用起来舒服,出错了能快速定位,扩展新功能不需要改老代码。这就够了。

别追求什么「最优雅的设计」「最符合标准的实现」,追求「最实用、最少出bug、最方便维护」。代码是写给人看的,顺眼比啥都强。

祝大家的接口都稳稳当当,永不炸机。

相关文章

你的接口在说”别卷了”——我是如何用限流把爬虫和内鬼一起拒之门外的
当 AI 开始整活:最近这些新鲜玩意儿把我整不会了
当 AI 开始整活:最近这些新鲜玩意儿把我整不会了
写API七年,我踩过的那些坑,以及我是如何爬出来的
你以为你懂状态机?业务逻辑混乱的根源在这里
后台任务失败?你的队列可能比你的业务逻辑还不可靠

发布评论