主题
04 · 路径 & 操作:paths / operations
生活类比:
paths就是外卖菜单的菜品列表—— 路径 = 菜名("宫保鸡丁"), HTTP 方法 = 你跟服务员说的话("我点这个" / "把这个换成微辣" / "这个不要了"), 操作(operation)= 一道菜的完整描述(菜名 + 配料 + 做法 + 出餐时间)。
1. 一个 path 长什么样
yaml
paths:
/users: # ← 路径
get: # ← 方法(operation)
summary: 用户列表
tags: [user]
parameters: [...]
responses:
'200': {...}
post: # ← 同一路径可有多个方法
summary: 创建用户
requestBody: {...}
responses:
'201': {...}结构:
paths
└── /xxx (路径)
├── get (操作 1)
├── post (操作 2)
├── put (操作 3)
├── delete (操作 4)
└── parameters (路径级参数,所有 method 共享)2. HTTP 方法(七种)大全
| 方法 | 语义 | 幂等? | 安全? | 一般状态码 | 类比 |
|---|---|---|---|---|---|
| GET | 读取资源 | ✅ | ✅ | 200, 404 | 看菜单 |
| POST | 创建资源 / 任意动作 | ❌ | ❌ | 201, 200, 400 | 下单 |
| PUT | 完整更新(替换) | ✅ | ❌ | 200, 204 | 退掉重新下整桌 |
| PATCH | 局部更新(打补丁) | ❌* | ❌ | 200, 204 | 加一份米饭 |
| DELETE | 删除资源 | ✅ | ❌ | 204, 200 | 退单 |
| HEAD | 同 GET 但不返回 body | ✅ | ✅ | 200, 404 | 问"今天有这道菜吗" |
| OPTIONS | 询问支持的方法 / CORS 预检 | ✅ | ✅ | 204, 200 | 问"你们能做啥" |
幂等:同样请求执行 N 次效果跟 1 次一样。 安全:不会改服务端状态。 *PATCH 是否幂等取决于实现,比如
{ "+balance": 100 }不幂等,{ "name": "Alice" }幂等。
YAML 写法
yaml
paths:
/users:
get: {...}
post: {...}
/users/{id}:
get: {...}
put: {...}
patch: {...}
delete: {...}
head: {...}
options:{...}3. operation 对象的所有字段
每个方法(get/post/...)下面可以写:
yaml
get:
tags: [user] # 分组
summary: 获取用户列表 # 一句话简介(UI 折叠条上)
description: | # 详细描述(支持 Markdown)
返回当前用户列表,支持分页。
operationId: listUsers # 唯一 ID(代码生成时变成方法名)
parameters: [...] # 入参(query/path/header/cookie)
requestBody: {...} # 请求体(POST/PUT/PATCH 用)
responses: {...} # 响应(按状态码分组)
security: [] # 鉴权(覆盖全局)
deprecated: false # 是否废弃
servers: [...] # 这个接口单独的服务器(少用)
callbacks: {...} # 异步回调(webhook 风格)
externalDocs: {...} # 外链实战中最常用的是:
summary+tags+parameters+requestBody+responses。
4. summary vs description 怎么写
yaml
summary: 创建订单
description: |
## 业务规则
- 商品库存不足时返回 409
- 用户余额不足时返回 402
## 限流
每用户每秒最多 2 次
> 详见 [订单文档](https://wiki.example.com/order)UI 显示效果:
┌─────────────────────────────────────────────────────┐
│ POST /orders 创建订单 ← summary │
├─────────────────────────────────────────────────────┤
│ 业务规则 ← description │
│ • 商品库存不足时返回 409 │
│ ... │
└─────────────────────────────────────────────────────┘经验:summary 写动词短语("创建订单"),description 写业务规则。
5. operationId 不是装饰品
yaml
paths:
/users:
get:
operationId: listUsers # ← 见名知意
post:
operationId: createUser
/users/{id}:
get:
operationId: getUser
put:
operationId: updateUser
delete:
operationId: deleteUser代码生成时(第 8 章)会变成:
typescript
// 生成的前端 SDK
export class UserApi {
async listUsers(): Promise<User[]> { ... }
async createUser(user: User): Promise<User> { ... }
async getUser(id: number): Promise<User> { ... }
async updateUser(id: number, user: User): Promise<User> { ... }
async deleteUser(id: number): Promise<void> { ... }
}强烈推荐每个 operation 都写
operationId,否则代码生成器会用usersGet、usersIdGet这种丑名字。
命名约定:驼峰命名 + 动词在前。
- ✅
listUsers、getUserById、updateUserAvatar - ❌
users_get、POST_users、api_create
6. 路径参数 {xxx} 的写法
OpenAPI 规定路径参数用 {} 包起来:
yaml
paths:
/users/{id}:
get:
parameters:
- name: id # ← 必须跟 {} 里同名
in: path # ← 必须 path
required: true # ← 必须 true
schema:
type: integer
minimum: 1
responses:
'200': {...}
/users/{userId}/orders/{orderId}: # 多个路径参数
get:
parameters:
- name: userId
in: path
required: true
schema: { type: integer }
- name: orderId
in: path
required: true
schema: { type: integer }
responses:
'200': {...}第 5 章会专门讲所有参数类型。
7. 路径设计 RESTful 风格 · 经验法则
| 操作 | URL | 方法 |
|---|---|---|
| 列表 | /users | GET |
| 创建 | /users | POST |
| 单条 | /users/{id} | GET |
| 完整更新 | /users/{id} | PUT |
| 局部更新 | /users/{id} | PATCH |
| 删除 | /users/{id} | DELETE |
| 子资源列表 | /users/{id}/orders | GET |
| 子资源单条 | /users/{id}/orders/{orderId} | GET |
| 自定义动作(不能用 method 表达) | /users/{id}:reset-password | POST |
经验法则:
- 用名词复数,不用动词:✅
/users❌/getUsers - 用 HTTP 方法表达动作,不用 URL:✅
DELETE /users/1❌GET /users/delete?id=1 - 嵌套不超过 2 层:
/users/1/orders/5可以,/users/1/orders/5/items/3/refunds/2就该拆 - 实在不是 CRUD 的,加
:action:POST /orders/{id}:cancel、POST /users/{id}:reset-password
8. 同路径多方法 · parameters 复用
某些参数(如 id)所有方法都要用,可以提到 path 级别:
yaml
paths:
/users/{id}:
parameters: # ← 路径级,所有方法共享
- name: id
in: path
required: true
schema: { type: integer }
get:
summary: 获取用户
responses: {...}
put:
summary: 更新用户
requestBody: {...}
responses: {...}
delete:
summary: 删除用户
responses: {...}不用每个方法里都写一遍 id 参数。
9. deprecated · 废弃但暂不删除
yaml
paths:
/v1/users:
get:
deprecated: true # UI 会画删除线 + 警告
summary: "[废弃] 请用 /v2/users"
responses:
'200': {...}原则:废弃 ≠ 删除。先标
deprecated、推动下游迁移、等使用率到 0 再真删。
10. tags 多重分组
yaml
paths:
/admin/users:
get:
tags: [admin, user] # 同时归属两个分组
summary: 管理员查看用户
responses: {...}UI 上这个接口会同时出现在 "admin" 和 "user" 两个折叠组下。
11. 实战:一个完整的"博客 API" paths
yaml
openapi: 3.0.3
info:
title: 博客 API
version: 1.0.0
tags:
- name: post
- name: comment
- name: user
paths:
/posts:
get:
tags: [post]
operationId: listPosts
summary: 获取文章列表
parameters:
- name: page
in: query
schema: { type: integer, default: 1 }
- name: tag
in: query
schema: { type: string }
responses:
'200':
description: 文章列表
post:
tags: [post]
operationId: createPost
summary: 发表文章
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title: { type: string }
content: { type: string }
required: [title, content]
responses:
'201':
description: 创建成功
/posts/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
get:
tags: [post]
operationId: getPost
responses:
'200': { description: 文章详情 }
'404': { description: 找不到 }
put:
tags: [post]
operationId: updatePost
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title: { type: string }
content: { type: string }
responses:
'200': { description: 更新成功 }
delete:
tags: [post]
operationId: deletePost
responses:
'204': { description: 删除成功 }
/posts/{id}/comments:
get:
tags: [comment]
operationId: listComments
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200': { description: 评论列表 }
post:
tags: [comment]
operationId: createComment
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
content: { type: string }
responses:
'201': { description: 评论成功 }
/posts/{id}:like:
post:
tags: [post]
operationId: likePost
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200': { description: 点赞成功 }12. 章末面试题速览
- PUT 和 PATCH 的区别? → PUT 是替换整个资源(即使你只改一个字段,也要把所有字段传齐);PATCH 是只改你传的字段,其他保留。
- POST 和 PUT 都能创建资源,怎么选? → URL 里不知道 ID 时用 POST(服务端生成 ID);知道 ID 时用 PUT(客户端指定 ID,例:
PUT /users/u-123)。 operationId写不写有差别吗? → 写:代码生成器生成的方法名好看(createUser());不写:随机生成(usersPost())。强烈推荐写。
🎬 可视化演示
下方 demo 是一个 "HTTP 方法选择器"——选不同方法 + URL,看典型响应、状态码、是否幂等等。
→ 打开 04_paths_operations/demo.html
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗