做接口设计这么多年,我最大的感受是:接口写起来容易,设计好很难。你写一个接口,自己调用爽了不算数,要别人拿过去也能三分钟接入、不看文档也能猜到参数含义、出问题能快速定位,这才是真本事。这个道理,前端骂过后端、后端骂过前端的人都懂。这篇内容我把自己这些年踩坑换来的经验整理成36条锦囊,覆盖接口设计从定位、定义、上线到维护的完整链路,每一条都交代了背后的逻辑和实操细节,希望能让做后端、做全栈、以及刚入门想少走弯路的朋友,真正派上用场。
1. 动手之前:先把接口的定位想清楚
这一阶段很多人会忽略,觉得接口设计不就是写个URL、返回个JSON吗?实际上,大量返工和联调冲突都发生在设计阶段。想不清楚就编码,后面全是债。
1.1 接口是契约,文档先行
接口本质上不是你和你自己代码之间的函数调用,而是你和外部调用方之间的契约。契约意味着稳定、明确、可预期。所以动手写代码前,先把接口文档定下来,哪怕用最简单的Markdown都行,把路径、方法、请求参数、返回结构、错误码、示例值列清楚。
我见过太多项目,后端把接口写完了,前端一看参数名是create_time,后端返回的却是createdAt,联调现场吵得不可开交。文档先行不是为了好看,是为了让双方在动手之前就对齐认知,把“我以为”变成“我们约定”。
1.2 面向调用方设计,别面向数据库设计
新手常犯的一个错误是:数据库表有哪个字段,接口就返回哪个字段。表里有status tinyint,接口就返回0、1、2,调用方根本不知道什么意思。表里有user_id,接口就返回user_id,但调用方理解的是“用户编号”,你还得在文档里解释半天。
正确的做法是:面向调用方的使用场景设计。调用方要展示用户列表,你就返回用户ID、昵称、头像、注册时间这种语义清晰的结构,必要时把状态值翻译成人话,比如status_desc: "已启用"。多一层转换不费事,但调用方的接入成本会低很多。
1.3 命名规范统一,自解释优先
接口路径、参数名、字段名,命名风格必须统一。业内最常见的是路径用短横线命名法(kebab-case,如/user-profile),参数用驼峰或下划线都行,但一个系统内只能选一种。最怕的是同一个系统里,有的接口用userProfile,有的接口用user_profile,调用方直接懵掉。
我在实际项目中有一个习惯:凡是看一眼名字就能知道含义的,坚决不额外解释。比如/v1/users/{id}/orders,任何人一看就知道是“查询某个用户的订单列表”。反过来,/v1/queryUserOrderInfo这种命名就含糊,到底是查询单个订单还是列表?参数是什么?全靠文档兜底,一旦文档没跟上就废了。
1.4 资源定位统一风格,别发明REST之外的花样
现在做接口,基本都是REST风格,路径里用名词复数表示资源,用HTTP方法表示操作:GET读、POST新建、PUT全量更新、PATCH部分更新、DELETE删除。这套风格最大的好处不是“标准”本身,而是降低沟通成本,后端说“你DELETE一下这个订单”,前端立刻知道是怎么回事。
但要注意,REST不是银弹。有些场景,比如批量操作、复杂查询、触发任务,硬套REST会非常别扭。比如“批量审核订单”,POST /orders/batch-audit 就比硬造一个PATCH /orders/status更直观。我的经验是:主要资源走REST,特殊动作走POST加动词路径,规则清晰,别混用即可。
1.5 版本策略一开始就嵌进去
接口一旦上线,就会被多个调用方使用。你改返回结构,老调用方崩了,这是最痛的事。所以从第一个接口开始,就要把版本号放进URL或者Header里。业内最常见的是URL路径携带,如/v1/orders、/v2/orders,清晰直观,也能在网关层直接做路由转发。
更细一点的做法是:Header里带X-API-Version,优点是URL干净,缺点是排查问题时不如URL直观。我的建议是中小项目直接放URL里,省心;如果你们对API规范性要求很高,再考虑Http Header方案。关键是定下来之后就不能改,要改就是新版本,而不是在旧版本上玩花样。
1.6 同步还是异步,想清楚再动手
这个决定越早做越好。同步接口,调用方发请求后一直等着,适合实时性要求高的场景,比如查询订单状态、用户登录。异步接口,调用方发请求后立刻收到“已受理”,真正的结果通过回调或轮询拿,适合耗时较长的操作,比如批量导出报表、发送大量消息。
我见过不少项目,一开始所有接口都做同步,结果某个接口处理要30秒,前端超时直接报错,后端又被迫改成异步,白白返工。提前判断:如果这个操作无法在1秒内返回结果,且调用方不关心实时结果,就果断设计成异步,并约定回调地址或任务查询接口。
