新闻详情

新闻详情

首页 / 资讯中心 / 详情

RESTful API设计核心:资源、路径与HTTP方法的对应关系

发布时间:2026/10/2 8:41:56来源:尧图网络
RESTful API设计核心:资源、路径与HTTP方法的对应关系
很多人第一次接触 RESTful 时都会陷入一种奇怪的状态RESTful API、RESTful 风格、RESTful 接口这几个词天天见但真要让自己动手设计一套接口却说不清楚资源、路径、方法这三者到底怎么对应。我在工作里见过不少后端同学把接口写成/getUserById、/deleteOrder或者把所有操作统一成 POST前端联调时特别别扭。这篇文章就围绕这个基础问题展开把资源建模、路径设计、方法映射一次说清楚。适合刚接触 RESTful 的开发者也适合写过不少接口但没系统整理过设计规范的同学。搞懂这套对应关系之后你再去看市面上任何一份 REST API 文档思路都会清晰很多。1. 先搞清楚 RESTful 到底在解决什么问题1.1 从“网址资源”开始理解我们平常用的网址本质上就是在访问一个资源。什么叫资源订单、用户、商品、文章这些都是资源。资源是名词不是动词。RESTful 的核心思想就是让“网址路径”只负责告诉服务器“你要的是哪个资源”让“HTTP 方法”只负责告诉服务器“你想对这个资源做什么”。这两个职责一旦分开接口设计就从“到处起名字”变成了“有规律可循”。举个例子不按 RESTful 风格写的时候你可能会看到这些接口/queryUserById?userId123/getUserInfo?userId123/findUser?uid123三个接口其实都在表达“根据 ID 获取用户”但路径命名完全不一样。而按 RESTful 风格它只有一种标准表达GET /users/123。路径里的users代表“用户这类资源”123代表“具体哪一个用户”GET代表“查询”。这套表达方式很接近人与人之间的交流——你问别人“我要找 123 号用户的信息”而不是“执行一下 findUserById 这个函数”。很多人一开始不理解为什么路径里要用名词复数比如users而不是user。我的理解是/users表示整个用户资源集合/users/123表示这个集合里的某一个元素。集合用复数单条资源用集合路径加 ID这样层级关系非常自然也方便扩展。这个约定不是银弹但它能让接口看起来高度一致。1.2 没有 RESTful 时接口为什么乱我接手过一个老项目里面的接口风格五花八门有的用/doCreateUser有的用/user/create还有的干脆一个/operation走天下靠请求体里的类型字段区分动作。这种设计最大的问题不是难看而是“没有规律”。前端每次接一个接口都要问后端“这个接口是干嘛的参数传什么”后端自己三个月后再看代码也要重新翻接口清单才能回忆起来。RESTful 把“资源”和“操作”拆开以后接口文档可以变得很短。因为只要定义好了资源路径剩下的就是给每条路径配一套标准方法。比如用户资源无非就是列表、详情、创建、更新、删除。订单资源也一样。这样后端写接口时不用再纠结命名前端看路径就能猜出八九分。更重要的是HTTP 协议本身自带了很多能力比如缓存、重试、状态码当你的接口严格遵循 RESTful 风格时这些能力就能顺理成章地用起来。这里顺便提一句“资源隔离”的概念。现在很多系统都是多租户架构不同租户的数据必须隔离开。RESTful 路径设计里可以通过/tenants/{tenantId}/orders这种方式把不同租户的订单资源放在不同路径下。从接口表现上看这就是一种资源隔离从后端实现上看还要在服务层再做一次权限校验不能只靠路径。后面我会专门讲这个问题。2. 资源建模与路径设计详解2.1 资源是名词不是动词设计 RESTful 接口的第一步不是打开编辑器写路由而是先梳理业务里有哪些“名词”。你可以拿出一张纸把系统里能想到的实体都列出来用户、订单、商品、支付记录、评论、标签。这些就是候选的资源集合。然后再看实体之间的关系是独立存在还是必须依附于另一个实体存在。确定资源边界是最容易出错的地方。我自己的经验是如果一个对象有独立的生命周期、独立的 ID、可以被多个其他对象引用那它就应该是一个顶级资源如果它只属于某个父对象离开父对象就没有意义那它更适合做子资源。比如“订单项”它只存在于某个订单之下单独查“第 5 个订单项”没有任何业务意义所以放到/orders/{orderId}/items里更合理。而“支付记录”虽然也关联订单但它可能被财务系统独立查询也有自己的唯一标识这时候把它设计成顶级资源/payments会更灵活。路径里千万不要出现动词。有些同学会把“登录”设计成/login把“注册”设计成/register严格来说这不太 RESTful。登录的本质是“创建一条会话资源”所以可以写成POST /sessions注册的本质是“创建一个用户”所以可以写成POST /users。刚开始这样设计可能觉得绕但想清楚“动作背后的资源”以后接口的扩展性会好很多。比如你以后要支持“退出登录”那就对应DELETE /sessions/current整个路径体系非常统一。2.2 集合、单个资源、子资源的路径层级路径设计有一个基本套路我整理成了一张表路径模式含义示例/资源集合这一类资源的集合/users/资源集合/{id}集合中的单个资源/users/123/资源集合/{id}/子资源集合某个资源下的子资源集合/users/123/orders/资源集合/{id}/子资源集合/{子id}某个资源下的单个子资源/users/123/orders/456这个层级要控制深度我不建议超过三层。比如/schools/{schoolId}/students/{studentId}/courses/{courseId}/enrollments这种路径看一眼就头大。层级太深说明资源关系建模出了问题或者你在强行用路径表达复杂的关联关系。遇到这种情况可以考虑把最深的那一级提升为顶级资源然后用查询参数来表达过滤关系。子资源到底要不要嵌入父路径这其实是个取舍。以订单为例“某个用户下的订单”可以写成/users/123/orders也可以写成/orders?userId123。两种都能实现但语义不同前者强调“用户的子资源”适合业务上总是从用户维度出发的场景后者强调“订单是独立资源只是用 userId 过滤”适合订单本身是业务核心、需要被各种维度查询的场景。我自己的倾向是如果这个子资源离开父资源后仍有意义就用顶级资源加过滤参数如果它纯粹是父资源的一部分就做嵌套。这样代码维护起来更清晰。2.3 过滤、排序、分页用查询参数不要把条件塞进路径路径只用来定位资源筛选条件应该放在查询参数里。比如查询已支付的订单不太合适/orders/paid更合适/orders?statuspaid为什么不用/orders/paid因为paid不是订单集合里的某个资源它只是一个筛选状态。如果把状态都写进路径你会发现后面还有pending、shipped、cancelled每个都要设计一条路径整个接口会被条件组合撑爆。查询参数的好处是组合灵活比如GET /orders?statuspaidpage2size20sortcreated_at,desc一次把筛选、分页、排序全都表达清楚而且不需要新增路径。这里有一个常见的困惑路径参数和查询参数到底怎么分我的经验是路径参数用于唯一标识一个资源比如{id}或者标识资源的亲缘关系比如{userId}查询参数用于描述一堆资源的属性比如status、type、page。记住这个原则路径就不会乱。另外接口一旦发布路径最好不要变。因为路径一变等于给资源改了门牌号旧客户端全部会迷路。所以在设计阶段多花点心思比上线后被迫兼容要省力得多。3. HTTP 方法与资源状态变更的对应关系3.1 GET、POST、PUT、PATCH、DELETE 各自该干嘛资源和路径确定之后剩下的问题就是“用哪个方法”。HTTP 协议里常用方法就五个它们的语义很清楚GET读取资源。它不应该改变服务器上的任何状态所以是安全且幂等的。浏览器缓存、爬虫、预加载都依赖这个性质。POST在集合上创建新资源或者执行一些无法用其他方法表达的操作。它不幂等同一个 POST 请求发两次可能创建两条记录。PUT整体替换某个资源。客户端传过来的应该是一个完整的资源对象服务端用这个对象完全覆盖现有资源。它是幂等的。PATCH部分更新某个资源。客户端只需要传要修改的字段比如改用户名就只传{username: new}。它不是天然幂等的要看实现方式。DELETE删除资源。发一次和发一百次最终结果都是“资源不存在”所以它是幂等的。我用一个生活化的类比来记GET像是查快递物流信息看看不动手POST像是下单创建一个新的订单PUT像是买了一台全新的电脑整机把所有配置一次性给到PATCH像是给电脑换个内存条只动其中一个部件DELETE就是把它扔了。这样一想什么时候该用哪个方法心里就会很明白。3.2 方法映射场景表拿用户资源举例子完整的映射关系是这样的操作路径方法请求体成功状态码获取用户列表/usersGET无200获取单个用户/users/{id}GET无200创建用户/usersPOST用户完整信息201整体更新用户/users/{id}PUT用户完整信息200部分更新用户/users/{id}PATCH需要修改的字段200删除用户/users/{id}DELETE无204这套映射关系是所有 RESTful API 的骨架。创建资源用POST /资源集合返回 201 并且带上新资源的路径获取资源用GET更新资源用PUT或PATCH删除资源用DELETE。你会发现路径一直没变变的只是 HTTP 方法。这就是“资源、路径、方法对应关系”最直观的体现。实际开发里我最想强调的一点是动作不要塞进路径。如果有人把“获取用户”写成/getUser把“删除用户”写成/deleteUser?id123那就是把方法语义和路径语义混在一起了。get这个动作本身就隐藏在GET方法里路径根本不需要再写一遍。正确的做法是GET /users/{id}、DELETE /users/{id}。路径保持纯净方法承担动作职责这个边界一旦模糊整个接口风格就会变形。3.3 幂等性的实战价值你只看理论知识可能觉得“幂等”这个词很抽象但在生产环境里它非常重要。我遇到过线上问题用户下单时客户端请求超时前端做了重试结果同一笔订单被创建了两条。为什么因为创建订单用的是POST它不幂等。一次 POST 就是创建一单重试一次就多了一单。解决思路有两个方向。一是把创建操作改成幂等的比如客户端在请求体里带一个全局唯一的clientRequestId服务端收到请求后先查一下这个 ID 是不是处理过处理过就直接返回上一次的结果。二是用PUT搭配客户端生成的资源 ID比如PUT /orders/order-abc-123服务端按这个 ID 做 upsert重试多少次都不会重复创建。这两种方式在支付、下单这类高风险场景里都很有用。相对而言GET、PUT、DELETE因为天然幂等客户端超时后可以放心重试POST则需要开发者在服务端额外做幂等处理。理解这点你的接口健壮性会有明显提升。4. 实操从零设计一套“用户订单” RESTful API4.1 先梳理资源与关系纸上谈兵没有意义我拿一个很常见的“用户订单”业务来走一遍完整设计过程。假设我们在做一个电商系统核心实体有用户、订单、订单项、支付记录。首先把实体间的关系理清用户和订单一个用户有多个订单订单属于用户。但订单本身有独立业务价值需要被后台按各种条件查询所以我会把它设计成顶级资源/orders同时提供/users/{id}/orders作为便捷查询入口。订单和订单项订单项没有独立标识离开了订单就没有存在意义所以设计成子资源/orders/{id}/items。订单和支付记录支付记录有独立查询需求而且一笔订单可能多次支付尝试设计成顶级资源/payments。资源边界确定以后路径就好写了。不会一张口就写路由心里先有一个资源地图比什么都重要。我见过很多新同学上来就写POST /createOrder其实他没有先问自己“Order 这个资源到底有哪些子资源、哪些关联资源”。资源地图画好了接口设计就是填空题。4.2 路径与方法对应清单下面是我们这套业务最终确定的核心接口清单操作路径方法说明用户注册/usersPOST创建用户返回 201获取用户信息/users/{id}GET单个用户详情修改用户资料/users/{id}PATCH只传需要修改的字段删除用户/users/{id}DELETE返回 204创建订单/ordersPOST请求体包含userId、items查询用户订单列表/users/{id}/ordersGET按用户维度浏览订单查询全部订单/ordersGET后台用支持筛选分页获取订单详情/orders/{id}GET返回订单及订单项部分更新订单/orders/{id}PATCH比如修改收货地址取消订单/orders/{id}PATCH传{status: cancelled}获取订单项/orders/{id}/itemsGET查看订单下的商品明细创建支付记录/paymentsPOST支付回调或下单后发起支付创建订单我选用了POST /orders而不是POST /users/{id}/orders。因为订单是独立资源而且创建订单需要的信息很多跟用户 ID 并没有强绑定关系。查询用户订单时才走子资源路径这样职责更清晰。取消订单我用了PATCH加状态字段而不是POST /orders/{id}/cancel。如果你只是改变订单状态理论上可以直接更新资源但如果不只是改状态还伴随着复杂的校验、发消息、写流水那把它定义成一个动作资源POST /orders/{id}/cancel也完全可以。关键是要在团队里保持一致不要一会儿用状态字段一会儿用动作路径。4.3 响应结构、状态码与错误处理接口的响应结构也要提前定好。我给团队常用的统一结构是{ data: { ... }, error: null }出错时变成{ data: null, error: { code: ORDER_NOT_FOUND, message: 订单不存在, details: [] } }状态码严格按语义来。成功时列表和详情用 200创建用 201 并在Location头里带上新资源路径删除用 204。出错时参数错误用 400未认证用 401权限不足用 403找不到资源用 404数据冲突用 409参数能过格式校验但业务校验失败用 422触发限流用 429。我最反对的做法是“无论成功失败都返回 200然后靠 body 里的 code 区分”。这套做法虽然方便前端统一处理但会让 HTTP 状态码失去意义也失去了网关、负载均衡、监控系统直接判断错误的能力。 RESTful 风格本来就主张“用协议表达语义”状态码就是其中很重要的一环。4.4 API 版本化路径设计路径设计里还有一个容易忽略的问题版本。你的 API 不可能永远不变而路径一变老客户端就崩。比较通行的做法是在路径最前面加版本号比如/api/v1/users、/api/v2/users。这样当你需要引入不兼容的变更时可以开一条新版本路径老版本继续维护给客户端足够的时间迁移。有的团队喜欢把版本号放在 Header 里比如Accept: application/json; version2。这种方案路径更干净但实际使用中不利于缓存和排查——你抓包看 URL 看不出客户端用的是哪个版本还得额外看 Header麻烦不少。对外部公开 API我个人更推荐 URL 路径版本化内部服务之间如果你的框架和网关支持 Header 版本也可以接受。核心原则是先约定好再动手写接口不要等上线之后才想起来要兼容老版本。路径是资源的门牌号改门牌号等于折腾所有用户。5. 常见问题与排查技巧实录5.1 动词进路径怎么纠正我前面已经说过最典型的错误就是/getUserById这类路径。但在一个存量系统里你说改就改成本很高。这时我建议分两步走第一步在新的路由层把老路径映射到新路径比如把GET /getUserById?id123重定向或转发到GET /users/123第二步在客户端逐步切换到新路径等老路径没有任何调用量以后再下线。这个过程里要注意老路径的参数解析方式和新路径可能不同重定向时要做参数转换。不要想着一步到位兼容期是难免的。如果你是新项目从一开始就要定规矩路径里只允许出现名词和 ID不允许出现create、update、delete、get这些动作词。代码 Review 时看到这种路径直接打回去改。长期执行下来团队的 API 风格会很统一。5.2 方法用错导致的安全隐患有些同学会用 GET 去做删除操作比如GET /api/user/delete?id123。这在公网服务里相当危险。浏览器预加载、搜索引擎爬虫、聊天工具预览都有可能自动发起 GET 请求。万一某个爬虫顺着链接爬过来服务器就把用户删了这属于重大事故。正确的做法是把删除映射到DELETE /users/{id}这样 GET 请求永远只读不会触发状态变更。另外删除操作前一定要做权限校验和 CSRF 防护不要因为接口是 DELETE 就掉以轻心。还有一个小坑PUT和PATCH用反。PUT要求客户端传完整资源如果只传一个字段服务端会把其他字段覆盖成空这是非常典型的线上事故。所以客户端做“只改某个字段”时必须用PATCH。后端也要在文档里写清楚PUT是整存PATCH是定点修改。5.3 路径嵌套过深怎么办路径嵌套能直观表达资源关系但不要贪多。我之前接手的项目里有一条/api/v1/schools/{schoolId}/students/{studentId}/courses/{courseId}/enrollments每次看到都头大。走查代码时发现这个路径在后端其实也是逐层校验性能差逻辑也绕。后来我们把它改成/api/v1/enrollments?courseIdxxx一下子清爽了。什么时候该拆“路径层级超过三层”就是一个信号。如果某个子资源会被很多不同的父资源共享或者你需要单独对它做列表、筛选、分页那它就应该从嵌套路径里脱离出来变成顶级资源。嵌套是表达从属关系的手段不是把所有关联关系都塞进路径的理由。5.4 找不到资源和权限不足的状态码区分排查接口问题时最常用的命令就是curl -i https://api.example.com/v1/users/123直接看状态码和响应头。我遇到过同事把权限不足和资源不存在都返回 400导致前端没法区分“参数错了”还是“权限不对”只能硬解析错误码。建议未认证返回 401已认证但没权限返回 403资源不存在返回 404。如果出于安全考虑不想暴露资源是否存在客户端拿到了 404 和拿到 403 语义不同需要你根据业务自己权衡。常规做法是“未登录一律 401登录后不管资源存不存在权限相关的都返回 403”因为此时判断的是“你能否访问这个资源”而不是“资源是否存在”。5.5 多租户资源隔离与并发更新前面提过资源隔离这里展开说说。多租户系统常见路径是/api/v1/tenants/{tenantId}/orders从接口层面看路径已经做了租户隔离。但服务端必须从登录态或 JWT 里解析出当前租户再和路径里的tenantId比对不能只信路径参数。因为路径参数是客户端传的攻击者完全可以传一个别人的tenantId。我在项目里通常还会在网关层统一注入租户上下文业务层只取上下文里的租户 ID避免每个接口都重复写校验逻辑。并发更新则是另一个容易踩的坑。两个管理员同时编辑同一个用户资料后提交的覆盖先提交的业务上可能就是事故。我常用的方案是用 ETag 做乐观锁GET /users/{id}返回一个ETag更新时客户端带If-Match: etag服务端发现 ETag 不匹配就返回 412 Precondition Failed让客户端刷新数据后再提交。这套机制配合 RESTful 风格非常自然因为GET、PUT、PATCH、DELETE天然支持条件请求头不需要额外设计一套并发控制协议。6. 最后分享一个设计小习惯说了这么多最后分享一个我自己的小习惯。每次设计新接口前我会先列一张表格资源名、路径、方法、用途、返回状态码。表格填完接口设计基本就定下来了。这个习惯帮我避开了很多“边写边改、越改越乱”的情况。一开始可能会觉得麻烦但时间久了你会发现真正的好接口不是写出来的是设计出来的。RESTful 不是一套死板的标准它更像是一组约定目的是让接口保持简单、一致、可预测。资源、路径、方法这三者的对应关系就是你走向这套约定的第一把钥匙。你按这个思路去拆任何一套成熟的 REST API都会发现它们惊人地相似。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Flutter鸿蒙跨平台开发实战:从选型到适配全流程解析 2026/10/2 9:20:18

Flutter鸿蒙跨平台开发实战:从选型到适配全流程解析

去年年初我接到一个内部项目:把公司积累了好几年的消防知识培训内容,做成一个手机App。需求很明确——用户装到手机里,能看消防知识图文和视频、刷模拟题、做错题本、收藏重点,管理员后台可以远程更新内容。听起来不算复杂&#x…

阅读更多 →
NetworkX实战:Python图计算与网络分析快速上手 2026/10/2 9:20:18

NetworkX实战:Python图计算与网络分析快速上手

1. 项目概述:为什么说网络分析是数据处理的下一个必修课做开发这些年,我越来越觉得一个有意思的现象:很多你以为是“数据”的东西,本质上其实是一张图。社交网络里的人际关系是图,电商平台里用户和商品的购买关系是图&…

阅读更多 →
安卓开发必看:优先英语页面高效查文档与搜索技巧 2026/10/2 9:20:18

安卓开发必看:优先英语页面高效查文档与搜索技巧

1. 为什么优先看英语页面:这不是崇洋媚外,而是效率问题 做安卓开发的朋友应该都有过这种经历:搜一个问题,中文结果翻了好几页,要么是过时的解决方案,要么是绕来绕去的转载帖,最后还得靠猜。后来…

阅读更多 →
Linux非root用户源码安装aria2与环境变量配置指南 2026/10/2 9:20:18

Linux非root用户源码安装aria2与环境变量配置指南

共享服务器上没有 sudo 权限,想装个 aria2 却发现apt install一律返回 Permission denied——这种场景我前后遇到过不下十次。多数人的第一反应是"找管理员开权限",但更现实的路径是以 Linux 非root用户身份完成源码安装 aria2,再通…

阅读更多 →
ADB 自动化测试实战:命令、Python 封装、元素定位与排错 2026/10/2 9:20:18

ADB 自动化测试实战:命令、Python 封装、元素定位与排错

1. 先搞懂 adb 是什么:它凭什么成为自动化测试的地基刚接触 adb 自动化测试的人,几乎都会经历同一个阶段:把adb devices敲进命令行,看到一串设备号,然后陷入沉默——这玩意儿到底能帮我干什么?我自己的经历…

阅读更多 →
PostgreSQL命令行利器psql:从连接到元命令的完整实战指南 2026/10/2 9:20:05

PostgreSQL命令行利器psql:从连接到元命令的完整实战指南

不少朋友接触 PostgreSQL,第一反应都是装个 pgAdmin 或者 DataGrip,然后鼠标点点点。但真到了服务器上排查问题、写一次性脚本、或者处理几百万行数据导入导出的时候,你会发现所有图形界面都使不上劲——身边只剩一个黑乎乎的终端窗口。这时候…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉