主题
06 · 请求体 & 响应体 & Schema 复用:OpenAPI 的灵魂
生活类比:
requestBody= 你点单时填写的外卖订单纸条("汉堡 1 份、薯条 1 份、不要番茄、备注:少冰")responses= 厨房给你的出餐回执("打包好了,编号 042,5 分钟取餐")components.schemas= 大堂墙上贴的通用配料表(番茄酱、生菜、面包,所有汉堡都能引用)
1. 请求体 requestBody
注意:OpenAPI 3.0 把请求体从 parameters 里抽出来,单独作为 requestBody:
yaml
paths:
/users:
post:
summary: 创建用户
requestBody:
required: true # 是否必填
description: 用户基本信息
content: # 不同 MIME 类型
application/json:
schema: # 请求体的结构
type: object
properties:
name: { type: string }
email: { type: string, format: email }
required: [name, email]
example: # 单个示例
name: Alice
email: alice@example.com
responses:
'201': { description: Created }1.1 多 MIME 类型同存
yaml
requestBody:
content:
application/json: # JSON
schema: { $ref: '#/components/schemas/User' }
application/xml: # XML
schema: { $ref: '#/components/schemas/User' }
multipart/form-data: # 表单
schema:
type: object
properties:
name: { type: string }
avatar: { type: string, format: binary } # 文件大多数 REST API 只用
application/json,但文件上传必须用multipart/form-data。
1.2 文件上传
yaml
paths:
/upload:
post:
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary # ← 重点
description:
type: string
required: [file]
responses:
'200': { description: 上传成功 }1.3 application/x-www-form-urlencoded(表单提交)
yaml
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
username: { type: string }
password: { type: string, format: password }
required: [username, password]适合纯表单登录场景。前端用
URLSearchParams发出,后端@FormData接。
2. 响应 responses
yaml
responses:
'200': # 状态码(用字符串)
description: 成功 # 必填
headers: # 响应头
X-RateLimit-Remaining:
schema: { type: integer }
description: 剩余调用次数
content: # 响应体
application/json:
schema:
$ref: '#/components/schemas/User'
examples: # 多组示例
alice:
value: { id: 1, name: Alice }
bob:
value: { id: 2, name: Bob }
'404':
description: 资源不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
default: # 其他所有未声明的状态码
description: 未预期的错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'2.1 状态码必须是字符串
yaml
# ✗ 错(yaml 里 200 是数字)
responses:
200:
description: OK
# ✓ 对
responses:
'200':
description: OK2.2 常见状态码 Cheat Sheet
| 码 | 含义 | 用法 |
|---|---|---|
| 200 | OK | GET / PUT / PATCH 成功 |
| 201 | Created | POST 创建成功 |
| 204 | No Content | DELETE 成功 / PUT 不返回内容 |
| 301 | Moved Permanently | 资源永久迁移 |
| 304 | Not Modified | 缓存未变(条件请求) |
| 400 | Bad Request | 参数错误 |
| 401 | Unauthorized | 未登录 / token 无效 |
| 403 | Forbidden | 已登录但没权限 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 业务冲突(如重复创建) |
| 422 | Unprocessable Entity | 参数格式对,但业务校验不通过 |
| 429 | Too Many Requests | 限流 |
| 500 | Internal Server Error | 后端崩了 |
| 502 | Bad Gateway | 网关下游挂了 |
| 503 | Service Unavailable | 维护中 / 过载 |
| 504 | Gateway Timeout | 网关超时 |
3. Schema 详解
schema 描述 "这个 JSON 长什么样"。语法是 JSON Schema 的子集(OpenAPI 3.1 完全兼容 2020-12 版)。
3.1 对象 schema
yaml
schema:
type: object
properties:
id:
type: integer
readOnly: true # 只读字段,POST 时不让传
name:
type: string
minLength: 1
maxLength: 50
age:
type: integer
minimum: 0
maximum: 150
email:
type: string
format: email
role:
type: string
enum: [admin, user, guest]
default: user
isActive:
type: boolean
default: true
createdAt:
type: string
format: date-time
readOnly: true
password:
type: string
writeOnly: true # 只写字段,响应时不返回
required: [name, email]
additionalProperties: false # 不允许额外字段3.2 数组 schema
yaml
schema:
type: array
items: # 每一项的 schema
$ref: '#/components/schemas/User'
minItems: 0
maxItems: 100
uniqueItems: true3.3 嵌套对象
yaml
schema:
type: object
properties:
user:
type: object # 内嵌定义
properties:
name: { type: string }
address:
$ref: '#/components/schemas/Address' # 引用
orders:
type: array
items:
$ref: '#/components/schemas/Order'3.4 nullable(OpenAPI 3.0)
yaml
phone:
type: string
nullable: true # 允许 null(OpenAPI 3.0 写法)OpenAPI 3.1 改用 type 数组:
yaml
phone:
type: [string, "null"] # OpenAPI 3.1 写法4. 复用 · components.schemas(最常用)
4.1 定义
yaml
components:
schemas:
User:
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string }
email: { type: string, format: email }
createdAt: { type: string, format: date-time, readOnly: true }
required: [name, email]
UserCreate: # 创建用的(不带 id 和 createdAt)
type: object
properties:
name: { type: string }
email: { type: string, format: email }
password: { type: string, format: password, minLength: 6 }
required: [name, email, password]
UserUpdate: # 更新用的(所有字段可选)
type: object
properties:
name: { type: string }
email: { type: string, format: email }
PageMeta:
type: object
properties:
total: { type: integer }
page: { type: integer }
size: { type: integer }
UserListResponse:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/User' }
meta:
$ref: '#/components/schemas/PageMeta'
Error:
type: object
properties:
code: { type: integer }
message: { type: string }
details: { type: object }
required: [code, message]4.2 引用
yaml
paths:
/users:
get:
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/UserListResponse' }
post:
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UserCreate' }
responses:
'201':
content:
application/json:
schema: { $ref: '#/components/schemas/User' }4.3 命名约定(业内通用)
| Schema 名 | 用途 |
|---|---|
User | 完整对象(一般是响应) |
UserCreate | 创建用(少了 id、时间戳) |
UserUpdate | 更新用(字段都可选) |
UserPatch | 局部更新 |
UserListResponse | 列表响应(含分页) |
Error | 错误响应 |
5. 组合:allOf / oneOf / anyOf / not
5.1 allOf — 继承 / 合并
yaml
components:
schemas:
Pet:
type: object
properties:
name: { type: string }
age: { type: integer }
required: [name]
Dog:
allOf: # 继承 Pet 的所有字段,再加自己的
- $ref: '#/components/schemas/Pet'
- type: object
properties:
breed: { type: string }
required: [breed]Dog 等价于:{ name, age, breed },必填 name + breed。
5.2 oneOf — 必须满足其中恰好一个
yaml
PaymentMethod:
oneOf:
- $ref: '#/components/schemas/CreditCard'
- $ref: '#/components/schemas/Alipay'
- $ref: '#/components/schemas/WeChatPay'
discriminator: # 用哪个字段区分
propertyName: type
mapping:
credit_card: '#/components/schemas/CreditCard'
alipay: '#/components/schemas/Alipay'
wechat: '#/components/schemas/WeChatPay'调用时,PaymentMethod 必须只能是其中之一(且 type 字段决定是哪个)。
5.3 anyOf — 满足至少一个(可以同时满足多个)
yaml
Search:
anyOf:
- type: object
properties:
keyword: { type: string }
- type: object
properties:
author: { type: string }请求体可以只有 keyword、只有 author、或者两个都有。
5.4 not
yaml
NonString:
not:
type: string # 不能是字符串
oneOf / anyOf / not在工程里用得不多,但allOf极其常用(继承 + 扩展)。
6. 实战 · 一份完整的"评论 API"
yaml
openapi: 3.0.3
info:
title: 评论 API
version: 1.0.0
paths:
/comments:
post:
operationId: createComment
summary: 发表评论
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CommentCreate' }
examples:
short:
value: { postId: 1, content: '👍' }
normal:
value: { postId: 1, content: '写得真好,学到了' }
responses:
'201':
description: 评论成功
content:
application/json:
schema: { $ref: '#/components/schemas/Comment' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
default:
$ref: '#/components/responses/Default'
/comments/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: integer }
get:
operationId: getComment
responses:
'200':
description: 评论详情
content:
application/json:
schema: { $ref: '#/components/schemas/Comment' }
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
Comment: # 完整评论
type: object
properties:
id: { type: integer, readOnly: true }
postId: { type: integer }
content: { type: string, minLength: 1, maxLength: 1000 }
authorId: { type: integer, readOnly: true }
createdAt: { type: string, format: date-time, readOnly: true }
replyCount: { type: integer, readOnly: true, default: 0 }
required: [id, postId, content, authorId, createdAt]
CommentCreate:
type: object
properties:
postId: { type: integer }
content: { type: string, minLength: 1, maxLength: 1000 }
required: [postId, content]
Error:
type: object
properties:
code: { type: integer }
message: { type: string }
traceId: { type: string }
required: [code, message]
responses:
BadRequest:
description: 参数错误
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { code: 400, message: 'content 不能为空' }
Unauthorized:
description: 未登录
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: 评论不存在
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
Default:
description: 未知错误
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }这种全 ref写法看起来啰嗦,但接口一多收益翻倍——加字段只需改 schemas 一个地方。
7. 常见坑
7.1 状态码必须用字符串
见 §2.1。
7.2 readOnly / writeOnly
yaml
User:
type: object
properties:
id: { type: integer, readOnly: true } # POST 不传,GET 才返
password: { type: string, writeOnly: true } # POST 必传,GET 不返配合代码生成器,能自动从前端类型里把 readOnly 字段移除,不让前端误传。
7.3 example vs examples
yaml
# 单个示例
example:
name: Alice
# 多个示例(更专业,UI 显示下拉选择)
examples:
alice:
summary: 普通用户
value: { name: Alice }
bob:
summary: 管理员
value: { name: Bob, role: admin }注意:
example写在schema里也行,写在content.application/json下面也行;examples只能写在 content 下面。
7.4 嵌套引用 $ref 不能跟其他字段并列
yaml
# ✗ 错
schema:
$ref: '#/components/schemas/User'
description: 用户对象 # 会被忽略!
# ✓ 对:用 allOf 包一层
schema:
allOf:
- $ref: '#/components/schemas/User'
description: 用户对象8. 章末面试题速览
- OpenAPI 3.0 为什么把 requestBody 单独抽出来? → 2.0 把请求体塞在 parameters 里很怪(不是真的"参数"),3.0 独立后能更清晰描述多 MIME 类型、复用 requestBody。
allOf和oneOf的区别? → allOf = 全部都满足(合并 / 继承),oneOf = 恰好满足其中一个(多态)。readOnly和writeOnly在生成代码时有什么用? → readOnly 字段不出现在创建 / 更新的 body 类型里;writeOnly 字段不出现在响应类型里。代码更安全。
🎬 可视化演示
下方 demo 是一个 "Schema 编辑器"——左边写字段,右边自动生成 yaml 和 JSON 示例。
→ 打开 06_request_body/demo.html
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗