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 文件,通过本地文件引用或构建工具合并。

复用最佳实践:

  1. 统一入口: 所有共享模型定义在 components/schemas 下
  2. 命名规范: 使用 PascalCase 命名,如 UserProfile、OrderItem
  3. 避免深层嵌套: 将复杂对象拆分为多个独立 Schema,通过 $ref 组合
  4. 版本管理: 外部引用的 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 复用之间的层级关系:

请求体数据模型层级关系 requestBody 对象 description required (boolean) content (必填) Media Type 映射 (Map) application/json application/xml multipart/form-data schema: Schema 对象(type / properties / required / items / enum / format ...) $ref: '#/components/schemas/ModelName' 复用 说明:content 映射是 requestBody 的唯一必填属性,每个 Media Type 下必须包含一个 schema 定义

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。这确保了数据模型的可追溯性与一致性。