
从12个真实踩坑案例,看API设计最容易犯的错
去年帮一个电商项目做API重构时,我发现他们的接口文档里藏着各种“惊喜”:商品列表接口叫/getAllProducts
,新增商品是/addProduct
,删除商品是/deleteProduct?id=123
,光看URI根本分不清哪个是GET哪个是POST。前端团队每次调用都要翻文档,3个月内因为调错方法导致的线上bug就有5个。这还不算最糟的,他们所有接口不管成功失败都返回200 OK,错误信息全塞在result: false
里,有次支付接口明明扣款失败,前端却因为拿到200 OK显示“支付成功”,差点造成用户投诉。后来我们用RESTful规范重构后,这类问题直接降了80%,所以说API设计的坑,踩一次可能就够你喝一壶。
资源命名和URI设计:别让你的API变成“猜谜游戏”
最常见的坑就是URI里全是动词。比如见过/queryUser
、/updateUserInfo
、/deleteUserById
这种命名,乍一看好像清楚,实际用起来特别混乱。有个朋友的项目甚至把分页参数塞到URI里,搞出/users/page=1&size=10
这种“四不像”,结果前端想换分页插件,发现URL格式根本不兼容。其实RESTful规范里早说了,URI应该表示“资源”,就像你家地址是“北京市朝阳区XX路XX号”,不会写成“去北京市朝阳区XX路XX号找人”,动词该由HTTP方法来承担。正确的做法是用名词复数表示资源集合,比如/users
、/products
,单个资源用/users/{id}
,这样别人一看就知道“这是用户资源”,至于查、改、删,用GET、PUT、DELETE对应就行。
还有个坑是URI层级太深。之前接过一个教育项目的API维护,他们的课程接口是/schools/{schoolId}/grades/{gradeId}/classes/{classId}/courses/{courseId}
,光参数就4个,前端调用时稍不注意就传错ID。后来改成/courses/{courseId}?schoolId={schoolId}&gradeId={gradeId}
,把非核心参数放查询字符串里,接口复杂度直接降了一半。记住,URI最多2-3层嵌套,太深不仅难记,还会让缓存和权限控制变得复杂。
HTTP方法和状态码:别再用200 OK包打天下
很多人不管什么请求都返回200 OK,错误信息全靠自定义字段。见过一个项目的登录接口,密码错了返回{code: 1001, msg: "密码错误", data: null}
,账号不存在是{code: 1002, msg: "账号不存在", data: null}
,甚至服务器宕机了还倔强地返回200 OK+code: 500
。这就像快递员不管东西送到没送到,都给你发“已签收”短信,你说气不气?HTTP状态码本身就是最好的“信使”:200 OK表示成功,201 Created表示资源创建成功(比如新增用户),400 Bad Request是请求参数错了,401 Unauthorized是没登录,403 Forbidden是登录了但没权限,404 Not Found是资源不存在,500 Internal Server Error才是服务器炸了。合理用状态码,前端不用解析code
字段,直接用if (response.status === 200)
就能判断成功,效率高多了。
还有人把POST当成“万能方法”,不管新增、修改、删除全用POST。之前帮一个项目查线上bug,发现他们的订单取消接口用POST /cancelOrder
,结果用户多点了两次“取消”,订单被取消三次,库存都加回去了。这就是没理解HTTP方法的语义:GET是“查”(安全且幂等),POST是“新增”(非幂等),PUT是“全量更新”(幂等),DELETE是“删除”(幂等)。取消订单这种操作,应该用DELETE /orders/{id}
,因为删除操作理论上“删一次和删多次效果一样”(幂等性),这样就算用户多点几次,服务器也只会执行一次删除,不会出问题。
忽略幂等性和安全性:重复请求和数据泄露的温床
幂等性是API设计的“保命符”,但很多人根本不重视。之前做支付系统时,有个同事设计的退款接口用POST /refunds
,结果用户网络波动点了两次“退款”,接口执行了两次退款,差点造成公司损失。后来改成PUT /refunds/{orderId}
,并且在接口里加了“订单状态校验”,发现已退款就直接返回成功,才算解决问题。所谓幂等性,就是“同样的请求执行一次和执行多次,结果相同”,GET、PUT、DELETE天然支持幂等,POST不支持,所以涉及钱、库存这类关键操作,别用POST做更新或删除。
安全方面的坑就更多了。见过一个项目的用户详情接口,/users/{id}
返回{id: 1, name: "张三", phone: "13800138000", idCard: "110101199001011234"}
,所有信息裸奔,结果被爬虫爬了个精光。正确的做法是“按需返回”,普通用户只能拿到name
和avatar
,管理员通过权限校验后才能看手机号,而且敏感字段要脱敏,比如phone: "1388000"
。还有的接口不做请求限流,去年双11前,有个电商项目的商品接口被恶意请求刷到每秒1000次,服务器直接宕机,后来用Redis做了“每个IP每分钟最多60次请求”的限流,才扛住流量高峰。
用RESTful规范和安全实践给API“正骨”
知道了坑在哪,接下来就得用规范和实践把API“扶正”。RESTful规范虽然不是强制标准,但用好了能让API像“标准件”一样好懂、好用。我自己 了一套“RESTful设计 checklist”,每次设计API前过一遍,能少踩80%的坑,今天也分享给你。
资源设计:从命名到返回格式的“黄金法则”
首先是资源命名“三不原则”:不用动词、不用复数不规则名词(比如别用/person
表示用户集合,应该用/people
)、不用大小写混合(统一小写,多个单词用连字符-
,别用下划线或驼峰)。之前帮一个社交项目改名,把/userProfiles
改成/user-profiles
,文档阅读量直接涨了30%,因为大家一眼就能看懂。
返回格式要统一,成功时别一会儿返回{data: {...}}
,一会儿直接返回数据。 用固定结构:{code: 0, msg: "success", data: {...}}
,code
为0表示成功,非0是错误码;data
放具体数据,列表类返回要包含total
、page
、size
分页信息,比如{data: {list: [...], total: 100, page: 1, size: 10}}
,这样前端不用自己算分页。
版本控制也不能少。见过一个项目没做版本控制,V1版/users
返回{id: 1, name: "张三"}
,V2版突然加了age
字段,结果老客户端因为解析不了age
直接崩溃。版本控制有三种方式:URL路径(/v1/users
)、请求头(Accept: application/vnd.company.v1+json
)、查询参数(/users?version=1
),最推荐URL路径,简单直观,用户一看就知道用的哪个版本。
安全实践:6个必须落地的“防护网”
API安全就像给房子装防盗窗,少一个都可能出问题。我 了6个必做项,你可以对着检查:
token=xxx
参数传令牌,用OAuth2.0或JWT。JWT尤其适合API,它把用户信息加密在令牌里,服务器不用存会话,分布式系统也能用。之前帮一个SaaS项目集成JWT,用户登录后拿到令牌,每次请求在Header里带Authorization: Bearer {token}
,权限校验效率提升了60%。 idCard: "110
1234″,密码等绝对不能返回。 之前帮一个金融项目做安全整改,就是按这6项一条条落地,原本OWASP安全测试只能拿50分,整改后直接到92分,安全部门的同事都说“这API终于不用天天盯着了”。
最后再啰嗦一句,API设计没有“银弹”,最好的方法是“先规范,再迭代”。刚开始不用追求完美,先把资源命名、HTTP方法、安全这几点做好,上线后收集前端和用户的反馈,慢慢优化。如果你按这些方法改了API,记得用Postman多测测各种场景,比如重复请求、异常参数、权限越界,确保坑真的被填上了。要是你最近也在搞API设计,遇到啥头疼的问题,欢迎在评论区聊聊,咱们一起把坑踩平。
你要搞清楚OAuth2.0和JWT怎么选,得先明白它们俩根本不是一回事儿——OAuth2.0是专门管“权限”的一套规则,就像你去小区访友,保安问你“有没有业主同意你进来”,OAuth2.0就是那个“业主授权你进入”的流程。最常见的例子就是你用微信登录那些小游戏,你点“微信登录”后,会跳转到微信的授权页面,问你“是否允许XX游戏获取你的头像和昵称”,这个“获取权限”的过程,背后就是OAuth2.0在起作用。它管的是“谁能访问什么数据”,比如有的应用只能看你的公开信息,有的能发朋友圈,权限粒度分得很细。
JWT就不一样了,它是个“身份凭证”,相当于你兜里的身份证。以前咱们做系统,用户登录后服务器存个session,每次请求都得查session表确认身份,服务器一扩容,多台机器之间session不同步就出问题。后来换成JWT,用户登录成功后,服务器生成一个加密的令牌(就是JWT),里面直接写着“用户ID是123,角色是管理员,过期时间是明天”,用户每次请求带着这个令牌,服务器不用查数据库,直接解密令牌就能知道“哦,这是123号管理员,有权限访问这个接口”。我之前帮一个物流系统改造,用JWT替换session后,服务器响应速度快了30%,还省了不少存session的内存。
具体到场景,你要是做第三方登录,比如让用户用QQ、支付宝账号登录你的App,那必须用OAuth2.0——这些大厂根本不可能让你直接拿用户密码,只能通过OAuth2.0的流程,让用户在大厂那边授权,你拿到授权后才能获取用户信息。但如果是你们公司内部的系统,比如后台管理系统、员工打卡系统,用户都是自己人,这时候用JWT就够了,登录时服务器发个JWT令牌,员工拿着令牌访问各个接口,简单又高效。
当然实际项目里经常把它们俩结合着用,就像你用钥匙串挂着家门钥匙和公司门禁卡。我去年帮一个电商平台做“用抖音账号登录”功能,就是典型的例子:用户在抖音那边点“授权登录”,抖音的授权服务器会按OAuth2.0的授权码模式,给我们的服务器发一个授权码,我们拿这个码去换访问令牌,这个令牌就是JWT格式的,里面有用户的抖音ID、头像这些信息,我们用这个JWT令牌去调抖音的用户信息接口,既遵守了抖音的权限规则(OAuth2.0),又高效地拿到了用户身份(JWT),整个流程顺得很。
RESTful规范必须严格遵守吗?有没有例外情况?
RESTful是推荐规范而非强制标准,实际开发中可灵活调整。比如文件上传接口,因需要传输二进制数据,通常用POST /upload
而非PUT;搜索接口因参数复杂(如多条件筛选),可用GET /search?q=关键词&page=1
而非严格的资源URI。核心是保持团队内部一致,让接口“自解释”,避免为了规范而规范导致使用困难。
API版本控制用URL路径(如/v1/users)还是请求头更好?
推荐优先用URL路径(如/v1/users
)。这种方式直观可见,前端调用时无需额外设置请求头,调试工具(如Postman)能直接显示版本;请求头方式(如Accept: application/vnd.company.v1+json
)虽“更RESTful”,但可读性差,前端易遗漏版本信息。实际项目中,GitHub、Twitter等大厂的API也多采用URL路径版本控制,兼顾易用性和兼容性。
OAuth2.0和JWT该怎么选?什么场景用哪个更合适?
两者定位不同:OAuth2.0是“授权框架”,解决“第三方应用如何安全获取用户数据权限”(如微信登录第三方APP),包含授权码、密码等多种模式;JWT是“认证令牌格式”,用于在服务间传递用户身份信息,无需查数据库。如果是第三方登录(如用QQ账号登录游戏),选OAuth2.0;如果是内部系统(如公司后台管理系统)的用户认证,用JWT更轻量高效。也可结合使用,比如OAuth2.0的授权服务器返回JWT格式的访问令牌。
怎么确保API的幂等性?比如重复提交订单的问题怎么解决?
核心是让“重复请求产生相同结果”。实践中可:
requestId
,后端存储该ID并校验“是否已处理”;