ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RESTful API 设计最佳实践:从资源建模到幂等与限流

RESTful API 设计最佳实践:从资源建模到幂等与限流 我做后端开发这些年前前后后评审过的接口设计请求多得数不清。几乎每个新项目启动时团队都会说这次我们一定要用 RESTful 风格来设计 API可翻开代码一看Controller 里十有八九长这样/api/getUser、/api/updateUser、/api/sendVerifyCode……名字倒是挺规范实际上跟 RESTful 没什么关系。说白了多数人把用 HTTP JSON 写接口当成了 RESTful结果做出来的是披着 REST 外衣的 RPC 接口。这篇文章我想把 RESTful API 设计这件事讲透从资源建模、URL 命名、HTTP 方法语义到错误处理、分页筛选、幂等并发再到安全限流和文档契约。面向的读者是正在设计接口的后端开发、负责前后端协作的架构师以及打算把现有接口规范化的团队。不管你是刚入行还是已经写了几年接口只要能跟着把每一节的核心思路落到项目里接口的可用性、可维护性和协作体验都会明显上一个台阶。1. 先搞清楚 RESTful 到底在解决什么问题1.1 为什么很多接口只是形似 RESTful在聊最佳实践之前得先花点时间说清楚 RESTful 的本质。很多团队上来的第一个问题就是RESTful 的 URL 到底该用单数还是复数用 PUT 还是 PATCH这些当然要讨论但都是战术层面的细节。真正的问题在于你设计接口的时候到底是用资源的视角在思考还是用动作的视角在思考。动作视角是最直觉的思考方式。业务上有个操作叫下单那就建一个/api/createOrder有个操作叫取消订单那就建一个/api/cancelOrder。需求一多接口列表会变成一长串动词清单。这种设计在 RPC 框架里完全没问题但放在 HTTP 接口里就浪费了 HTTP 本身的能力。HTTP 协议天生是面向资源的它定义了一套统一的方法GET、POST、PUT、PATCH、DELETE、OPTIONS……来表达对资源做什么而不是让你为每个动作发明新 URL。REST 的全称是 Representational State Transfer翻译过来是表征状态转移。这个名词很学术拆开看其实不难我们把业务数据抽象成资源Resource资源在服务器上有自己的状态客户端通过 HTTP 方法让资源发生状态转移服务器返回给客户端的不是内部实现而是资源的一种表征Representation通常是 JSON 或 XML。也就是说客户端不关心服务器怎么存数据、怎么跑逻辑它只跟资源打交道。1.2 REST 的四个核心约束少一个都不完整很多人以为 RESTful 就是URL 用名词、返回 JSON其实那只是表象。正式一点的 REST 架构有四个核心约束我结合自己的实践逐个说资源化Resource把业务对象建模为资源例如用户、订单、商品。每个资源有唯一的标识也就是 URI。这个约束是最容易理解的但也是最容易被打破的。无状态Stateless服务器不保存客户端上下文每个请求都携带足够的信息可以被服务器独立理解。登录状态、会话信息必须由客户端管理。好处是服务器容易横向扩展负载均衡随便加机器。统一接口Uniform Interface所有资源通过同一套 HTTP 方法访问通过状态码、媒体类型等标准化的方式传递语义。这样客户端面对一个新资源时不需要学习一套新的调用约定。表征Representation服务器返回的是资源的表征而不是内部对象本身。客户端通过表征来理解资源状态并通过提交表征来请求修改资源。我见过最典型的不满足 REST 约束的接口就是登录接口。很多项目里写POST /api/login这没什么大问题但紧接着会写POST /api/logout、POST /api/refreshToken、POST /api/changePassword再加上GET /api/getUserInfo——接口的语义就完全碎掉了。资源化思考的第一步是识别出登录在系统里其实是对Token 资源的创建POST /api/auth/tokens创建令牌POST /api/auth/tokens/refresh刷新令牌POST /api/auth/tokens/revoke吊销令牌。你看动词就从 URL 里消失了全被 HTTP 方法和资源名消化掉了。1.3 别把 REST 当万能药什么时候不该硬上既然把 RESTful 说得这么重要我也得泼一盆冷水。REST 适合的场景是资源清晰、CRUD 语义明确、客户端多样化Web、App、小程序、第三方的系统。但有几类接口硬上 RESTful 反而别扭内部高并发 RPC 调用服务与服务之间传输的都是明确的调用语义比如批量计算价格推送消息给一百个人用 gRPC 或内部 RPC 框架更合适性能和表达能力都更好。复杂报表聚合查询一张大宽表十几个维度自由组合你很难把它建模成一组干净的资源。这时候暴露一个POST /api/reports/query反而诚实——它本质上就是一次命令式查询。纯实时指令比如重启设备、发送验证码、触发转码任务这类动作强行资源化会得到POST /devices/{id}/restart这种带动词的子资源。这其实无所谓REST 社区普遍接受动作子资源作为一种实用妥协。我个人的判断标准很简单如果这个接口被客户端当作函数来调用且没有明显的资源归属那就别硬凹 RESTful 的造型。反过来只要数据结构上有明显的名词比如用户、订单、文章、任务那 REST 就是最省心的选择。2. URL 设计资源命名、层级与版本管理2.1 命名规则名词复数、小写、中划线确认了资源的视角URL 的命名就有章可循了。业界经过多年沉淀基本收敛出了一套默认约定我直接给结论约定项推荐写法不推荐写法原因资源名用名词/orders/getOrdersURL 里不应出现动词集合用复数/users/user复数语义上表示集合更一致字母全小写/orders/Orders避免大小写敏感导致的混乱单词间用中划线/order-items/order_items或/orderItems中划线在 URL 里最不易被误解资源名具体化/customers/{id}/invoices/data/{id}资源要能被业务理解这套约定不是拍脑袋定的。URL 的第一个原则是可预测性客户端只要知道资源的集合名就能推导出单个资源的 URL知道了一个子资源就能顺着层级探索到父资源。第二个原则是可读性日志和监控里出现/api/v1/order-items/1042的时候任何人一看就知道在说什么。第三个原则是安全全小写加中划线能避免很多大小写转换引起的缓存失效和路由歧义。一个常见的争议点是单个资源该用/users/1还是/user/1我推荐统一用复数。虽然单个资源用单数/user/1在逻辑上更精确但团队里总会有人忘了这个约定一会儿写/users/{id}一会儿写/user/{id}路由就成了隨缘匹配。统一用复数后集合和单体的关系一目了然/users是列表/users/1是列表里的一个元素。2.2 层级嵌套两层够用最多不要超过三层URL 的层级能表达资源之间的从属关系。比如订单明细一定是从属于某个订单的那么GET /orders/{orderId}/items就比GET /items?orderId123更自然。嵌套的规则建议这样掌握有从属关系才嵌套没有从属关系的资源一律平铺。比如用户和订单逻辑上订单属于用户可以嵌套GET /users/{userId}/orders但如果你需要按订单号直接查也应该保留GET /orders/{orderId}这种扁平入口。嵌套最多两层。/organizations/{orgId}/projects/{projectId}/tasks/{taskId}这种三段嵌套虽然每个层级都真实存在但会让 URL 变得冗长而且客户端拼接容易出错。第三层开始建议在查询参数里用关系字段表达GET /tasks?projectIdxxx。动作子资源要克制。/orders/{id}/cancel这种带动词的 URL 不是禁用但要先想想能不能用状态的转移来表达。比如订单从待支付变成已取消本质上是一次收集修改那PATCH /orders/{id}加上{ status: cancelled }就是资源化的做法。只有当修改附带大量副作用、或者状态机不允许客户端直接改字段时才用动作子资源。我在实际项目里还遇到一个需求前端经常问我为什么不能用/api/orders_products这种中间表命名。原因很简单URL 是给人看的不是给数据库表设计的。如果你想让用户在所有订单中查询同时筛选出包含某个商品品的订单合理设计是GET /orders?productId42而不是建一个/order-products资源。资源建模要贴近业务语言而不是暴露物理表结构。2.3 版本策略路径版本为什么最省心接口一定会变这是铁律。版本管理的目标不是让人不破坏接口而是让破坏控制在可接受的范围内。常见的版本方案有四种路径版本/api/v1/orders、/api/v2/orders。优点直白、易缓存、日志清晰、网关容易做灰度路由缺点URL 看着重复。Header 版本Accept: application/vnd.example.v1json。优点URL 干净缺点排错时不容易看到调试工具里要额外配置对前后端联调不够友好。参数版本/api/orders?version1。优点实现简单缺点容易被人忽略缓存 key 天然隔离性差。域名版本v1.api.example.com。优点可以彻底隔离缺点运维成本高同域认证和跨域策略要额外处理。我自己的项目几乎无脑选路径版本原因特别实际线上排查接口问题时负载均衡和网关日志里看到的都是完整 URL版本号直接在路径里不用再翻请求头Nginx 灰度一个/v2/前缀远比解析 Header 简单。版本从v1开始永远不要做v0也不要带日期版本——20240101这种版本号在代码里没有任何语义优势。版本策略还有一个容易踩的坑什么时候升大版本我的标准是出现不兼容变更且无法通过新增可选参数兼容时才升版。能向后兼容的改动比如新增字段、新增可选参数、新增枚举值都不应该升版本。只有删字段、改类型、改必填约束这种操作才配得上升一个大版本。顺便提一句删字段之前最好提前一个版本在文档里标记deprecated给客户端留够迁移周期这是生产级接口的基本素质。3. HTTP 方法与状态码的语义化运用3.1 方法分工不要再把 POST 当万能用法HTTP 协议定义了一组方法每个方法都有明确语义。设计接口时方法选对了接口语义就完成了一半。我见过太多接口文档里的接口类型POST下面列上一大堆不同的业务操作这是典型的没有用 HTTP 方法表达语义。具体分工是这样的GET安全且幂等只读获取资源或资源列表不产生任何副作用。POST创建资源或者触发一个不受约束的动作不幂等。PUT完整替换一个资源幂等。客户端提交的是资源的完整新状态。PATCH部分更新一个资源不幂等。客户端只提交要修改的字段。DELETE删除资源幂等。第一次删除返回 204第二次删除同一个 ID 返回 404但资源状态不再变化这被认为是幂等的。HEAD与 GET 相同但只返回响应头常用于探测资源是否存在。OPTIONS返回资源支持的 HTTP 方法用于 CORS 预检等场景。这里很多团队纠结的是 PUT 和 PATCH 怎么选。我的建议很简单绝大多数业务更新场景用 PATCH。因为前端的表单往往只提交几个字段用 PUT 的话必须要求客户端把整个对象完整传回来万一把没渲染出来的字段漏传服务器一覆盖就出事。PUT 留给那些真正的全量替换场景比如把充值的对象整个换掉。另一个高频问题是幂等这个词。GET、PUT、DELETE 幂等POST 不幂等这个结论大家在表上都看到了但生产环境要操心的往往是客户端因为超时而重试请求这类场景。比如前端创建一个订单点击提交网络超时用户再点一次结果出现了重复订单。这就引出第 5 章要重点讲的幂等键这里先记着HTTP 方法自带的幂等性只覆盖了同一条请求重复执行的情况解决不了两次不同的 POST 请求携带相同业务意图的问题。3.2 状态码每一个数字都有它的位置状态码用对了调用方连响应体都不用看就能知道发生了什么。我整理了一份自己在项目里最常用的状态码清单标注了真实使用场景状态码含义典型使用场景200 OK成功GET 获取资源、PATCH 更新成功201 Created创建成功POST 新建资源响应头带 Location204 No Content成功但无响应体DELETE 成功、PUT 成功但无需返回内容400 Bad Request请求参数错误缺少必填参数、类型错误、JSON 格式错误401 Unauthorized未认证缺少 Token、Token 过期403 Forbidden已认证但无权限角色权限不足404 Not Found资源不存在资源 ID 错误、路径错误409 Conflict状态冲突重复创建、状态机不允许当前流转422 Unprocessable Entity语义正确但业务校验失败用户名重复、库存不足429 Too Many Requests触发限流超过速率限制500 Internal Server Error服务器内部错误未捕获异常502 Bad Gateway上游代理错误网关连不上后端服务503 Service Unavailable服务不可用过载熔断、停机维护有几个容易用错的点值得单独说。401 和 403 的区别是你是谁和你有没有权限客户端拿到 401 会主动重新登录拿到 403 会弹出无权限提示用反了体验很糟。422 和 400 搞混也很常见我的约定是JSON 解析失败、类型错误这种请求本身烂掉用 400参数格式正确但业务上过不去比如邮箱已注册用 422。409 是给并发冲突和状态冲突准备的比如两个操作同时修改同一条数据、订单已经关闭却又要取消409 比 400 更能描述你的请求没问题但当前状态不允许。顺带提醒一个容易忽略的不要轻易返回 200 加业务错误码。很多国内团队把{code: 5001, msg: 库存不足}和 HTTP 200 配套使用理由是反正业务错误也得走响应体。这个做法在对外 API 里是灾难网关的限流统计、CDN 的缓存判断、监控系统的告警规则都依赖 HTTP 状态码你全部返回 200等于把这些基础设施全都蒙在鼓里。正确的做法是HTTP 状态码表达这次请求处理得怎么样业务码表达业务逻辑里具体是哪个环节出了问题两者各司其职。响应体里可以同时保留业务码但 HTTP 层面一定要如实反映结果。3.3 响应体结构让客户端少写几个 if响应体的结构直接影响客户端代码的复杂度。我推荐的统一响应结构长这样{ code: 0, message: success, data: { id: 10001, status: paid, amount: 99.00 }, traceId: a1b2c3d4-5678-90ab-cdef-1234567890ab }code是业务码0表示成功非 0 是业务错误标识。message是人类可读的描述方便快速判断。data是核心业务数据失败时可为null。traceId是链路追踪号排查问题时让客户端直接甩给你这个号。这个结构看起来简单但有个原则性要求成功和失败时响应结构必须一致。有些项目成功时返回{id: 1, name: x}失败时返回{error: xxx}客户端就得写两套解析逻辑。统一信封后客户端只需要判断code 0然后取data错误时读message。关于信封要不要带其实也有争议。纯 REST 原教旨主义者会说状态码已经足够表达结果不该再包一层data。但现实是Web 前端、App、小程序各自的请求封装不一样有的需要用message直接给用户弹提示有的需要拿traceId上报日志。统一信封降低了客户端的理解成本我自己的对外公开 API 会保留信封结构内部的微服务间调用反而会去掉信封直接用裸数据因为服务间通信有更严格的结构约定。4. 错误处理、分页过滤与字段筛选别让客户端猜4.1 错误信息要能直接定位问题404 加一句{message: not found}是最省事的写法也是最让客户端头疼的写法。好的错误响应应该告诉调用方三件事出了什么错、为什么错、怎么办。我参考了 RFC 7807 Problem Details 的思路结合团队习惯整理出了一个实用结构{ code: 40002, message: 订单金额不能为负数, details: [ { field: amount, reason: must_be_positive, message: amount 必须大于 0 } ], traceId: a1b2c3d4, path: /api/v1/orders }details数组是给前端表单报错用的。比如注册接口一次性提交用户名、邮箱、密码三个字段其中两个不合格你可以在details里带上每个字段的具体错误前端直接映射到表单输入框上。字段错误码称之为reason因为它是机器可读的稳定标识前端可以根据reason展示预置的文案而不是解析message字符串。我踩过的坑是message里直接拼了内部异常信息比如SQLIntegrityConstraintViolationException: Duplicate entry这既暴露了技术细节又对客户端没有任何帮助。还有一次把整个 Java 堆栈放到响应体里确实方便了排查但生产环境风险太大后来全部收敛为三个固定字段可读的 hint给用户看、稳定的 reason给代码判断、traceId给你去日志里查堆栈。4.2 分页、排序、过滤的通用约定列表接口是后端最常用的接口种型这类接口设计得好不好直接影响数据库压力和前端表现。先说分页业界两种主流风格页码分页?page1pageSize20响应里带total。适合管理后台、数据量可控的列表。游标分页?cursoreyJpZCI6MTAwMH0limit20响应里带nextCursor。适合信息流、数据量无限增长、深分页性能敏感的场景。我给了两种方案但更想强调的其实是深分页陷阱。客户端的页码一旦翻到一万页OFFSET 10000 LIMIT 20这种 SQL 的扫描成本会越来越高响应越来越慢。信息流场景必须用游标分页游标里编码了上一次返回的最后一条记录的位置每次查询都是从那之后取 20 条不管翻多深性能都一样。排序和过滤的约定也要提前统一。我的习惯是这样排序: /orders?sortcreated_at:desc,id:desc 过滤: /orders?statuspaidamount_min100amount_max500 语义过滤: /orders?statuspaidcustomer_id88 字段选择: /orders?fieldsid,amount,status 模糊搜索: /orders?q手机排序字段用冒号连接方向多个排序条件用逗号分隔这是后端最容易解析也最不容易产生歧义的格式。过滤条件也尽量不要用filterkey:value这种一次性编码的写法字段名值是最直白的。这里特别提醒查询参数的字段命名用camelCase还是snake_case不重要但一个项目只能选一种。推荐查询参数用snake_casecreated_atJSON 响应体用camelCasecreatedAt这符合 HTTP 查询串和 JSON 两个体系各自的社区习惯。4.3 字段选择与敏感信息保护一个复杂的业务对象的字段可能多达几十个但大多数客户端列表场景只需要其中几个。我强烈建议对外 API 支持fields参数GET /api/v1/users/42?fieldsid,name,avatar,email服务器只返回这几个字段。好处有三点减少网络传输、降低客户端解析成本、避免把不该暴露的字段比如password_hash、internal_note输出出去。实现fields参数最省事的办法是 Jackson 的JsonFilter或者用专门的视图对象DTO来做字段映射——优先推荐 DTO因为白名单写在代码里是可审计的而fields参数一旦允许任意字段透传你又得再加一层字段名白名单校验否则等于开了一个泄露口子。敏感信息保护是另一个容易漏的点。我见过一个真实案例用户详情接口直接把phone明文返回给前端前端又把它渲染到了页面上。抓包工具一抓手机号全部泄露。正确做法是响应用户对象时脱敏138****1234接口单独提供GET /users/{id}/balance等高敏感数据接口并且要求二次认证或权限校验。5. 幂等与并发最容易翻车的两个设计点5.1 幂等键让客户端可以放心重试现在很多项目都接了支付、下单、消息推送这类绝对不能重复执行的接口。拿下单举例用户点击提交订单前端发出POST /orders网络抖动导致响应超时用户又点了一次前端又发出一个一模一样的POST /orders。如果服务器没有幂等保护就会出现两条重复订单、两笔重复扣款——这是线上事故级别的 bug而且往往是设计阶段没考虑等出了事故才补。解决方案是幂等键。客户端在创建类请求的 Header 里携带一个全局唯一的 IDPOST /api/v1/orders Idempotency-Key: 5d0a4a0e-6d4e-4f0e-b2d2-8b3c7a1f4e9a Content-Type: application/json { product_id: 100, quantity: 2 }服务器端做的处理逻辑是收到请求后先查idempotency_key对应的处理记录。如果已存在且处于处理中直接返回之前的结果如果已存在且处于失败则允许使用新的请求重试。如果不存在开始处理业务同时把idempotency_key与处理结果一起存下来。实现上有几个细节值得注意。幂等键的存储最好与业务操作放在同一个数据库事务里也就是写业务记录和记录幂等键要么同时成功要么同时失败否则会出现业务没建上但幂等键已写入的脏状态。分布式环境下可以用 Redis 存幂等键但要注意设置合理的过期时间比如 24 小时到 48 小时过期后再次出现相同 key 就不在保护范围内。我自己实践下来凡是涉及资金、库存、积分变动的写接口一律强制客户端传Idempotency-Key不做兼容。5.2 乐观锁与条件请求防止并发覆盖幂等键解决的是重复请求乐观锁解决的是并发修改。典型场景是这样的两个管理后台的运营同时编辑同一个商品的标题和价格。A 先加载出商品信息改了标题B 在 A 保存之后也提交了自己的版本。由于后保存的 B 是基于旧数据改的提交时直接用整条记录覆盖A 的修改就丢了。这就是经典的丢失更新问题。乐观锁的经典做法是在数据库表里加一个version字段UPDATE products SET title ?, price ?, version version 1 WHERE id ? AND version ?;如果更新后受影响行数为 0说明 version 已经变了返回 409 Conflict 给客户端。客户端重新拉取最新数据再决定要不要合并。HTTP 层面还有更规范的做法用ETag加上If-Match头。服务器在GET /products/42的响应里返回ETag: v1.2客户端修改后提交PUT /products/42带If-Match: v1.2头。服务器比较ETag匹配则更新不匹配则返回412 Precondition Failed。这个方案的好处是完全符合 HTTP 语义坏处是服务端要自己生成ETag稍微多写一点逻辑。业务压力不大时用版本号字段就够了只要记住在更新语句里带上WHERE version ?这个细节救过我好几次。5.3 状态机与唯一约束最后的兜底幂等键和乐观锁之外还有两个兜底手段虽然不是 API 设计直接要求的部分但生产级接口必须了解。第一个是状态机校验。比如订单状态只能从待支付变成已支付再从已支付变成已发货。在一笔订单上做任何更新操作都应该用状态流转校验来防止非法跳转。代码上可以做一层简单的状态机配置而不是散落的if (order.status ! XXX)判断。多个接口并发操作同一个订单时状态机配合数据库行锁或乐观锁能拦截掉大部分不该发生的操作。第二个是数据库唯一约束。很多重复数据问题靠代码判断if (exists(phone))是拦不住的并发请求会同时通过这个判断。正确做法是在数据库字段上加唯一索引比如users.phone设成 unique插入时直接把异常抛出来再用代码翻译成 409 返回。幂等键的存储表里Idempotency-Key字段本身也应该建唯一索引双保险。6. 从能跑到能用安全、限流、文档与契约6.1 认证授权别让每个接口自己造轮子接口安全的首要原则是统一认证、统一授权每个接口各写各的登录校验最后一定有人漏掉。现在主流的 API 认证方式有三种选择取决于你的系统类型API Key适合服务器之间的调用和开发者工具。在 Header 里传X-API-Key: xxx网关统一校验。实现简单但 API Key 本质上是长期有效的明文凭证必须有权限范围且定期轮换。TokenJWT / Opaque Token适合用户态的 Web 和移动端。用户登录成功后拿到 Access Token后续请求带Authorization: Bearer token。JWT 自包含、无需查库但过期、吊销都不好处理Opaque Token 需要服务器存储或校验安全性更好。OAuth2.0适合有第三方授权需求的开放平台。完整流程复杂但如果是标准的三方登录和开放 API 场景这是最规范的选择。我自己的建议是如果团队没有专门的平台化需求用户态接口用 OAuth2.0 的简化流程比如 Authorization Code PKCE并配合短期 Access Token 和长期 Refresh Token 就足够了。Token 中不要塞入不必要的敏感数据过期时间不要太长一般 Access Token 控制在 15 分钟到 2 小时Refresh Token 一周到一个月。所有接口都必须跑在 HTTPS 上这是底线没什么好讨论的。6.2 限流与可观测性高并发接口的必修课一个设计良好的 API 不能只在正常负载下运转还要在流量暴涨时保护自己。限流是 API 平台最基础的保护手段常见做法是令牌桶算法在网关层统一配置每个用户、每个 API Key 的调用速率。限流触发时返回429 Too Many Requests响应头里带上三个标准字段X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 999 X-RateLimit-Reset: 1710000000这三个字段告诉客户端一共多少额度、还剩多少、什么时候重置客户端可以根据剩余额度决定是否降低调用频率而不是傻傻地等到被限流了才开始重试。配合Retry-After: 120头客户端可以知道下一次尝试的时间。可观测性方面最基础但也最容易被忽略的是traceId 透传。我前面在响应体结构里提过traceId前端报错时把 traceId 甩给你你就能在日志系统里查出整条调用链。服务端在接收请求时traceId如果为空就生成一个然后透传到下游的 Redis、数据库、MQ 等所有日志上下文。这样一次请求从网关到微服务再到数据库操作的完整链路都能串起来。6.3 OpenAPI 契约先行文档、Mock 与 Mock 测试一锅端说到 RESTful API 的最佳实践最后绕不开的就是文档。我见过太多团队维护一份 Markdown 格式接口文档接口改了忘了更新前端对着过期的文档联调浪费大量时间。后来我转型为写 OpenAPISwagger规范直接用代码生成文档把问题从根上解决了。以 Spring Boot 为例引入springdoc-openapi后只需要在 Controller 上写注解Operation(summary 创建订单, description 提交订单并扣减库存) ApiResponses({ ApiResponse(responseCode 201, description 创建成功), ApiResponse(responseCode 422, description 库存不足或金额非法) }) PostMapping(/api/v1/orders) public Order createOrder(Valid RequestBody CreateOrderRequest request) { ... }文档自动生成地址一般在/swagger-ui.html。更关键的是把 OpenAPI 文件当成契约来管理接口改动前先改 YAML通过评审后再动代码。前端的 Mock 服务可以直接基于 OpenAPI 文件生成联调时不需要等后端代码部署完。我个人的习惯是团队里维护一份openapi.yaml作为唯一的接口契约源代码生成、Mock、自动化测试全部从这份文件拉取。改接口的时候只要契约没改测试就不会挂契约一改CI 里自动生成的兼容性检查就会报警。这套流程跑顺之后接口设计和维护的效率提升非常明显团队协作里那种你改了接口不告诉我的抱怨基本就消失了。接口设计这件事最适合作为起步的不是把 URL 改漂亮而是先把资源建模想清楚这个业务对象是什么、它有哪些状态、客户端最常对它做什么操作。资源模型定了URL、方法、状态码就顺理成章。其次是统一错误处理和分页这些通用约定让调用方一次学会处处复用。最后才是幂等、限流、文档这些生产级的东西——它们不一定立刻派上用场但等出了问题再补代价往往翻倍。最后分享一个我一直在坚持的小技巧在你团队的内网 Wiki 里维护一份接口评审检查清单包括URL 是否使用了名词复数创建接口是否支持 Idempotency-Key错误响应是否包含 traceId是否更新了 OpenAPI 契约这些条目。每次提测前让开发同学自己过一遍比你在 code review 时反复口头强调管用得多。规范只有沉淀成清单和工具才能真正落地而不是靠某一个人的记忆力。
返回列表