API设计避坑指南|常见错误分析|RESTful规范与安全实践

API设计避坑指南|常见错误分析|RESTful规范与安全实践 一

文章目录CloseOpen

从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"},所有信息裸奔,结果被爬虫爬了个精光。正确的做法是“按需返回”,普通用户只能拿到nameavatar,管理员通过权限校验后才能看手机号,而且敏感字段要脱敏,比如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放具体数据,列表类返回要包含totalpagesize分页信息,比如{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%。
  • HTTPS传输:所有接口必须用HTTPS,别侥幸用HTTP。去年有个项目因为图省事用HTTP,结果用户登录信息被中间人攻击截获,最后不得不全量换HTTPS,还赔偿了用户损失。
  • 请求限流:用Redis或Nginx做限流,比如“每个用户每分钟最多100次请求”“每个IP每秒最多10次请求”。可以用漏桶算法或令牌桶算法,具体实现可以参考Nginx官方文档的限流配置
  • 数据脱敏:手机号、身份证、银行卡这类敏感信息,返回时必须脱敏,比如idCard: "1101234″,密码等绝对不能返回。
  • 防重放攻击:关键接口(比如支付、退款)要加nonce随机数和timestamp时间戳,服务器校验“这个nonce是否用过”“时间戳是否在5分钟内”,防止别人抓包重放请求。
  • 输入校验:所有参数必须校验,比如手机号格式、邮箱格式、ID是否为数字,推荐用Spring Validation(Java)或Joi(Node.js)这类校验库,别自己写正则表达式,容易有漏洞。
  • 之前帮一个金融项目做安全整改,就是按这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的幂等性?比如重复提交订单的问题怎么解决?

    核心是让“重复请求产生相同结果”。实践中可:

  • 用唯一标识(如订单号、请求ID)+状态校验,比如提交订单时前端生成唯一requestId,后端存储该ID并校验“是否已处理”;
  • 结合HTTP方法,PUT/DELETE天然幂等,新增操作(POST)可通过“唯一业务标识”(如订单号)确保重复提交时返回“已存在”而非重复创建;3. 关键接口加分布式锁(如Redis锁),防止并发重复处理。例如支付接口,通过订单号+Redis锁,确保同一订单只能被处理一次。
  • 0
    显示验证码
    没有账号?注册  忘记密码?