OpenAPI、Swagger 与 Redoc 契约文档工具手册
同一份契约,四个工具给出四种答案
一次接口联调卡在了一个很典型的现场:Redocly CLI 报 $ref 无法解析,Swagger Editor 仍然显示了大部分接口,Swagger UI 页面能打开却少了响应模型,后端生成的文档又把 nullable 和联合类型解释成另一种结果。团队若把“页面能渲染”当成契约正确,错误会一直拖到 SDK 生成或客户端运行时才暴露。
先把四类对象拆开。OpenAPI Specification 是规范;openapi.yaml 是按某一规范版本写出的 API Description;Swagger UI、Swagger Editor 和 Redoc 是消费者;Redocly CLI 的 lint、bundle 与静态构建是处理链。消费者可以容错、忽略未知字段或采用不同的 JSON Schema 实现,因此任一页面成功都不能替代规范检查与下游兼容实验。
OpenAPI 官方发布页列出了 3.2.0、3.1.x、3.0.x 和 2.0。根字段 openapi 表示解释文档所需的规范版本,info.version 表示这份 API Description 的版本,两者不是同一个版本号。规范对象与字段章节是权威定义;官方同时提醒,配套 JSON Schema 能发现很多错误,但不能保证发现所有规范违规。
3.2 是当前规范线,但团队基线应取整条工具链共同支持的最高版本,不能把某一条命令的支持外推成整个产品链都支持。Redocly CLI 的 lint、bundle 与 build-docs 也不是同一兼容面:当前 build-docs 只接受 Swagger 2.0、OpenAPI 3.0 和 3.1,3.2 支持仍未进入该命令。Swagger Editor 官方文档仍把 Editor 4 标为 legacy,并说明只有 Editor Next / Editor 5 支持 OpenAPI 3.1。采用 3.2 前要让 lint、bundle、UI、Editor、Redoc 静态构建、SDK 生成器、网关导入器和契约测试分别跑一次固定样例。任何一个关键消费者失败,就继续使用已验证的 3.1 或 3.0 基线,而不是仅修改版本字符串。
从一个可审查的根文件开始
第一次落地可使用 OpenAPI 3.1,先验证多工具兼容,再把 3.2 的新字段作为单独升级变更。创建 docs/api/openapi.yaml:
openapi: 3.1.0
info:
title: Example Orders API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/health:
get:
operationId: getHealth
summary: Read service health
responses:
"200":
description: Service is ready
content:
application/json:
schema:
$ref: "#/components/schemas/Health"
example:
status: ok
components:
schemas:
Health:
type: object
required: [status]
properties:
status:
type: string
enum: [ok]这里故意使用假域名、假数据和无凭证接口。真正的内部域名、租户名、手机号、订单号或令牌一旦进入 servers、examples、Markdown 描述或生成后的 HTML,就会随搜索索引、构建制品和截图扩散。
Redocly CLI 2 是 ESM-only 包,运行时要求为 Node.js 22.12.0 及以上,或 20.19.0 及以上。项目安装比全局安装更容易复现;package-lock.json、pnpm-lock.yaml 或 yarn.lock 应锁住实际解析版本:
npm install --save-dev @redocly/cli@2
npx redocly --version临时试用可运行 npx @redocly/cli@latest,但 CI 不应每次解析 latest。不允许安装 Node 包的环境可以使用官方 Redocly CLI Docker 入口,并将镜像标签进一步锁到团队验证过的版本或 digest:
docker run --rm -v "$PWD:/spec" redocly/cli lint openapi.yamlPowerShell 中的挂载路径写法与 shell 不同,路径解析失败时先查看容器内 /spec 是否存在目标文件,不要把 ENOENT 误判为 OpenAPI 语法错误。
lint 规则会改变合并结果
在仓库根目录放置 redocly.yaml,让本地、IDE 与 CI 共享同一组规则:
extends:
- recommended
apis:
orders@v1:
root: docs/api/openapi.yaml
rules:
operation-operationId: error
operation-operationId-unique: error
no-unresolved-refs: error
no-unused-components: warn
security-defined: errorerror 会让 lint 以非零退出码结束,warn 只产生提示,off 不执行规则。Redocly 规则文档提供 spec、recommended、recommended-strict 和 minimal 等规则集。新项目可从 recommended 开始;存量契约若一次出现大量历史问题,可以先把非安全类问题降为 warning,再按目录和 owner 逐批收紧。直接启用严格规则并长期追加 ignore 文件,只会把治理债务藏起来。
先检查配置本身,再检查描述:
npx redocly check-config
npx redocly lint orders@v1 --extends=spec --format=stylish
npx redocly lint orders@v1 --format=stylish第一条 lint 用 spec 规则集单独暴露结构与规范约束,第二条再执行仓库配置中的团队规则;两条都通过才说明“规范形状”和“组织约束”没有被混成一个模糊绿灯。OpenAPI 官方提供的 JSON Schema 也有明确边界:3.1 及以上的 schema 不验证 Schema Object 内部使用的 JSON Schema 方言,而 schema-base 只适用于 OAS 基础方言。即便 JSON Schema 校验成功,规范正文中的全部约束、跨文件引用、operationId 唯一性和下游生成兼容仍需其他门禁证明。
正确样例应以退出码 0 结束,并出现契约通过检查的摘要。输出中的文件路径、行列号、规则名和严重级别是故障证据;评审意见应引用这些字段,而不是只贴一张编辑器红线截图。
现在做一次反向实验,把响应中的引用改为不存在的对象:
$ref: "#/components/schemas/HealthMissing"再次运行 lint,预期得到 no-unresolved-refs 错误和非零退出码。若命令仍然成功,优先检查三件事:执行目录是否找到了预期的 redocly.yaml,命令是否读取了别名对应的根文件,规则是否被 API 级配置覆盖。修复引用后重跑,只有错误消失且退出码恢复为 0,才形成完整的正反证据。
bundle 解决的是引用交付,不是语义正确
契约变大后可把 schema、参数和响应拆成多个文件。bundle 从一个根文件沿 $ref 收集依赖,产出下游更容易消费的单文件;join 则用于合并多个 API Description,二者不是同一操作。官方 bundle 命令说明还指出,bundle 会依次执行 preprocessors、rules 和 decorators,而 lint 不执行 decorators。
npx redocly lint orders@v1 && \
npx redocly bundle orders@v1 \
--output artifacts/openapi.bundle.yaml预期结果是 lint 成功后才创建 bundle;lint 失败时后半条命令不会运行。不要把 bundle 直接覆盖根文件,也不要同时手改根文件和生成物。CI 可以把 bundle 当作短期制品交给文档站、网关或生成器,仓库若提交 bundle,则必须在 CI 重建后执行 diff,防止旧制品伪装成新契约。
远程 $ref 会让构建依赖网络、DNS、证书和远端内容可用性,还可能把私有描述发送给第三方主机。Redocly 支持通过环境变量给特定 URL 配置解析请求头,但密钥只能来自 CI secret;URL 匹配规则、最小权限、日志脱敏和凭证轮换都要评审。更稳妥的公共发布链通常在受控构建阶段拉取依赖、校验摘要并生成自包含 bundle。
循环引用也要进入供应链测试。OpenAPI 规范要求工具检测引用环,避免资源耗尽,但不同消费者对递归 schema 的渲染和代码生成能力并不相同。递归模型必须经过目标生成器和运行时序列化实验,不能只靠 bundle 成功下结论。
Swagger Editor 负责即时反馈,Git 负责事实
Swagger Editor 适合在修改时同时查看源文件、诊断和预览。公共网页入口方便试写,却不适合粘贴内部契约;浏览器页面、在线 validator、代码生成服务和遥测都可能形成额外的数据处理边界。
自托管时先确定 Editor 代际。Editor 4 的官方页面仍提供 docker.swagger.io/swaggerapi/swagger-editor 入口,但它不支持 OpenAPI 3.1;Editor 5 / Editor Next 的仓库和镜像行为不同,并且当前发行与集成方式仍在演进。镜像上线前应查看对应发行说明和 SBOM,锁定镜像 digest,并用团队样例做回归。以下命令适合隔离的本地验证,不能替代版本锁定:
docker pull docker.swagger.io/swaggerapi/swagger-editor:latest
docker run --rm -p 8080:80 \
docker.swagger.io/swaggerapi/swagger-editor:latest打开 http://localhost:8080 后导入最小契约,确认 GET /health、响应 schema 和 example 都出现。再导入含错误 $ref 的反例,记录 Editor 的诊断位置。若 Editor 显示正常而 CLI 失败,以已锁定的规范检查和 CI 结果阻断合并,并为 Editor 兼容问题建立独立升级项。
容器验证结束后停止进程;若没有使用 --rm,还要删除测试容器。镜像缓存是否清理由共享 runner 的保留策略决定,不能在多人机器上用无范围的清理命令。
Swagger UI 的交互按钮也是请求客户端
Swagger UI 可以从 URL 或内联对象渲染契约,并提供 Try it out。官方安装文档区分 swagger-ui、swagger-ui-react 与面向静态资源托管的 swagger-ui-dist。项目内嵌应选与现有前端栈匹配的包,静态发布则可以构建固定资源;不要在生产页面从不固定版本的 CDN 动态拉脚本。
关键设置会直接改变安全与排障结果:
SwaggerUIBundle({
url: "/api-docs/openapi.bundle.yaml",
dom_id: "#swagger-ui",
deepLinking: true,
tryItOutEnabled: false,
persistAuthorization: false,
validatorUrl: null,
});url 决定浏览器从哪里读取契约;跨域读取会受 CORS、CSP、网络代理和证书影响。tryItOutEnabled: false 降低误操作概率,但真正的保护必须落在网关和服务端授权。persistAuthorization: false 避免令牌跨刷新保留。Swagger UI 的在线 validator 默认值属于外部服务调用,配置文档允许将 validatorUrl 设为 null;内部契约通常应关闭它或改为受控的内部验证服务。
浏览器也限制可编程设置的请求头。Swagger UI 限制说明列出了 Cookie、Host、Origin 等 forbidden headers。Try it out 无法复现某个 Cookie 请求时,应转向 DevTools、curl 或专用 API 客户端,不要通过关闭浏览器安全策略来“修复”文档页面。
OAuth 配置尤其危险。官方明确警告,不要在生产 Swagger UI 配置 clientSecret,因为它会暴露给浏览器。授权码流程应结合 PKCE、受限 redirect URI、测试 client 和最小 scope;API key 与 bearer token 只由当前用户临时输入,不能写进初始化脚本、契约或构建产物。
Redoc 与 Redocly 分工
Redoc 是开源的 API 文档渲染器;Redocly CLI 负责 lint、bundle、转换和静态文档构建;Redocly 托管平台增加协作、托管、权限与企业治理能力。三者名字接近,但部署、数据边界和成本模型不同。
使用 3.0 或 3.1 契约时,本地静态构建可以这样完成:
npx redocly build-docs docs/api/openapi.yaml \
--output artifacts/redoc-static.html预期得到可离线打开的 HTML。打开后检查操作列表、schema、示例、鉴权说明和外部链接,再搜索 example.com 以外的域名、邮箱形态、token 形态和内部标识。若输入改成 3.2 契约,当前命令应被视为兼容性反例:在官方明确支持前,不接受“降级渲染了大部分页面”作为成功证据,应阻断发布或先把文档基线留在 3.1。静态页面能阅读,不代表 Try it out、契约兼容或业务接口已验证。
对多页面文档项目,Redocly CLI 还提供 preview 启动本地预览服务;单个 OpenAPI 文件的快速文档检查可使用 build-docs。升级 CLI 时先运行 redocly --version 和 redocly check-config,再比较 lint 规则、bundle diff 与 HTML 快照。CLI 2 的 ESM 迁移可能影响 CommonJS 配置和自定义插件,不能只看命令是否启动。
开源 CLI 与 Redoc 可以在本地运行;托管能力按席位、项目、页面、SSO、RBAC、数据驻留和支持等级形成商业边界。采购前从Redocly 定价页和 Swagger 账户的 Plan Details 核对实际套餐,不在仓库中固化价格假设。需要 SSO、RBAC、私有托管或采购流程时,要同时评估数据驻留、读者是否计费、审计导出、离职回收和供应商退出方案。
把契约变更接入项目与 CI
一个可维护的目录可以保持主源、配置与生成物职责清楚:
docs/api/
openapi.yaml
components/
schemas.yaml
redocly.yaml
artifacts/
openapi.bundle.yaml
redoc-static.htmlartifacts/ 通常应由 CI 生成并按保留策略清理。项目脚本使用本地依赖,不通过全局命令解析版本:
{
"scripts": {
"api:config": "redocly check-config",
"api:lint": "redocly lint orders@v1",
"api:bundle": "redocly bundle orders@v1 --output artifacts/openapi.bundle.yaml",
"api:docs": "redocly build-docs docs/api/openapi.yaml --output artifacts/redoc-static.html"
}
}CI 顺序应是安装锁定依赖、检查配置、lint、bundle、对 bundle 做下游兼容检查、构建预览、扫描制品。SDK 或 server stub 生成放在这些步骤之后,并由对应语言 owner 审查生成器版本、模板 diff 和发布目标。lint 主要检查结构与团队规则;breaking change 检测、实现漂移和真实请求验证需要专门工具与测试,不能由文档渲染结果代替。
对于 code-first 项目,代码生成 OpenAPI 后先和仓库基线做语义 diff,再运行同一条处理链;对于 spec-first 项目,服务实现和客户端都从评审后的契约消费。无论哪一种,都只能有一个可编辑主源。平台导出、网关导出和静态 bundle 是输入快照或生成物,不应反向变成第二份手工真相。
故障证据如何回到底层机制
页面空白,但 lint 成功
先看浏览器 Network 和 Console。契约请求若是 404、CORS 或 CSP 拒绝,问题在静态托管与浏览器安全边界;请求成功但渲染器异常,再核对渲染器支持的 OAS 版本和扩展字段。lint 成功仅说明所选规则未发现错误,不保证每个消费者都实现了相同能力。
本地 bundle 成功,CI 报引用不存在
比较执行目录、路径大小写、Git 是否提交了被引用文件,以及远程 $ref 的 DNS、代理、CA 和授权。Windows 上不敏感的路径大小写可能在 Linux runner 失败。修复后应在与 CI 相同的容器或 runner 镜像中重跑,不能用本地成功覆盖 CI 证据。
预览能调用,浏览器请求却是 401 或 CORS
检查 servers 解析出的最终 URL、预检请求、允许来源、授权 scheme 与 scope。Swagger UI 只是浏览器客户端,仍受同源策略和 forbidden headers 约束。修复通常落在测试网关、OAuth client、CORS 策略或契约安全定义,而不是把生产授权关闭。
生成器丢字段或生成出错误类型
保存根文件、bundle、生成器版本、命令和最小 schema。若问题只在 3.2 出现,用同一模型降到已验证的 3.1 表达做对照;若只在递归或联合类型出现,构造最小反例并提交给生成器。回滚是恢复契约规范基线和生成器锁定版本,不是手改所有生成文件。
lint 突然新增大量问题
先看 lockfile 和 CLI 实际版本,再看规则集是否更新、配置是否从 CommonJS 迁移、插件是否改变。将新规则先作为 warning 观测可以减少无意义阻断,但结构错误、未解析引用和安全 scheme 缺失不应长期降级。升级 PR 应包含旧版与新版的诊断差异及回滚命令。
供应链、容量与长期治理
OpenAPI 处理链会读取 Markdown、远程引用、插件代码和生成模板。规范要求工具对 Markdown 中的 HTML 做适当清理,也提醒外部资源可能来自不可信域。对外发布必须使用支持清理的渲染器,限制远程引用域名;自定义 Redocly 插件和生成模板按可执行代码评审,依赖扫描、SBOM 和许可证检查不能省略。
大型契约的成本通常出现在三处:lint 与 bundle 的 CPU/内存、静态 HTML 和 source map 的制品体积、每个 PR 重复生成 SDK 的构建时间。记录根文件大小、引用数量、lint 时长、bundle 大小和生成器耗时;当趋势持续增长时,按 API owner 拆分入口、缓存不变依赖,并只对受影响的 API 执行下游生成。不要用 --max-problems 隐藏增长,也不要无边界保留每次预览制品。
每次规范、CLI、UI、Editor 或生成器升级都应带一组固定契约样例:正常对象、鉴权、递归引用、联合类型、上传下载、webhook,以及当前规范线新增字段。升级先在分支生成诊断与制品 diff,再灰度到非关键 API;失败时恢复 lockfile、镜像 digest、规则配置和规范基线,撤销临时凭证并删除测试制品。
团队至少维护这些不变量:可编辑主源只有一个;错误级规则在本地与 CI 一致;公共制品不含内部地址与真实数据;Try it out 没有绕过服务端权限;每个生成器都有锁定版本和 owner;所有临时预览、容器、制品和凭证都有到期或清理路径。做到这些,Swagger 与 Redoc 才是契约交付链,而不是几张看起来正确的页面。
