想象你走进一家生意火爆的餐厅:菜单编号统一、服务员动作规范、上菜小票一目了然,加菜、退菜、换菜都有固定术语。如果每个服务员各有暗号,菜单乱写,厨房动不动上错菜,这家餐厅很快会倒闭。RESTful API 设计就是给 Web 服务立一套“餐厅规矩”:资源是菜单上的菜品,URI 是菜品编号,HTTP 方法是点单动作,状态码是回执小票,版本管理是菜单改版,Token 是会员卡,OpenAPI 是官方菜单。掌握了这套规矩,你设计的接口就能让任何客户端不费力气地点到想要的“菜”。
学会 RESTful API 设计,是 AI 工程师把模型能力封装成标准服务、与前后端和微服务高效协作的必备基本功。
可视化演示
动态展示对称加密的完整流程:密钥生成、明文加密、密文传输与解密还原,直观呈现加密保护数据安全的核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
对称加密流程 发送方使用密钥加密明文生成密文 接收方使用相同密钥解密还原明文 E(K,P)=C 加密函数生成密文
资源命名约定
餐厅菜单上的每道菜都有固定编号,比如“凉菜-03”,而不是“好吃不贵的那盘”。如果编号随意,顾客点菜全凭暗号。RESTful API 也一样,资源命名是客户端与服务端约定的“菜品编号”。一个清晰稳定的 URL,能让调用方不看文档也猜出大概含义。
在 REST 中,资源是 API 管理的核心对象,URI 是资源的地址。最佳实践是:用名词复数表示集合,例如 /users 表示用户集合,/users/{id} 表示单个用户;全部使用小写,多词之间用连字符 -,比如 /order-items,避免下划线或驼峰;资源的嵌套层级不要超过三层,如 /users/{id}/orders,更深的关系尽量用查询参数表达。
好的资源命名,就像餐厅编号规则一样,让每个地址都自带“可读性”。客户端看到 /users/42/orders,大脑立刻能翻译成“用户 42 的订单列表”,而不是一串莫名其妙的路径。
- 固定前缀
/api/v1是接口的统一入口,相当于餐厅的门牌号和楼层的固定前缀。 - 集合名词
\{\text{resource}\}表示一类菜品,如 “用户” 对应users,使用复数名词。 - 唯一标识
\{\text{id}\}是某个具体资源的编号,如同一桌客人的桌号。 - 从属资源
\{\text{subresource}\}表示主资源下的子资源,如用户下的订单。
HTTP方法语义
顾客不会对着服务员喊“给我上一道青椒肉丝”就完了,服务员需要知道你是想“看看有没有这道菜”、“点这道菜”、“换掉整桌菜”,还是“退掉菜”。HTTP 方法就是这套点单动作:GET、POST、PUT、PATCH、DELETE,各自有明确的语义。
把这些语义固定下来,是 REST 设计最重要的约定。GET 用于获取资源,不改变服务端状态;POST 用于创建新资源;PUT 用于整体替换一个资源;PATCH 用于局部更新;DELETE 用于删除资源。GET 是“安全”方法,因为它只读;PUT 和 DELETE 是“幂等”方法,因为重复执行多次,结果与执行一次一致。POST 和 PATCH 通常不保证幂等,重复提交可能产生多个资源或叠加变更。
这就像餐厅的服务规范里,“点菜”“换菜”“退菜”各有固定术语,所有服务员按同一套黑话沟通。客户端与服务器之间有了这套语义,即使跨团队合作,也不会把“更新”和“创建”混为一谈。
- 幂等集合
\text{Idempotent}表示“可以重复点同一份菜而不会出错”的方法集合。 - GET 方法获取数据,像“看一眼菜单”,不会改变任何东西。
- PUT 方法把资源整体替换,重复替换多少次结果都一样。
- 安全方法
\text{Safe}表示只读操作,不会让厨房多出一道菜。
状态码规范
上菜后,顾客会拿到一张小票:2 开头表示成功,3 开头表示需要再看看,4 开头表示顾客的问题,5 开头表示后厨的问题。不需要看小票上的明细,只扫一眼首位数字,就知道这单到底成没成。HTTP 状态码就是服务端给客户端的“标准回执”。
在设计 API 时,状态码要精确表达请求结果:200 OK 表示成功,201 Created 表示资源创建成功,204 No Content 表示成功但无返回体;400 表示参数错误,401 表示未认证,403 表示无权限,404 表示资源不存在,429 表示触发限流;500 表示服务器内部错误,503 表示服务暂时不可用。不要所有情况都返回 200,然后在响应体里塞一个业务码,那样会让缓存、监控和重试机制全部失效。
顾客看到小票首位是 4,会反思自己是不是点错菜;看到首位是 5,就知道是后厨出了问题。客户端程序也可以用同样的直觉:4xx 不重试,5xx 可以有限重试。这就是状态码的价值。
- 类别函数
\text{family}(s)表示状态码所属的大类,如同小票上的首位数字。 - 具体状态码
s是服务端返回的精确值,例如 404 或 500。 - 整除取类
\left\lfloor \cdot \right\rfloor的意思是向下取整,404 除以 100 得到 4。
查询与分页
当菜单有几十种菜时,顾客不会从头翻到尾,而是会问:“有素的吗?价格 30 元以内有哪些?”服务员根据筛选条件快速缩小范围,再一页一页展示。API 的查询、过滤、排序、分页,就是这套“点菜筛选”工具。
查询集合资源时,应该用 query 参数做过滤和排序,而不是把这些条件塞进路径。例如 GET /api/v1/dishes?category=vegetarian&sort=price&limit=10&offset=20。常见的分页参数是 limit 和 offset:limit 表示每页返回多少条,offset 表示跳过的条数。响应中可以返回总条数,并利用 Link 头字段提供 next 和 prev 链接,客户端不需要自己拼接 URL。
分页让接口在大数据量下像翻菜单一样轻松。更重要的是,返回标准化的分页链接,就像菜单底部写清楚“下一页”,客户端不用猜测翻页规则。
- 偏移量
\text{offset}是已经跳过的条目数,比如跳过前 20 条。 - 每页条数
\text{limit}是每页展示多少条,比如每页 10 条。 - 计算跳过的页数
\left\lfloor \frac{\text{offset}}{\text{limit}} \right\rfloor得到已经越过的完整页数。 - 页码换算加 1 把从 0 开始的偏移量换算成人类友好的页码。
版本管理
餐厅经营几年后要升级菜单:改良配方、调整价格,但老顾客还习惯旧套餐。聪明的餐厅不会直接撕掉旧菜单,而是在一段时间内新旧菜单并存,并标明“经典款”和“新款”。API 版本管理也是同样的道理。
REST API 一旦被外部使用,就不能随意破坏兼容性。最常用的版本策略是 URL 路径前缀,例如 /api/v1/... 和 /api/v2/...,直观、方便路由和缓存。另一种方式是使用自定义头或 Accept 头,例如 Accept: application/vnd.api+json;version=1,但这种方式不如 URL 直观,排障时也容易被忽略。版本管理不只是加一个数字,还要有兼容策略和弃用周期:新版本上线后,旧版本应至少并行运行一段时间,通过 Deprecation 响应头提醒客户端迁移,并给出清晰的迁移文档。
版本管理像菜单改版一样,要照顾老顾客。保留“经典套餐”,提前告知停售日期,用户才愿意继续信任这家店,而不是被迫匆忙迁移。
v_i 用来选择其中一版。- 版本集合
V = \{v_1, v_2, \dots, v_n\}表示系统同时维护的所有版本。 - 版本编号
v_i是第i个版本的显式标识,如v1。 - 路径前缀
\text{path} = \text{/api/} v_i / \text{resource}表示 URL 以版本号开头,路由时按版本分发。
认证与授权
高档餐厅不是谁都能直接进后厨。顾客需要先出示会员卡证明身份,服务员再根据会员等级决定你能进包厢还是只能在堂食区。这里的会员卡就是 Token,进包厢的权限就是 Scope。
认证(Authentication)回答“你是谁”,授权(Authorization)回答“你能做什么”。REST API 通常把 Token 放在 Authorization: Bearer <token> 请求头中传递,避免把 Token 写在 URL 里被日志记录。更复杂的场景使用 OAuth2 授权码流程:用户同意后,应用获得一个有限权限的访问令牌。设计原则是默认禁止匿名访问,并遵循权限最小化:给令牌只分配 read、write 等必要权限,而不是一把万能钥匙。
会员卡让顾客畅通无阻,但后厨重地只有主厨助理能进。Token 同样让请求带着身份和权限抵达资源,既方便又安全。
- 令牌映射
\mapsto表示“拿令牌换权限”,服务器验证 Token 后得到对应的 Scope。 - 权限子集
\subseteq表示权限范围是全集中的一个子集,不能超出预定义范围。 - 最小权限默认只给
read,需要写入时才分配write。
文档与反模式
一家餐厅若没有统一菜单,顾客只能靠口耳相传,点单全靠缘分。OpenAPI 就是 RESTful API 的“官方菜单”,它用结构化文件描述每个接口的路径、参数、响应格式和安全要求,甚至可以自动生成 SDK 和在线文档。
OpenAPI 规范的核心三要素是:paths 描述所有接口路径和操作,schemas 描述请求与响应数据结构,security 描述认证与授权方案。采用“先写文档、再写代码”的 Design First 方式,可以让前后端在开工前对齐契约。常见的反模式包括:URL 中使用动词,比如 /createUser;所有请求一律返回 200,只在 body 里写业务码;资源嵌套超过三层;没有分页;状态码语义混乱。这些都会让 API 像一本没有目录的菜谱,越用越痛苦。
有了“官方菜单”,顾客、服务员和后厨不再鸡同鸭讲。OpenAPI 文档就是团队协作的通用语言,也是自动化工具落地的基础。
- 路径集合
\text{paths}列出“有哪些菜”,即接口路径和可用的 HTTP 方法。 - 数据模式
\text{schemas}定义“菜的用料”,即请求和响应体的字段结构。 - 安全方案
\text{security}说明“谁可以进后厨”,即认证和授权方式。
AI 应用场景
大模型推理服务接口
阶段:模型部署与推理服务
解决的问题:传统方式常把模型推理封装成 RPC 风格接口,例如 /invoke?action=chat,参数混乱、难以复用。用 REST 资源化后,模型补全任务被视为一种资源,通过 POST /v1/completions 创建任务,用 201 Created 表示成功,429 表示限流,客户端可以依据标准语义正确处理错误与重试。
具体案例:某 AI 平台为内部多个模型提供统一推理网关,所有模型暴露为 POST /v1/{model}/completions,版本用 URL 前缀 v1/v2,认证统一使用 API Key 放在 Authorization 头。模型升级时新老版本并行运行,旧版本半年后下线,业务方在迁移窗口内平滑切换。
RAG 知识库检索接口
阶段:RAG 应用开发
解决的问题:知识库检索接口如果按函数命名,很容易出现一接口一风格。REST 设计把“文档”和“集合”作为资源,用 GET /v1/collections/{id}/documents?query=...&limit=10&offset=0 统一处理过滤和分页,让前端、后端和算法工程师按同一套规则协作。
具体案例:企业知识库机器人提供 GET /v1/documents?tags=hr&sort=-updated_at&limit=20&offset=40 查询文档列表,响应中通过 Link 头返回下一页地址,并用 OpenAPI 自动生成 SDK,客户端接入成本大幅降低。
Python 演示
import re class ApiPathValidator: def __init__(self): self.resource_re = re.compile(r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$") self.action_keywords = {"create", "delete", "update", "get", "list"} def validate_uri(self, uri): segments = uri.strip("/").split("/") errors = [] for seg in segments: if seg.isdigit() or seg.startswith("{"): continue if not self.resource_re.fullmatch(seg): errors.append("segment '{}' should use lowercase and hyphens".format(seg)) word = seg.lower().replace("-", "") if any(word.startswith(k) for k in self.action_keywords): errors.append("segment '{}' should not be a verb".format(seg)) if len(segments) > 4: errors.append("path depth exceeds recommended 3-4 levels") return errors or ["OK: valid resource naming"] def report(self, uris): for uri in uris: print(uri, "->", "; ".join(self.validate_uri(uri))) if __name__ == "__main__": validator = ApiPathValidator() uris = [ "/api/v1/userOrders", "/api/v1/users/{id}/orders", "/api/v1/createOrder", "/api/v1/users/42/orders/7/items/9/details" ] validator.report(uris)
self.resource_re = re.compile(r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$")定义资源名的合法格式:小写字母开头,只允许小写字母、数字和连字符。if seg.isdigit() or seg.startswith("{"):跳过路径中的数字 ID 和模板参数,比如42和{id}。if not self.resource_re.fullmatch(seg):检查路径片段是否完全匹配规范,驼峰命名userOrders会被判定为非法。errors.append("segment '{}' should use lowercase and hyphens".format(seg))记录第一个错误:建议改为小写连字符格式。word = seg.lower().replace("-", "")把片段归一化,方便识别createOrder这类动词开头。if any(word.startswith(k) for k in self.action_keywords):发现create、delete、get、list等动词时给出反模式提示。validator.report(uris)对一组实际 URI 执行批量检查,输出每条路径的合规结论。
常见误区
误区把动作写进 URL 很直观,比如 `/api/createUser`,一看就知道是创建用户。
正解REST 的要点是让 HTTP 方法表达动作,URL 只表达资源。就像餐厅里“点菜”是动作,菜单上的“青椒肉丝”是资源,你不会把菜单写成“请炒青椒肉丝”。/api/createUser 应该改为 POST /api/users,用 POST 的语义表示创建。
为何常见:早期 Web 接口经常按函数名定义 URL,RPC 风格影响根深蒂固,容易把“函数调用”习惯带到 REST 设计中。
误区不管成功失败都返回 HTTP 200,在响应体里用 `code` 字段表示业务状态,认为这样前端好统一处理。
正解HTTP 状态码是“上菜小票”,客户需要一眼看出这单成没成。200 表示成功;4xx 表示顾客的问题,如参数错误、未登录、无权限、不存在;5xx 表示后厨的问题。全用 200 会让监控、告警和自动重试机制全部失灵。
为何常见:一些旧 SDK 习惯把响应统一包一层 result/code,形成了“业务码优先”的惯性。
误区更新资源时 PUT 和 PATCH 差不多,随便选一个都可以。
正解PUT 是整体替换,像换一份完整套餐,未传字段可能被清空;PATCH 是局部修改,像给菜加一份辣,只影响传进来的字段。用 PUT 做局部更新是一种常见 bug。
为何常见:两者都翻译成“更新”,很多人忽略了“整体”和“局部”的根本差异。
误区只要 URL 带上 `/v1`、`/v2`,版本管理就算做到位了,旧版本可以随时下架。
正解版本管理的核心是向后兼容和平滑迁移。像餐厅改菜单时,老顾客手里的旧套餐券还得能用。除了 URL 前缀,还要定义弃用周期、用 Deprecation 头提醒客户端、提供迁移文档,而不是突然断供。
为何常见:团队内部接口使用者少时,变更成本低,容易忽略外部消费者的升级成本。
面试题
Q1. 在 RESTful 风格中,下列哪个 URL 最符合资源命名约定?
- A./api/users/42← 正确答案
- B./api/getUser/42
- C./api/User/42
- D./api/user/42
答案:A。REST 推荐用复数名词表示资源集合,全部小写。/api/users/42 用复数 users 表示集合,42 是资源 ID,简洁规范;B 含动词,C 大小写不统一,D 用单数不够规范。
Q2. 关于 GET 方法,下列说法正确的是?
- A.创建新资源
- B.安全且幂等← 正确答案
- C.只负责删除
- D.必须返回页面
答案:B。GET 用于获取资源,不改变服务端状态,因此是“安全”的;重复执行多次结果一致,因此也是“幂等”的。A 是 POST 的职责,C 是 DELETE 的职责,D 过于绝对。
Q3. 关于幂等性,下列说法正确的是?
- A.POST 保证幂等,重复提交无影响
- B.PUT 保证幂等,重复执行结果一致← 正确答案
- C.GET 不保证幂等,可能改变数据
- D.DELETE 不保证幂等,二次必报错
答案:B。PUT 是整体替换,重复执行多次资源状态不变,是幂等的。POST 通常不幂等,GET 是幂等的,DELETE 虽可能第二次返回 404,但语义上可以视为幂等,因为最终状态不变。
Q4. 资源不存在时,服务端最应返回哪个状态码?
- A.400 参数有误
- B.401 身份未认证
- C.404 资源不存在← 正确答案
- D.500 服务器异常
答案:C。404 Not Found 专门表示目标资源不存在。400 表示请求格式错误,401 表示未认证,500 表示服务器内部错误,都不符合“资源不存在”的语义。
Q5. 分页响应中,Link: <...?offset=40&limit=20>; rel="next" 的主要作用是?
- A.声明接口版本
- B.指向下一页地址← 正确答案
- C.执行用户认证
- D.标记资源删除
答案:B。Link 头里的 rel="next" 明确告诉客户端“下一页去哪里取”,客户端无需自己拼接 URL。版本、认证和删除都不属于分页 Link 的职责。
Q6. 下列哪个头信息常用于 Bearer Token 认证?
- A.认证请求头携带访问令牌← 正确答案
- B.内容类型指定为 JSON 格式
- C.自定义版本头声明版本号
- D.请求头声明期望返回格式
答案:A。Bearer Token 应放在 Authorization 请求头中,格式为 Authorization: Bearer <token>。其他选项分别涉及内容类型、版本和 Accept 协商,与认证无关。
Q7. 想要只更新资源中的一个字段,最合适的语义是?
- A.整体替换资源
- B.局部更新资源← 正确答案
- C.创建新资源
- D.删除目标资源
答案:B。PATCH 专用于局部更新,只修改传入字段。PUT 是整体替换,POST 是创建,DELETE 是删除。选对语义才能避免误清空其他字段。
Q8. 下列哪些做法符合 REST 最佳实践?
- A.用复数名词表示资源集合← 正确答案
- B.用状态码表达请求结果← 正确答案
- C.在 URL 中使用动词表示操作
- D.提供分页参数限制返回量← 正确答案
答案:ABD。复数名词、状态码语义、分页都是 REST 推荐做法。把动词写进 URL 是典型反模式,动作应交由 HTTP 方法表达。
Q9. 关于 API 版本管理,正确的策略有?
- A.在 URL 前缀中使用 v1/v2← 正确答案
- B.旧版本不通知直接下线
- C.明确弃用周期与迁移文档← 正确答案
- D.所有接口永远只保留一份
答案:AC。URL 前缀是最直观的版本策略,同时要定义弃用周期和迁移文档。B 的做法会伤害已有用户,D 则完全否定了版本管理的必要性。
Q10. 以下哪些内容属于 OpenAPI 规范描述的范围?
- A.接口路径与参数定义← 正确答案
- B.请求响应数据结构← 正确答案
- C.认证与授权方案说明← 正确答案
- D.服务器机房温度监控
答案:ABC。OpenAPI 用 paths 描述路径参数,用 schemas 描述数据结构,用 security 描述认证授权方案。机房温度属于运维监控,不在 API 契约范围内。