📦 本文基于的完整项目源码:https://github.com/meirongdev/shop

🏷️ 当前文章对应的代码版本:openapi_3_1_upgrade


背景#

我们的电商平台自启动以来,一直使用 OpenAPI 3.0.x 作为 API 文档规范。近期在整理 SpringDoc 2.8.9 和 JSON Schema 2020-12 工具链时,我顺手验证了一次 OpenAPI 3.1 升级的可行性,最后决定先在项目里试着切过去。

就这次实践看,升级过程主要是一行配置,没有涉及 Java 代码改动。下面更像是一份升级笔记:记录我为什么做这件事、哪些收益比较直接,以及哪些兼容性边界要先确认。


OpenAPI 3.1 的核心变化#

OpenAPI 3.1 于 2021 年底发布(官方发布公告),虽然看起来只是小版本号 +1,但它底层做了一个根本性的改变:全面对齐 JSON Schema Draft 2020-12JSON Schema 2020-12 规范)。

这不只是"换了一个标准",更像是把 OpenAPI Schema 往 JSON Schema 工具链上靠近了一步。

变化一:nullable 语义重构#

OpenAPI 3.0 的写法

{
  "type": "string",
  "nullable": true
}

这是一个 OpenAPI 自定义扩展字段。JSON Schema 标准中不存在 nullable,这意味着:

  • 标准 JSON Schema 校验器不认识它
  • 代码生成器需要专门处理 OpenAPI 特有的逻辑
  • 与 JSON Schema 生态工具链不互通

OpenAPI 3.1 的写法

{
  "type": ["string", "null"]
}

这是 JSON Schema Draft 2020-12 的原生语法type 字段接受类型数组,null 成为一个合法的类型值。

实际影响

// 项目中的 DTO 注解(代码不变)
@Schema(description = "用户昵称,可选填", nullable = true)
private String nickname;
维度 OpenAPI 3.0 输出 OpenAPI 3.1 输出
生成的 JSON {"type":"string","nullable":true} {"type":["string","null"]}
JSON Schema 校验器 ❌ 不识别 ✅ 原生支持
代码生成器 需要 OpenAPI 专用逻辑 使用标准 JSON Schema 逻辑

变化二:$ref 与同级字段共存#

OpenAPI 3.0 中,$ref 不能与任何其他字段同时出现(参见 OpenAPI 3.0 规范)。如果需要给引用加描述,只能用 allOf 嵌套:

{
  "allOf": [
    { "$ref": "#/components/schemas/User" }
  ],
  "description": "The user who created this order"
}

OpenAPI 3.1 对齐了 JSON Schema 的行为,$ref 可以与 descriptiondeprecatedexample 等字段直接共存(参见 OpenAPI 3.1 规范 - Reference Object):

{
  "$ref": "#/components/schemas/User",
  "description": "The user who created this order"
}

这消除了长期以来被开发者诟病的 allOf 包装模式(相关 GitHub 讨论:OAI/OpenAPI-Specification#1219)。

变化三:examples 多值支持#

OpenAPI 3.0 中,每个参数/响应体只能有一个 example(参见 OpenAPI 3.0 规范 - Example Object):

{
  "schema": { "type": "string" },
  "example": "buyer.demo"
}

OpenAPI 3.1 引入了 examples Map,可以为同一个字段提供多个示例(参见 OpenAPI 3.1 规范 - Media Type Object):

{
  "schema": { "type": "string" },
  "examples": {
    "buyer": { "value": "buyer.demo" },
    "seller": { "value": "seller.demo" },
    "vip": { "value": "buyer.vip" }
  }
}

这对 API 文档的消费者非常有价值——一个接口可以展示不同角色、不同场景下的多种示例值。

变化四:Webhooks 原生支持#

OpenAPI 3.0 没有描述 Webhook / 回调的标准方式。callbacks 字段只能描述基于请求-响应模式的回调(参见 OpenAPI 3.0 规范 - Callback Object),不适合事件驱动的推送场景。

OpenAPI 3.1 在顶层引入了 webhooks 字段(参见 OpenAPI 3.1 规范 - Webhooks):

openapi: 3.1.0
info:
  title: Order Service API
  version: v1
webhooks:
  orderCreated:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderEvent'
      responses:
        '200':
          description: Webhook received

这更适合描述我们平台里偏事件驱动的回调或推送场景(如 buyer.registered.v1order.events.v1)。

变化五:deprecated 字段规范化#

OpenAPI 3.1 允许在 Security Scheme 上标记 deprecated(参见 OpenAPI 3.1 规范 - Security Scheme Object),这对于 OAuth 迁移、JWT 算法迁移等场景很有价值:

components:
  securitySchemes:
    oldJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT-HS256
      deprecated: true    # ← 3.1 新增
    newJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT-RS256

变化六:exclusiveMinimum / exclusiveMaximum 语义变更#

OpenAPI 3.0

{ "minimum": 0, "exclusiveMinimum": true }

OpenAPI 3.1(对齐 JSON Schema Draft 2020-12,参见 JSON Schema 规范 - exclusiveMinimum):

{ "exclusiveMinimum": 0 }

exclusiveMinimumexclusiveMaximum 从布尔标志变成了数值本身。这意味着 exclusiveMinimum: 0 表示"大于 0",而不是"大于等于 0"。


为什么现在升级#

1. 工具链支持已经比较完整#

工具 OpenAPI 3.1 支持状态
SpringDoc 2.8.9 ✅ 原生支持,一行配置即可
Swagger UI 5.x ✅ 完整支持
JSON Schema 校验器(Ajv, jsonschema) ✅ Draft 2020-12 原生
Postman ✅ 支持
Redoc ✅ 支持
OpenAPI Generator 7.x ✅ 支持

2. 与 JSON Schema 生态互通#

这是这次升级里我最看重的一点。过去 OpenAPI 的 Schema 更像是一个"接近 JSON Schema,但仍有差异"的子集,导致:

  • 不能直接拿很多 JSON Schema 校验器来验证请求/响应
  • 代码生成器往往要维护 OpenAPI 专用的类型映射
  • 和 JSON Schema 社区里的一些工具、库、实践之间总有一层转换成本

升级到 3.1 后,OpenAPI Schema 采用了 JSON Schema Draft 2020-12 的语义,大多数现代 JSON Schema 工具链能更顺畅地接入。不过如果团队里还有旧版解析器或生成器,兼容性仍然要逐个确认。

3. 为 OpenAPI 3.2 铺路#

OpenAPI 3.2(2025 年 9 月发布)在 3.1 的基础上增加了 Streaming、QUERY 方法、结构化 Tags 等特性。对大多数团队来说,如果后面想继续跟进 3.2,通常也会先把 3.1 这一步走稳。


升级过程#

改动量:每个服务仅一行配置#

# application.yml
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
    version: openapi_3_1   # ← 新增这一行

至少在这次升级里,Java 代码没有改动。 @Schema@Operation@ApiResponse 等注解可以继续沿用,SpringDoc 在序列化时输出 3.1 格式的 JSON。

我们平台的实际操作#

平台共有 15 个微服务(1 Gateway + 2 BFF + 12 Domain Service),每个服务的 application.yml 添加一行 version: openapi_3_1

 springdoc:
   api-docs:
     enabled: ${SPRINGDOC_ENABLED:true}
     path: /v3/api-docs
+    version: openapi_3_1

按这次提交统计:15 行配置变更,0 行 Java 代码变更。


升级后的验证#

升级后,访问任意服务的 /v3/api-docs 端点:

curl http://127.0.0.1:18080/v3/api-docs/buyer | jq '.openapi'

输出应为:

"3.1.0"

同时在 Swagger UI 中查看 nullable 字段的 Schema:

// 3.0 格式(升级前)
{ "type": "string", "nullable": true }

// 3.1 格式(升级后)
{ "type": ["string", "null"] }

注意事项#

下游工具兼容性#

如果团队中有下游系统(如第三方 API 消费者、旧版代码生成器)依赖你的 OpenAPI spec,需要确认它们支持 3.1。主要关注点:

  1. nullabletype: ["...", "null"]:旧版解析器可能不识别数组形式的 type
  2. exclusiveMinimum 语义变化:从布尔值变为数值
  3. $ref 同级字段:旧版解析器可能忽略 $ref 旁的 description

对于 demo / 内部项目,这些通常不是问题。对于对外暴露的 API,建议通过版本号灰度或提供双版本 spec(3.0 和 3.1 同时可用)。

@Schema(nullable = true) 注解#

Java 代码中的 @Schema(nullable = true) 注解不需要修改。SpringDoc 在 3.1 模式下会自动将其转换为 type: ["string", "null"] 格式。注解是 Java 层的抽象,与输出格式无关。


总结#

维度 OpenAPI 3.0 OpenAPI 3.1
Schema 基础 自定义 subset JSON Schema Draft 2020-12
nullable 自定义字段 type 数组含 "null"
$ref 同级字段 不支持 支持(descriptiondeprecated 等)
examples 单值 Map 多值
Webhooks 原生 webhooks 字段
与 JSON Schema 生态 需要额外适配 对接更顺畅
升级成本 一行配置,零代码变更

按我这次升级验证的感受,OpenAPI 3.1 最直接的价值,不是多了几个字段,而是和 JSON Schema 生态的对接更顺了。这让 API 文档不再只是给 Swagger UI 看的一份描述,也更容易接入校验器、代码生成器、表单生成器或 Mock 服务。

如果你的项目还在用 OpenAPI 3.0,且下游工具已经确认支持 3.1,我觉得这条升级路径至少值得纳入评估。


技术栈版本:Java 25 · Spring Boot 3.5.11 · Spring Cloud 2025.0.1 · SpringDoc 2.8.9 · OpenAPI 3.1.0

💻 完整项目:https://github.com/meirongdev/shop