做后端开发,接口写多了总会遇到这样的尴尬:前端吐槽你的接口看不懂,网关监控报警查不出是哪个接口出了问题,更糟的是用户重复下了一模一样的单,客服电话打到了老板那里。
问题出在哪?大致率不是技术不行,而是接口设计从一开始就”跑偏”了。
RESTful 这个词大家都听过,但真正落到项目里,十个团队有九个写的是”伪 RESTful”。这篇文章不讲理论定义,只抠三个最影响线上质量的点:资源建模、语义约束、工程化。搞懂这三个,你的接口设计能甩开身边大多数同行。
一、资源建模:URI 里只允许出现名词
先说一个最直观的判断标准:打开你们的接口文档,如果 URI 里能看到 get、create、delete 这类动词,基本可以断定设计已经走偏了。
RESTful 的核心思想实则一句话就能说清:URI 描述资源”是什么”,HTTP 方法描述”做什么”。
拿电商系统举例,两种写法的差距一目了然:
|
意图 |
常见错误写法 |
RESTful 写法 |
|
商品列表 |
/api/getGoodsList |
GET /api/v1/goods |
|
商品详情 |
/api/getGoodsById?id=42 |
GET /api/v1/goods/42 |
|
创建订单 |
/api/createOrder |
POST /api/v1/orders |
|
查店铺的商品 |
/api/getGoodsByShop?shopId=7 |
GET /api/v1/shops/7/goods |
光记住”不用动词”还不够,落地时还有三个坑:
第一,资源名统一用复数。 /goods 表明商品集合,/goods/42 是集合里的某一件。单复数混着用,是接口文档里最廉价的混乱来源。
第二,嵌套最多两层。 /shops/7/goods 表达”7 号店铺的商品”很自然,但四层五层嵌套下去,路由维护成本直接失控。超过两层就拆开,用查询参数过滤。
第三,状态变更优先改字段,而不是造动词。 列如撤销订单,简单场景直接 PATCH 修改订单的状态字段就行;动作复杂时才思考加一个”动作子资源”。两种都不犯规,怕的是混着用还没规律。
二、语义约束:方法用对、状态码用全、幂等做到位
如果说资源建模是”面子”,语义就是”里子”,出问题都在这一层。
HTTP 方法别乱用。 GET 只负责读、POST 负责创建、PUT 全量更新、PATCH 局部更新、DELETE 删除。最常见的毛病是所有操作都用 POST 包打天下——这等于主动放弃 HTTP 自带的语义,缓存、监控、幂等全得自己重新造轮子。
状态码别只会 200 和 500。 创建成功返回 201,参数错误返回 400,未认证 401,无权限 403,资源不存在 404,重复冲突 409,触发限流 429。客户端按状态码分支处理,远比解析自定义的错误码枚举可靠。
幂等性是电商接口的命门。 什么叫幂等?同一个请求发一次和发十次,效果完全一样。
想象一个场景:用户点了”提交订单”,网络卡了,前端超时自动重试,用户等不及又连点两下——如果服务端没有幂等保护,库存被扣了三次,钱也扣了三笔。
解决办法业内很成熟:客户端每次发起请求时带一个唯一的”幂等键”(Idempotency-Key),服务端认这个键,同一个键重复到达,直接返回第一次的处理结果,业务逻辑不再执行。就这么一个机制,能挡住绝大多数重复扣款、重复下单的事故。
三、工程化:版本、分页、错误格式
这一层和理论关系不大,纯粹是踩坑踩出来的共识。
版本管理:别过度设计。 最直接的做法是把版本号放进 URI,列如 /api/v1,调试方便、网关路由好配。把版本号藏在请求头里的方案理论上优雅,但联调抓包时极其折磨人,中小团队没必要折腾。
分页:数据量小用页码,量大用游标。 传统的”第 10 页、每页 20 条”实现简单,但翻得越深数据库越吃力,而且翻页过程中有新数据写入,就会出现数据重复或漏掉。大流量场景要用游标分页——以上一页最后一条数据的 ID 作为锚点往下取,数据库索引直接定位,性能和稳定性都不是一个量级。
错误格式:全系统必须统一,并且带上请求 ID。 不管用什么格式,关键是所有接口返回结构一致,前端才能写统一的拦截器。每个错误响应带上 request_id,接入链路追踪后,排查线上问题能少加许多班。
写在最后
接口规范的意义不在于符合某篇论文的定义,而在于降低协作和排障的成本。
URI 只放名词、方法语义用对、幂等和分页按场景选方案、错误格式全系统统一——这几点做不到位,文档写得再美丽,也只是”伪 RESTful”。





