想象你走进一家生意火爆的餐厅:菜单编号统一、服务员动作规范、上菜小票一目了然,加菜、退菜、换菜都有固定术语。如果每个服务员各有暗号,菜单乱写,厨房动不动上错菜,这家餐厅很快会倒闭。RESTful API 设计就是给 Web 服务立一套“餐厅规矩”:资源是菜单上的菜品,URI 是菜品编号,HTTP 方法是点单动作,状态码是回执小票,版本管理是菜单改版,Token 是会员卡,OpenAPI 是官方菜单。掌握了这套规矩,你设计的接口就能让任何客户端不费力气地点到想要的“菜”。

岗位关联

学会 RESTful API 设计,是 AI 工程师把模型能力封装成标准服务、与前后端和微服务高效协作的必备基本功。

目录
  1. 可视化演示
  2. 资源命名约定
  3. HTTP方法语义
  4. 状态码规范
  5. 查询与分页
  6. 版本管理
  7. 认证与授权
  8. 文档与反模式
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态展示对称加密的完整流程:密钥生成、明文加密、密文传输与解密还原,直观呈现加密保护数据安全的核心机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

对称加密流程 发送方使用密钥加密明文生成密文 接收方使用相同密钥解密还原明文 E(K,P)=C 加密函数生成密文

资源命名约定

餐厅菜单上的每道菜都有固定编号,比如“凉菜-03”,而不是“好吃不贵的那盘”。如果编号随意,顾客点菜全凭暗号。RESTful API 也一样,资源命名是客户端与服务端约定的“菜品编号”。一个清晰稳定的 URL,能让调用方不看文档也猜出大概含义。

在 REST 中,资源是 API 管理的核心对象,URI 是资源的地址。最佳实践是:用名词复数表示集合,例如 /users 表示用户集合,/users/{id} 表示单个用户;全部使用小写,多词之间用连字符 -,比如 /order-items,避免下划线或驼峰;资源的嵌套层级不要超过三层,如 /users/{id}/orders,更深的关系尽量用查询参数表达。

好的资源命名,就像餐厅编号规则一样,让每个地址都自带“可读性”。客户端看到 /users/42/orders,大脑立刻能翻译成“用户 42 的订单列表”,而不是一串莫名其妙的路径。

URI=/api/v1/{resource}/{id}/{subresource}\text{URI} = \text{/api/v1/}\{\text{resource}\}/\{\text{id}\}/\{\text{subresource}\}
通用资源地址模板:集合、唯一标识、子资源三个层次,一目了然。
  1. 固定前缀/api/v1 是接口的统一入口,相当于餐厅的门牌号和楼层的固定前缀。
    /api/v1\text{/api/v1}
  2. 集合名词\{\text{resource}\} 表示一类菜品,如 “用户” 对应 users,使用复数名词。
    {resource}=users\{\text{resource}\} = \text{users}
  3. 唯一标识\{\text{id}\} 是某个具体资源的编号,如同一桌客人的桌号。
    {id}=42\{\text{id}\} = 42
  4. 从属资源\{\text{subresource}\} 表示主资源下的子资源,如用户下的订单。
    {subresource}=orders\{\text{subresource}\} = \text{orders}

HTTP方法语义

顾客不会对着服务员喊“给我上一道青椒肉丝”就完了,服务员需要知道你是想“看看有没有这道菜”、“点这道菜”、“换掉整桌菜”,还是“退掉菜”。HTTP 方法就是这套点单动作:GET、POST、PUT、PATCH、DELETE,各自有明确的语义。

把这些语义固定下来,是 REST 设计最重要的约定。GET 用于获取资源,不改变服务端状态;POST 用于创建新资源;PUT 用于整体替换一个资源;PATCH 用于局部更新;DELETE 用于删除资源。GET 是“安全”方法,因为它只读;PUT 和 DELETE 是“幂等”方法,因为重复执行多次,结果与执行一次一致。POST 和 PATCH 通常不保证幂等,重复提交可能产生多个资源或叠加变更。

这就像餐厅的服务规范里,“点菜”“换菜”“退菜”各有固定术语,所有服务员按同一套黑话沟通。客户端与服务器之间有了这套语义,即使跨团队合作,也不会把“更新”和“创建”混为一谈。

Idempotent={GET,PUT,DELETE},Safe={GET}\text{Idempotent} = \{\text{GET}, \text{PUT}, \text{DELETE}\}, \quad \text{Safe} = \{\text{GET}\}
幂等集合表示这些方法反复调用结果稳定;安全集合表示方法不会改动资源状态。
  1. 幂等集合\text{Idempotent} 表示“可以重复点同一份菜而不会出错”的方法集合。
    Idempotent={GET,PUT,DELETE}\text{Idempotent} = \{\text{GET}, \text{PUT}, \text{DELETE}\}
  2. GET 方法获取数据,像“看一眼菜单”,不会改变任何东西。
    GET∈Idempotent\text{GET} \in \text{Idempotent}
  3. PUT 方法把资源整体替换,重复替换多少次结果都一样。
    PUT∈Idempotent\text{PUT} \in \text{Idempotent}
  4. 安全方法\text{Safe} 表示只读操作,不会让厨房多出一道菜。
    Safe={GET}\text{Safe} = \{\text{GET}\}

状态码规范

上菜后,顾客会拿到一张小票: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 可以有限重试。这就是状态码的价值。

family(s)=⌊s100⌋,s∈{200,201,400,401,403,404,429,500,503}\text{family}(s) = \left\lfloor \frac{s}{100} \right\rfloor, \quad s \in \{200, 201, 400, 401, 403, 404, 429, 500, 503\}
把状态码除以 100 取整数,就能快速判断这是哪一类回执。
  1. 类别函数\text{family}(s) 表示状态码所属的大类,如同小票上的首位数字。
    family(s)=⌊s100⌋\text{family}(s) = \left\lfloor \frac{s}{100} \right\rfloor
  2. 具体状态码s 是服务端返回的精确值,例如 404 或 500。
    s∈{200,201,400,401,403,404,429,500,503}s \in \{200, 201, 400, 401, 403, 404, 429, 500, 503\}
  3. 整除取类\left\lfloor \cdot \right\rfloor 的意思是向下取整,404 除以 100 得到 4。
    family(404)=4\text{family}(404) = 4

查询与分页

当菜单有几十种菜时,顾客不会从头翻到尾,而是会问:“有素的吗?价格 30 元以内有哪些?”服务员根据筛选条件快速缩小范围,再一页一页展示。API 的查询、过滤、排序、分页,就是这套“点菜筛选”工具。

查询集合资源时,应该用 query 参数做过滤和排序,而不是把这些条件塞进路径。例如 GET /api/v1/dishes?category=vegetarian&sort=price&limit=10&offset=20。常见的分页参数是 limit 和 offset:limit 表示每页返回多少条,offset 表示跳过的条数。响应中可以返回总条数,并利用 Link 头字段提供 next 和 prev 链接,客户端不需要自己拼接 URL。

分页让接口在大数据量下像翻菜单一样轻松。更重要的是,返回标准化的分页链接,就像菜单底部写清楚“下一页”,客户端不用猜测翻页规则。

page=⌊offsetlimit⌋+1\text{page} = \left\lfloor \frac{\text{offset}}{\text{limit}} \right\rfloor + 1
页码等于偏移量除以每页条数后向下取整再加 1。
  1. 偏移量\text{offset} 是已经跳过的条目数,比如跳过前 20 条。
    offset=20\text{offset} = 20
  2. 每页条数\text{limit} 是每页展示多少条,比如每页 10 条。
    limit=10\text{limit} = 10
  3. 计算跳过的页数\left\lfloor \frac{\text{offset}}{\text{limit}} \right\rfloor 得到已经越过的完整页数。
    ⌊2010⌋=2\left\lfloor \frac{20}{10} \right\rfloor = 2
  4. 页码换算加 1 把从 0 开始的偏移量换算成人类友好的页码。
    page=2+1=3\text{page} = 2 + 1 = 3

版本管理

餐厅经营几年后要升级菜单:改良配方、调整价格,但老顾客还习惯旧套餐。聪明的餐厅不会直接撕掉旧菜单,而是在一段时间内新旧菜单并存,并标明“经典款”和“新款”。API 版本管理也是同样的道理。

REST API 一旦被外部使用,就不能随意破坏兼容性。最常用的版本策略是 URL 路径前缀,例如 /api/v1/... 和 /api/v2/...,直观、方便路由和缓存。另一种方式是使用自定义头或 Accept 头,例如 Accept: application/vnd.api+json;version=1,但这种方式不如 URL 直观,排障时也容易被忽略。版本管理不只是加一个数字,还要有兼容策略和弃用周期:新版本上线后,旧版本应至少并行运行一段时间,通过 Deprecation 响应头提醒客户端迁移,并给出清晰的迁移文档。

版本管理像菜单改版一样,要照顾老顾客。保留“经典套餐”,提前告知停售日期,用户才愿意继续信任这家店,而不是被迫匆忙迁移。

V={v1,v2,…,vn},path=/api/vi/resourceV = \{v_1, v_2, \dots, v_n\}, \quad \text{path} = \text{/api/} v_i / \text{resource}
版本是一组显式编号的集合,路径前缀中的 v_i 用来选择其中一版。
  1. 版本集合V = \{v_1, v_2, \dots, v_n\} 表示系统同时维护的所有版本。
    V={v1,v2,…,vn}V = \{v_1, v_2, \dots, v_n\}
  2. 版本编号v_i 是第 i 个版本的显式标识,如 v1。
    vi=v1v_i = v_1
  3. 路径前缀\text{path} = \text{/api/} v_i / \text{resource} 表示 URL 以版本号开头,路由时按版本分发。
    path=/api/v1/resource\text{path} = \text{/api/v1/resource}

认证与授权

高档餐厅不是谁都能直接进后厨。顾客需要先出示会员卡证明身份,服务员再根据会员等级决定你能进包厢还是只能在堂食区。这里的会员卡就是 Token,进包厢的权限就是 Scope。

认证(Authentication)回答“你是谁”,授权(Authorization)回答“你能做什么”。REST API 通常把 Token 放在 Authorization: Bearer <token> 请求头中传递,避免把 Token 写在 URL 里被日志记录。更复杂的场景使用 OAuth2 授权码流程:用户同意后,应用获得一个有限权限的访问令牌。设计原则是默认禁止匿名访问,并遵循权限最小化:给令牌只分配 read、write 等必要权限,而不是一把万能钥匙。

会员卡让顾客畅通无阻,但后厨重地只有主厨助理能进。Token 同样让请求带着身份和权限抵达资源,既方便又安全。

token↦Scope⊆{read,write,admin}\text{token} \mapsto \text{Scope} \subseteq \{\text{read}, \text{write}, \text{admin}\}
Token 可以映射到一个权限集合,集合中的元素代表该令牌拥有的访问范围。
  1. 令牌映射\mapsto 表示“拿令牌换权限”,服务器验证 Token 后得到对应的 Scope。
    token↦Scope\text{token} \mapsto \text{Scope}
  2. 权限子集\subseteq 表示权限范围是全集中的一个子集,不能超出预定义范围。
    Scope⊆{read,write,admin}\text{Scope} \subseteq \{\text{read}, \text{write}, \text{admin}\}
  3. 最小权限默认只给 read,需要写入时才分配 write。
    Scope={read}\text{Scope} = \{\text{read}\}

文档与反模式

一家餐厅若没有统一菜单,顾客只能靠口耳相传,点单全靠缘分。OpenAPI 就是 RESTful API 的“官方菜单”,它用结构化文件描述每个接口的路径、参数、响应格式和安全要求,甚至可以自动生成 SDK 和在线文档。

OpenAPI 规范的核心三要素是:paths 描述所有接口路径和操作,schemas 描述请求与响应数据结构,security 描述认证与授权方案。采用“先写文档、再写代码”的 Design First 方式,可以让前后端在开工前对齐契约。常见的反模式包括:URL 中使用动词,比如 /createUser;所有请求一律返回 200,只在 body 里写业务码;资源嵌套超过三层;没有分页;状态码语义混乱。这些都会让 API 像一本没有目录的菜谱,越用越痛苦。

有了“官方菜单”,顾客、服务员和后厨不再鸡同鸭讲。OpenAPI 文档就是团队协作的通用语言,也是自动化工具落地的基础。

OpenAPI={paths,schemas,security}\text{OpenAPI} = \{\text{paths}, \text{schemas}, \text{security}\}
OpenAPI 文档由路径、数据结构和安全方案三大部分组成。
  1. 路径集合\text{paths} 列出“有哪些菜”,即接口路径和可用的 HTTP 方法。
    paths={GET /users,POST /users}\text{paths} = \{\text{GET /users}, \text{POST /users}\}
  2. 数据模式\text{schemas} 定义“菜的用料”,即请求和响应体的字段结构。
    schemas={User,Order}\text{schemas} = \{\text{User}, \text{Order}\}
  3. 安全方案\text{security} 说明“谁可以进后厨”,即认证和授权方式。
    security={BearerToken,OAuth2}\text{security} = \{\text{BearerToken}, \text{OAuth2}\}

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 演示

python37 行
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)
目的:一个轻量级 URI 命名检查器,用来在代码评审中自动发现“驼峰命名”“动词 URL”“路径过深”等 RESTful 反模式。
  1. self.resource_re = re.compile(r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$")定义资源名的合法格式:小写字母开头,只允许小写字母、数字和连字符。
  2. if seg.isdigit() or seg.startswith("{"):跳过路径中的数字 ID 和模板参数,比如 42 和 {id}。
  3. if not self.resource_re.fullmatch(seg):检查路径片段是否完全匹配规范,驼峰命名 userOrders 会被判定为非法。
  4. errors.append("segment '{}' should use lowercase and hyphens".format(seg))记录第一个错误:建议改为小写连字符格式。
  5. word = seg.lower().replace("-", "")把片段归一化,方便识别 createOrder 这类动词开头。
  6. if any(word.startswith(k) for k in self.action_keywords):发现 create、delete、get、list 等动词时给出反模式提示。
  7. 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 最符合资源命名约定?

单选题 · easy

  • 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 方法,下列说法正确的是?

单选题 · easy

  • A.创建新资源
  • B.安全且幂等← 正确答案
  • C.只负责删除
  • D.必须返回页面

答案:B。GET 用于获取资源,不改变服务端状态,因此是“安全”的;重复执行多次结果一致,因此也是“幂等”的。A 是 POST 的职责,C 是 DELETE 的职责,D 过于绝对。

Q3. 关于幂等性,下列说法正确的是?

单选题 · easy

  • A.POST 保证幂等,重复提交无影响
  • B.PUT 保证幂等,重复执行结果一致← 正确答案
  • C.GET 不保证幂等,可能改变数据
  • D.DELETE 不保证幂等,二次必报错

答案:B。PUT 是整体替换,重复执行多次资源状态不变,是幂等的。POST 通常不幂等,GET 是幂等的,DELETE 虽可能第二次返回 404,但语义上可以视为幂等,因为最终状态不变。

Q4. 资源不存在时,服务端最应返回哪个状态码?

单选题 · mid

  • A.400 参数有误
  • B.401 身份未认证
  • C.404 资源不存在← 正确答案
  • D.500 服务器异常

答案:C。404 Not Found 专门表示目标资源不存在。400 表示请求格式错误,401 表示未认证,500 表示服务器内部错误,都不符合“资源不存在”的语义。

Q5. 分页响应中,Link: <...?offset=40&limit=20>; rel="next" 的主要作用是?

单选题 · mid

  • A.声明接口版本
  • B.指向下一页地址← 正确答案
  • C.执行用户认证
  • D.标记资源删除

答案:B。Link 头里的 rel="next" 明确告诉客户端“下一页去哪里取”,客户端无需自己拼接 URL。版本、认证和删除都不属于分页 Link 的职责。

Q6. 下列哪个头信息常用于 Bearer Token 认证?

单选题 · mid

  • A.认证请求头携带访问令牌← 正确答案
  • B.内容类型指定为 JSON 格式
  • C.自定义版本头声明版本号
  • D.请求头声明期望返回格式

答案:A。Bearer Token 应放在 Authorization 请求头中,格式为 Authorization: Bearer <token>。其他选项分别涉及内容类型、版本和 Accept 协商,与认证无关。

Q7. 想要只更新资源中的一个字段,最合适的语义是?

单选题 · mid

  • A.整体替换资源
  • B.局部更新资源← 正确答案
  • C.创建新资源
  • D.删除目标资源

答案:B。PATCH 专用于局部更新,只修改传入字段。PUT 是整体替换,POST 是创建,DELETE 是删除。选对语义才能避免误清空其他字段。

Q8. 下列哪些做法符合 REST 最佳实践?

多选题 · mid

  • A.用复数名词表示资源集合← 正确答案
  • B.用状态码表达请求结果← 正确答案
  • C.在 URL 中使用动词表示操作
  • D.提供分页参数限制返回量← 正确答案

答案:ABD。复数名词、状态码语义、分页都是 REST 推荐做法。把动词写进 URL 是典型反模式,动作应交由 HTTP 方法表达。

Q9. 关于 API 版本管理,正确的策略有?

多选题 · hard

  • A.在 URL 前缀中使用 v1/v2← 正确答案
  • B.旧版本不通知直接下线
  • C.明确弃用周期与迁移文档← 正确答案
  • D.所有接口永远只保留一份

答案:AC。URL 前缀是最直观的版本策略,同时要定义弃用周期和迁移文档。B 的做法会伤害已有用户,D 则完全否定了版本管理的必要性。

Q10. 以下哪些内容属于 OpenAPI 规范描述的范围?

多选题 · hard

  • A.接口路径与参数定义← 正确答案
  • B.请求响应数据结构← 正确答案
  • C.认证与授权方案说明← 正确答案
  • D.服务器机房温度监控

答案:ABC。OpenAPI 用 paths 描述路径参数,用 schemas 描述数据结构,用 security 描述认证授权方案。机房温度属于运维监控,不在 API 契约范围内。