Skip to content

OpenAPI 规范详解

掌握 OpenAPI 3.0 规范,实现高效的 API-First 开发

OpenAPI 规范概述

OpenAPI(原 Swagger)是一个用于描述 RESTful API 的规范,它使得人类和计算机都能够理解服务的功能,而无需访问源代码、文档或网络流量检查。

核心优势

1. 标准化的 API 描述

yaml
# 统一的、机器可读的 API 描述格式
openapi: 3.0.3
info:
  title: 用户管理 API
  version: 1.0.0
  description: |
    提供用户注册、认证、管理等功能的 RESTful API

2. 自动化代码生成

3. API-First 工作流支持

  • 设计优先: 在编码前完成 API 设计
  • 并行开发: 前后端基于规范独立开发
  • 契约测试: 确保实现符合规范
  • 文档同步: 代码和文档保持一致

OpenAPI 3.0 结构详解

1. 基本结构

yaml
# 必需:OpenAPI 版本声明
openapi: 3.0.3

# 必需:API 元信息
info:
  title: API 标题
  version: 1.0.0
  description: API 描述
  termsOfService: https://example.com/terms
  contact:
    name: API 支持团队
    email: api@example.com
    url: https://support.example.com
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html

# 可选:服务器配置
servers:
  - url: https://api.example.com/v1
    description: 生产环境
    variables:
      protocol:
        enum: [http, https]
        default: https
  - url: https://staging-api.example.com/v1
    description: 测试环境

# 可选:标签定义
tags:
  - name: users
    description: 用户管理相关接口
    externalDocs:
      description: 更多信息
      url: https://docs.example.com/users

# 必需:API 路径定义
paths: {}

# 可选:组件定义(推荐使用)
components:
  schemas: {}
  parameters: {}
  responses: {}
  examples: {}
  requestBodies: {}
  headers: {}
  securitySchemes: {}
  links: {}
  callbacks: {}

# 可选:安全配置
security:
  - bearerAuth: []

# 可选:外部文档
externalDocs:
  description: API 完整文档
  url: https://docs.example.com

2. 路径定义(Paths)

基本路径结构

yaml
paths:
  /users:
    # 路径级别的通用配置
    summary: 用户资源端点
    description: 管理系统用户的相关操作
    
    # GET 操作
    get:
      summary: 获取用户列表
      description: |
        获取系统中所有用户的列表,支持分页、排序和过滤。
        需要有效的认证令牌。
      operationId: listUsers  # 代码生成时的方法名
      tags: [users]
      
      # 请求参数
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/SizeParam'
        - name: status
          in: query
          description: 用户状态过滤
          required: false
          schema:
            type: string
            enum: [active, inactive, suspended]
          example: active
      
      # 响应定义
      responses:
        '200':
          description: 成功获取用户列表
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
              example:
                data:
                  - id: "123"
                    name: "张三"
                    email: "zhangsan@example.com"
                pagination:
                  page: 1
                  size: 20
                  total: 100
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '500':
          $ref: '#/components/responses/InternalServerError'
      
      # 安全要求
      security:
        - bearerAuth: []
    
    # POST 操作
    post:
      summary: 创建新用户
      operationId: createUser
      tags: [users]
      requestBody:
        description: 用户创建请求
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
            examples:
              normalUser:
                summary: 普通用户
                value:
                  name: "李四"
                  email: "lisi@example.com"
                  password: "SecurePass123"
                  role: "user"
              adminUser:
                summary: 管理员用户
                value:
                  name: "管理员"
                  email: "admin@example.com"
                  password: "AdminPass123"
                  role: "admin"
      responses:
        '201':
          description: 用户创建成功
          headers:
            Location:
              description: 新创建用户的 URI
              schema:
                type: string
                example: "/users/123"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequestError'
        '409':
          $ref: '#/components/responses/ConflictError'

  /users/{userId}:
    # 路径参数
    parameters:
      - name: userId
        in: path
        description: 用户唯一标识
        required: true
        schema:
          type: string
          format: uuid
        example: "123e4567-e89b-12d3-a456-426614174000"
    
    get:
      summary: 获取用户详情
      operationId: getUserById
      tags: [users]
      responses:
        '200':
          description: 成功获取用户信息
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFoundError'
    
    put:
      summary: 更新用户信息
      operationId: updateUser
      tags: [users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserRequest'
      responses:
        '200':
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
    
    delete:
      summary: 删除用户
      operationId: deleteUser
      tags: [users]
      responses:
        '204':
          description: 删除成功
        '404':
          $ref: '#/components/responses/NotFoundError'

3. 数据模型定义(Schemas)

基本类型定义

yaml
components:
  schemas:
    # 基础用户模型
    User:
      type: object
      description: 用户信息
      required: [id, name, email, createdAt]
      properties:
        id:
          type: string
          format: uuid
          description: 用户唯一标识
          readOnly: true  # 只在响应中出现
          example: "123e4567-e89b-12d3-a456-426614174000"
        name:
          type: string
          description: 用户姓名
          minLength: 2
          maxLength: 50
          pattern: '^[\u4e00-\u9fa5a-zA-Z\s]+$'
          example: "张三"
        email:
          type: string
          format: email
          description: 电子邮箱
          example: "zhangsan@example.com"
        phone:
          type: string
          description: 手机号码
          pattern: '^1[3-9]\d{9}$'
          nullable: true  # 可以为 null
          example: "13800138000"
        age:
          type: integer
          description: 年龄
          minimum: 0
          maximum: 150
          example: 25
        balance:
          type: number
          format: double
          description: 账户余额
          minimum: 0
          example: 1000.50
        status:
          type: string
          description: 用户状态
          enum: [active, inactive, suspended]
          default: active
        roles:
          type: array
          description: 用户角色列表
          items:
            type: string
            enum: [admin, user, guest]
          minItems: 1
          uniqueItems: true
          example: ["user"]
        profile:
          $ref: '#/components/schemas/UserProfile'
        createdAt:
          type: string
          format: date-time
          description: 创建时间
          readOnly: true
          example: "2024-01-01T00:00:00Z"
        updatedAt:
          type: string
          format: date-time
          description: 更新时间
          readOnly: true
          example: "2024-01-02T00:00:00Z"
    
    # 嵌套对象
    UserProfile:
      type: object
      description: 用户详细资料
      properties:
        avatar:
          type: string
          format: uri
          description: 头像 URL
          example: "https://example.com/avatar.jpg"
        bio:
          type: string
          description: 个人简介
          maxLength: 500
          example: "热爱编程的开发者"
        address:
          $ref: '#/components/schemas/Address'
        preferences:
          type: object
          description: 用户偏好设置
          additionalProperties:
            type: string
          example:
            theme: "dark"
            language: "zh-CN"
    
    # 地址模型
    Address:
      type: object
      properties:
        street:
          type: string
          example: "中山路100号"
        city:
          type: string
          example: "上海"
        province:
          type: string
          example: "上海市"
        country:
          type: string
          example: "中国"
        postalCode:
          type: string
          pattern: '^\d{6}$'
          example: "200000"

请求/响应模型

yaml
components:
  schemas:
    # 创建用户请求
    CreateUserRequest:
      type: object
      required: [name, email, password]
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        email:
          type: string
          format: email
        password:
          type: string
          format: password
          minLength: 8
          maxLength: 128
          pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$'
          description: 必须包含大小写字母和数字
        phone:
          type: string
          pattern: '^1[3-9]\d{9}$'
        role:
          type: string
          enum: [admin, user]
          default: user
    
    # 更新用户请求
    UpdateUserRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        phone:
          type: string
          pattern: '^1[3-9]\d{9}$'
        profile:
          $ref: '#/components/schemas/UserProfile'
      minProperties: 1  # 至少提供一个字段
    
    # 分页响应
    UserListResponse:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        pagination:
          $ref: '#/components/schemas/Pagination'
    
    # 分页信息
    Pagination:
      type: object
      required: [page, size, total, totalPages]
      properties:
        page:
          type: integer
          minimum: 1
          description: 当前页码
        size:
          type: integer
          minimum: 1
          maximum: 100
          description: 每页大小
        total:
          type: integer
          minimum: 0
          description: 总记录数
        totalPages:
          type: integer
          minimum: 0
          description: 总页数
        hasNext:
          type: boolean
          description: 是否有下一页
        hasPrevious:
          type: boolean
          description: 是否有上一页

高级特性

yaml
components:
  schemas:
    # 使用 oneOf(互斥选择)
    NotificationMethod:
      oneOf:
        - $ref: '#/components/schemas/EmailNotification'
        - $ref: '#/components/schemas/SmsNotification'
        - $ref: '#/components/schemas/PushNotification'
      discriminator:
        propertyName: type
        mapping:
          email: '#/components/schemas/EmailNotification'
          sms: '#/components/schemas/SmsNotification'
          push: '#/components/schemas/PushNotification'
    
    # 使用 allOf(组合)
    AdminUser:
      allOf:
        - $ref: '#/components/schemas/User'
        - type: object
          required: [adminLevel, permissions]
          properties:
            adminLevel:
              type: integer
              minimum: 1
              maximum: 10
            permissions:
              type: array
              items:
                type: string
    
    # 使用 anyOf(任意组合)
    SearchFilter:
      anyOf:
        - type: object
          properties:
            name:
              type: string
        - type: object
          properties:
            email:
              type: string
        - type: object
          properties:
            phone:
              type: string

4. 参数定义(Parameters)

yaml
components:
  parameters:
    # 路径参数
    UserIdParam:
      name: userId
      in: path
      description: 用户唯一标识
      required: true
      schema:
        type: string
        format: uuid
      example: "123e4567-e89b-12d3-a456-426614174000"
    
    # 查询参数
    PageParam:
      name: page
      in: query
      description: 页码(从1开始)
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
      example: 1
    
    SizeParam:
      name: size
      in: query
      description: 每页大小
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      example: 20
    
    # 请求头参数
    ApiKeyHeader:
      name: X-API-Key
      in: header
      description: API 密钥
      required: true
      schema:
        type: string
      example: "your-api-key-here"
    
    # Cookie 参数
    SessionIdCookie:
      name: sessionId
      in: cookie
      description: 会话 ID
      required: false
      schema:
        type: string

5. 响应定义(Responses)

yaml
components:
  responses:
    # 成功响应
    SuccessResponse:
      description: 操作成功
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                example: true
              message:
                type: string
                example: "操作成功"
    
    # 错误响应
    BadRequestError:
      description: 请求参数错误
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidEmail:
              summary: 邮箱格式错误
              value:
                code: "INVALID_EMAIL"
                message: "邮箱格式不正确"
                timestamp: "2024-01-01T00:00:00Z"
            missingField:
              summary: 缺少必填字段
              value:
                code: "MISSING_FIELD"
                message: "缺少必填字段:name"
                details:
                  field: "name"
                timestamp: "2024-01-01T00:00:00Z"
    
    UnauthorizedError:
      description: 未授权访问
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "UNAUTHORIZED"
            message: "请先登录"
            timestamp: "2024-01-01T00:00:00Z"
      headers:
        WWW-Authenticate:
          description: 认证方式
          schema:
            type: string
            example: "Bearer"
    
    NotFoundError:
      description: 资源不存在
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "NOT_FOUND"
            message: "用户不存在"
            timestamp: "2024-01-01T00:00:00Z"
    
    ConflictError:
      description: 资源冲突
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "CONFLICT"
            message: "邮箱已被注册"
            timestamp: "2024-01-01T00:00:00Z"
    
    InternalServerError:
      description: 服务器内部错误
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "INTERNAL_ERROR"
            message: "服务器内部错误,请稍后重试"
            timestamp: "2024-01-01T00:00:00Z"
            traceId: "abc123def456"

6. 安全定义(Security)

yaml
components:
  securitySchemes:
    # Bearer Token (JWT)
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 使用 JWT 令牌进行认证
    
    # API Key
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API 密钥认证
    
    # OAuth2
    oauth2:
      type: oauth2
      description: OAuth2 认证
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/oauth/authorize
          tokenUrl: https://auth.example.com/oauth/token
          refreshUrl: https://auth.example.com/oauth/refresh
          scopes:
            read:users: 读取用户信息
            write:users: 修改用户信息
            admin: 管理员权限
        clientCredentials:
          tokenUrl: https://auth.example.com/oauth/token
          scopes:
            api:access: API 访问权限
    
    # Basic Auth
    basicAuth:
      type: http
      scheme: basic
      description: 基本认证(用户名/密码)
    
    # Cookie Auth
    cookieAuth:
      type: apiKey
      in: cookie
      name: session_id
      description: Cookie 会话认证

7. 回调定义(Callbacks)

yaml
components:
  callbacks:
    # Webhook 回调
    userCreatedWebhook:
      '{$request.body#/callbackUrl}':
        post:
          summary: 用户创建事件通知
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  type: object
                  properties:
                    event:
                      type: string
                      enum: [user.created]
                    timestamp:
                      type: string
                      format: date-time
                    data:
                      $ref: '#/components/schemas/User'
          responses:
            '200':
              description: 通知接收成功

代码生成最佳实践

1. 为代码生成优化的设计

yaml
# 使用 operationId 指定方法名
paths:
  /users:
    get:
      operationId: listUsers  # 生成的方法名
      x-codegen-request-name: ListUsersRequest  # 自定义请求类名
      x-codegen-response-name: ListUsersResponse  # 自定义响应类名

# 使用 x-* 扩展属性
components:
  schemas:
    User:
      type: object
      x-class-name: UserDTO  # 自定义生成的类名
      x-implements: [Serializable, Cloneable]  # Java 接口实现
      properties:
        id:
          type: string
          x-field-name: userId  # 自定义字段名
          x-getter: getUserIdentifier  # 自定义 getter 方法名

2. 验证规则映射

yaml
# OpenAPI 验证规则会映射到生成代码的验证注解
properties:
  email:
    type: string
    format: email
    # 生成 Java: @Email
    # 生成 Python: validators.email()
    
  age:
    type: integer
    minimum: 0
    maximum: 150
    # 生成 Java: @Min(0) @Max(150)
    # 生成 Python: validators.integer(min=0, max=150)
    
  name:
    type: string
    minLength: 2
    maxLength: 50
    pattern: '^[a-zA-Z\s]+$'
    # 生成 Java: @Size(min=2, max=50) @Pattern(regexp="^[a-zA-Z\\s]+$")
    # 生成 Python: validators.string(min_length=2, max_length=50, regex=r'^[a-zA-Z\s]+$')

3. 枚举生成策略

yaml
# 字符串枚举
status:
  type: string
  enum: [active, inactive, suspended]
  x-enum-varnames: [ACTIVE, INACTIVE, SUSPENDED]  # 自定义枚举常量名
  x-enum-descriptions:  # 枚举描述
    active: 活跃状态
    inactive: 非活跃状态
    suspended: 已暂停

# 数字枚举
level:
  type: integer
  enum: [1, 2, 3]
  x-enum-names: [BASIC, PREMIUM, ENTERPRISE]

文档生成配置

1. 增强文档可读性

yaml
info:
  description: |
    # 用户管理 API
    
    本 API 提供完整的用户管理功能,包括:
    
    * 用户注册和认证
    * 用户信息管理
    * 权限和角色管理
    
    ## 快速开始
    
    1. 获取 API 密钥
    2. 使用密钥进行认证
    3. 调用相应的接口
    
    ## 认证方式
    
    支持以下认证方式:
    - Bearer Token (推荐)
    - API Key
    - Basic Auth

# 为每个操作添加详细说明
paths:
  /users:
    post:
      summary: 创建新用户
      description: |
        创建一个新的用户账号。
        
        ### 业务规则
        
        - 邮箱必须唯一
        - 密码必须符合安全要求
        - 首次创建的用户默认为普通用户角色
        
        ### 注意事项
        
        - 创建成功后会发送欢迎邮件
        - 用户需要通过邮件验证才能激活账号

2. 示例数据

yaml
components:
  examples:
    # 定义可重用的示例
    ValidUser:
      summary: 有效的用户数据
      value:
        id: "123e4567-e89b-12d3-a456-426614174000"
        name: "张三"
        email: "zhangsan@example.com"
        phone: "13800138000"
        status: "active"
        roles: ["user"]
        createdAt: "2024-01-01T00:00:00Z"
    
    UserList:
      summary: 用户列表示例
      value:
        data:
          - id: "123"
            name: "张三"
            email: "zhangsan@example.com"
          - id: "456"
            name: "李四"
            email: "lisi@example.com"
        pagination:
          page: 1
          size: 20
          total: 2
          totalPages: 1
          hasNext: false
          hasPrevious: false

# 在操作中引用示例
paths:
  /users/{userId}:
    get:
      responses:
        '200':
          content:
            application/json:
              examples:
                success:
                  $ref: '#/components/examples/ValidUser'

测试集成

1. 契约测试支持

yaml
# 添加测试相关的元数据
paths:
  /users:
    post:
      x-contract-test:
        priority: high
        scenarios:
          - name: "成功创建用户"
            given: "邮箱未被使用"
            when: "提供有效的用户信息"
            then: "返回201和用户信息"
          - name: "邮箱已存在"
            given: "邮箱已被注册"
            when: "使用相同邮箱注册"
            then: "返回409冲突错误"

2. Mock 数据生成

yaml
# 使用 x-faker 扩展生成测试数据
components:
  schemas:
    User:
      properties:
        name:
          type: string
          x-faker: name.fullName
        email:
          type: string
          format: email
          x-faker: internet.email
        phone:
          type: string
          x-faker: phone.phoneNumber
        avatar:
          type: string
          format: uri
          x-faker: image.avatar

版本管理策略

1. URL 版本控制

yaml
servers:
  - url: https://api.example.com/v1
    description: API v1(当前版本)
  - url: https://api.example.com/v2
    description: API v2(开发中)
  - url: https://api-legacy.example.com
    description: 旧版 API(已废弃)

2. 废弃标记

yaml
paths:
  /users/search:
    get:
      deprecated: true  # 标记为已废弃
      x-deprecation-date: "2024-12-31"
      x-replacement: "/users?q={query}"
      summary: 搜索用户(已废弃)
      description: |
        **⚠️ 此接口已废弃,将于 2024-12-31 下线**
        
        请使用新的搜索接口:`GET /users?q={query}`

工具集成

1. VS Code 插件配置

json
// .vscode/settings.json
{
  "openapi.validate": true,
  "openapi.linting": {
    "rules": {
      "oas3-api-servers": "error",
      "operation-operationId": "error",
      "operation-summary": "warning",
      "oas3-schema": "error"
    }
  }
}

2. 预提交钩子

yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/python-openapi/openapi-spec-validator
    rev: v0.5.1
    hooks:
      - id: openapi-spec-validator
        files: 'api/.*\.yaml$'

3. CI/CD 集成

yaml
# GitHub Actions
name: OpenAPI Validation
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Validate OpenAPI Spec
        run: |
          npx @redocly/cli lint api/openapi.yaml
          
      - name: Generate Code
        run: |
          npx @openapitools/openapi-generator-cli generate \
            -i api/openapi.yaml \
            -g spring \
            -o generated/
            
      - name: Run Contract Tests
        run: |
          npm run test:contract

常见问题

Q: 如何处理循环引用?

yaml
# 使用 $ref 处理循环引用
components:
  schemas:
    Category:
      type: object
      properties:
        id:
          type: string
        parent:
          $ref: '#/components/schemas/Category'  # 自引用
        children:
          type: array
          items:
            $ref: '#/components/schemas/Category'

Q: 如何定义文件上传?

yaml
paths:
  /users/{userId}/avatar:
    post:
      summary: 上传用户头像
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: 图片文件(支持 JPG、PNG)
            encoding:
              file:
                contentType: image/png, image/jpeg

Q: 如何定义动态响应?

yaml
# 使用 oneOf 定义多种可能的响应
responses:
  '200':
    description: 成功响应
    content:
      application/json:
        schema:
          oneOf:
            - $ref: '#/components/schemas/User'
            - $ref: '#/components/schemas/UserList'
          discriminator:
            propertyName: responseType

总结

OpenAPI 规范是实现 API-First 开发的基石。通过:

  1. 规范化设计: 统一 API 描述格式
  2. 自动化生成: 减少手工编码工作
  3. 文档同步: 保持代码和文档一致
  4. 测试集成: 支持契约测试和自动化测试

我们可以构建高质量、可维护的 API 系统。记住,好的 API 设计是成功的一半!

SOLO Development Guide