4. 请求体与数据模型
在 RESTful API 设计中,请求体(Request Body)是客户端向服务端传递结构化数据的核心通道。OpenAPI 3.0 通过 requestBody 对象统一了 POST、PUT、PATCH 等方法的载荷描述,并借助 Media Type 与 Schema 对象实现数据模型的精确建模与复用。本章将深入解析这三个层次的定义方法与最佳实践。
4.1 requestBody 对象结构
requestBody 对象位于路径操作(Path Item)的 requestBody 字段下,包含三个核心属性:
| 属性 | 类型 | 说明 | 必填 |
|---|---|---|---|
description |
string | 请求体的简要描述,用于文档生成 | 否 |
required |
boolean | 标识请求体是否为必须。默认 false | 否 |
content |
Map[Media Type, Media Type Object] | 包含一个或多个媒体类型及其对应的 Schema | 是 |
关键原则: 即使请求体只有一个媒体类型,也必须使用
content 映射包裹。这是 OpenAPI 3.0 与 Swagger 2.0 的重要区别。
以下是一个完整的 requestBody 定义示例:
paths:
/users:
post:
summary: 创建新用户
requestBody:
description: 用户注册信息
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
email:
type: string
age:
type: integer
required:
- username
- email
responses:
'201':
description: 用户创建成功
4.2 Media Type 与多格式支持
Media Type(媒体类型)是 content 映射的键,用于声明请求体支持的数据格式。常见的媒体类型包括:
- application/json — JSON 格式,最常用
- application/xml — XML 格式,遗留系统或特定领域
- application/x-www-form-urlencoded — 表单提交
- multipart/form-data — 文件上传或混合数据
- text/plain — 纯文本
- application/octet-stream — 二进制流
一个 API 端点可以同时支持多种媒体类型,客户端通过 Content-Type 头选择:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
metadata:
$ref: '#/components/schemas/UserMeta'
实践建议: 对于新系统,优先支持
application/json;仅在需要文件上传时使用 multipart/form-data;避免同时支持过多媒体类型,以降低服务端解析复杂度。
4.3 Schema 对象与数据模型定义
Schema 对象是 OpenAPI 数据建模的核心,它基于 JSON Schema 规范(draft-07 子集)来描述数据结构。Schema 对象支持以下核心关键字:
| 关键字 | 用途 | 示例 |
|---|---|---|
type |
定义数据类型 | string, integer, object, array |
properties |
对象类型的属性列表 | properties: { name: { type: string } } |
required |
必填属性数组 | required: [name, email] |
items |
数组元素的 Schema | items: { $ref: '#/...' } |
enum |
枚举值列表 | enum: [active, inactive] |
format |
扩展格式约束 | format: email, format: date-time |
nullable |
是否允许 null | nullable: true |
example |
示例值 | example: "张三" |
以下是一个包含嵌套对象与数组的 Schema 示例:
components:
schemas:
Order:
type: object
properties:
id:
type: integer
format: int64
example: 1001
customer:
$ref: '#/components/schemas/Customer'
items:
type: array
items:
$ref: '#/components/schemas/OrderItem'
totalAmount:
type: number
format: float
nullable: false
status:
type: string
enum: [pending, confirmed, shipped, delivered, cancelled]
required:
- customer
- items
- totalAmount
4.4 数据模型复用:$ref 与 components/schemas
在大型 API 中,同一数据模型(如 User、Order、Product)会出现在多个端点的请求体与响应体中。OpenAPI 通过 $ref 引用机制和 components/schemas 区域实现模型复用,避免重复定义。
$ref 引用语法:
$ref: '#/components/schemas/User'— 引用本地组件$ref: 'common.yaml#/components/schemas/Address'— 引用外部文件$ref: 'https://api.example.com/schemas/product.json'— 引用远程 URL
注意: 远程引用会增加 API 文档的加载依赖和网络延迟。在团队实践中,建议将共享模型抽取为独立的 YAML/JSON 文件,通过本地文件引用或构建工具合并。
复用最佳实践:
- 统一入口: 所有共享模型定义在
components/schemas下 - 命名规范: 使用 PascalCase 命名,如
UserProfile、OrderItem - 避免深层嵌套: 将复杂对象拆分为多个独立 Schema,通过
$ref组合 - 版本管理: 外部引用的 Schema 文件应纳入版本控制
以下展示一个典型的模型复用结构:
# components/schemas 区域
components:
schemas:
Address:
type: object
properties:
street: { type: string }
city: { type: string }
zipCode: { type: string }
required: [street, city, zipCode]
User:
type: object
properties:
id: { type: integer }
name: { type: string }
address: { $ref: '#/components/schemas/Address' }
required: [id, name]
Order:
type: object
properties:
orderId: { type: integer }
user: { $ref: '#/components/schemas/User' }
shippingAddress: { $ref: '#/components/schemas/Address' }
required: [orderId, user]
# 在路径中复用
paths:
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/orders:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
4.5 请求体数据模型关系图
下图展示了 requestBody、Media Type、Schema 对象与 $ref 复用之间的层级关系:
4.6 请求体定义常见陷阱
陷阱 1: 在 GET 或 DELETE 操作中定义
requestBody。OpenAPI 规范允许但 HTTP 语义不推荐,多数客户端/服务器实现会忽略。
陷阱 2: 多个 Media Type 使用不同的 Schema 结构。这会导致客户端困惑,应尽量保持各格式的 Schema 一致。
陷阱 3: 在
$ref 引用旁使用其他属性。OpenAPI 规范规定 $ref 所在对象不能包含兄弟属性,如需覆盖需使用 allOf 组合。
正确的覆盖方式:
# 错误:$ref 旁不能有 description
schema:
$ref: '#/components/schemas/User'
description: 用户信息 # 无效
# 正确:使用 allOf 组合
schema:
allOf:
- $ref: '#/components/schemas/User'
- description: 用户信息
4.7 实战:用户注册接口完整定义
综合以上知识点,以下是一个用户注册接口的完整 OpenAPI 3.0 定义:
openapi: 3.0.3
info:
title: 用户服务 API
version: 1.0.0
paths:
/api/v1/users/register:
post:
summary: 用户注册
tags: [用户管理]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterRequest'
application/xml:
schema:
$ref: '#/components/schemas/RegisterRequest'
responses:
'201':
description: 注册成功
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfile'
'400':
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: 用户已存在
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
RegisterRequest:
type: object
properties:
username:
type: string
minLength: 3
maxLength: 50
example: zhangsan
email:
type: string
format: email
example: zhangsan@example.com
password:
type: string
minLength: 8
maxLength: 128
format: password
profile:
$ref: '#/components/schemas/UserProfile'
required:
- username
- email
- password
UserProfile:
type: object
properties:
firstName:
type: string
example: 三
lastName:
type: string
example: 张
age:
type: integer
minimum: 0
maximum: 150
phone:
type: string
pattern: '^1[3-9]\d{9}$'
ErrorResponse:
type: object
properties:
code:
type: integer
example: 40001
message:
type: string
example: 邮箱已被注册
timestamp:
type: string
format: date-time
required:
- code
- message
治理要点: 在 API 治理实践中,应要求所有 POST/PUT/PATCH 操作的
requestBody 必须引用 components/schemas 中定义的模型,禁止在路径中内联定义复杂 Schema。这确保了数据模型的可追溯性与一致性。