Skip to content

OpenAPI 文件合并指南

当你的 OpenAPI 规范分割成多个文件时,需要将它们合并成单个文件用于代码生成或部署。本指南介绍多种合并方法和最佳实践。

🎯 为什么需要合并?

  • 代码生成 - 大多数生成器更好地支持单文件
  • 部署发布 - 单文件更易于分发和版本管理
  • 工具兼容 - 某些工具不支持 $ref 外部引用
  • 性能优化 - 减少文件加载次数

📁 典型的分割结构

api-docs/
├── openapi.yaml              # 主文件
├── components/
│   ├── responses.yaml        # 通用响应
│   ├── securitySchemes.yaml  # 安全方案
│   └── schemas/
│       ├── auth.yaml         # 认证模型
│       ├── user.yaml         # 用户模型
│       └── common.yaml       # 通用模型
└── paths/
    ├── auth.yaml             # 认证接口
    ├── users.yaml            # 用户接口
    └── admin.yaml            # 管理接口

🛠️ 合并方法对比

工具推荐度输出格式维护状态特点
Redocly CLI⭐⭐⭐⭐⭐YAML/JSON🟢 活跃现代化、功能丰富
OpenAPI Generator⭐⭐⭐⭐YAML🟢 活跃集成在代码生成中
Swagger CLI⭐⭐⭐JSON🔴 已废弃简单但不再维护

🚀 方法一:Redocly CLI (推荐)

安装

bash
npm install -g @redocly/cli

基本用法

bash
# 合并为 YAML 格式
npx @redocly/cli bundle api-docs/openapi.yaml -o openapi-bundled.yaml

# 合并为 JSON 格式  
npx @redocly/cli bundle api-docs/openapi.yaml -o openapi-bundled.json

# 指定格式
npx @redocly/cli bundle api-docs/openapi.yaml --format=yaml -o merged.yaml

高级选项

bash
npx @redocly/cli bundle api-docs/openapi.yaml \
  --remove-unused-components \
  -o clean.yaml
bash
npx @redocly/cli bundle api-docs/openapi.yaml \
  --pretty \
  -o pretty.yaml
bash
npx @redocly/cli bundle api-docs/openapi.yaml \
  --lint \
  -o validated.yaml

配置文件方式

创建 redocly.yaml

yaml
extends:
  - recommended

apis:
  main:
    root: api-docs/openapi.yaml

bundle:
  remove-unused-components: true
  pretty: true
  format: yaml

使用配置文件:

bash
npx @redocly/cli bundle --config redocly.yaml

⚙️ 方法二:OpenAPI Generator

基本用法

bash
npx @openapitools/openapi-generator-cli generate \
  -i api-docs/openapi.yaml \
  -g openapi-yaml \
  -o output/ \
  --additional-properties=outputFile=openapi-resolved.yaml

优点

  • 无需额外安装工具
  • 集成在代码生成流程中
  • 输出标准 YAML 格式

🔧 方法三:自定义脚本

对于复杂需求,可以编写自定义合并脚本:

Node.js 脚本

创建 merge-openapi.js

javascript
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';

function resolveRefs(obj, basePath = '') {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (Array.isArray(obj)) {
    return obj.map(item => resolveRefs(item, basePath));
  }

  if (obj.$ref && typeof obj.$ref === 'string') {
    const refPath = obj.$ref;
    if (refPath.startsWith('./') || refPath.startsWith('../')) {
      // 解析本地引用
      const [filePath, jsonPath] = refPath.split('#');
      const fullPath = path.resolve(basePath, filePath);
      
      try {
        const refContent = yaml.load(fs.readFileSync(fullPath, 'utf8'));
        let resolved = refContent;
        
        if (jsonPath) {
          const pathParts = jsonPath.split('/').filter(Boolean);
          for (const part of pathParts) {
            const decodedPart = decodeURIComponent(part.replace(/~1/g, '/').replace(/~0/g, '~'));
            resolved = resolved[decodedPart];
          }
        }
        
        return resolveRefs(resolved, path.dirname(fullPath));
      } catch (error) {
        console.warn(`⚠️  无法解析引用: ${refPath}`, error.message);
        return obj;
      }
    }
  }

  const result = {};
  for (const [key, value] of Object.entries(obj)) {
    result[key] = resolveRefs(value, basePath);
  }
  return result;
}

function mergeOpenAPI(inputFile, outputFile) {
  try {
    const basePath = path.dirname(inputFile);
    const content = yaml.load(fs.readFileSync(inputFile, 'utf8'));
    const resolved = resolveRefs(content, basePath);
    
    const output = yaml.dump(resolved, {
      indent: 2,
      lineWidth: -1,
      noRefs: true,
      sortKeys: false
    });
    
    fs.writeFileSync(outputFile, output, 'utf8');
    console.log(`✅ 合并完成: ${outputFile}`);
  } catch (error) {
    console.error('❌ 合并失败:', error.message);
    process.exit(1);
  }
}

// 使用示例
const inputFile = process.argv[2] || 'api-docs/openapi.yaml';
const outputFile = process.argv[3] || 'openapi-merged.yaml';

mergeOpenAPI(inputFile, outputFile);

使用脚本

bash
# 安装依赖
npm install js-yaml

# 运行脚本
node merge-openapi.js api-docs/openapi.yaml merged.yaml

📊 实际测试结果

基于示例项目的测试结果:

工具输出文件大小格式处理时间
原始文件openapi.yaml4.4KBYAML (带$ref)-
Redocly CLIopenapi-bundled.yaml76KBYAML~100ms
OpenAPI Generatoropenapi-resolved.yaml93KBYAML~3s
Swagger CLIopenapi-merged.json251KBJSON~200ms

🔄 自动化工作流

批处理脚本

创建 merge-and-generate.bat

batch
@echo off
echo 🔄 Starting merge and generation process...

REM 1. 合并文件
echo 📦 Step 1: Merging OpenAPI files...
npx @redocly/cli bundle api-docs/openapi.yaml -o api-docs/openapi-bundled.yaml

REM 2. 验证文件
echo ✅ Step 2: Validating merged file...
npx @redocly/cli lint api-docs/openapi-bundled.yaml

REM 3. 生成客户端
echo 🚀 Step 3: Generating clients...
npx @openapitools/openapi-generator-cli generate -i api-docs/openapi-bundled.yaml -g typescript-fetch -o clients/typescript
npx @openapitools/openapi-generator-cli generate -i api-docs/openapi-bundled.yaml -g java -o clients/java

REM 4. 生成文档
echo 📚 Step 4: Generating documentation...
npx @redocly/cli build-docs api-docs/openapi-bundled.yaml -o docs/index.html

echo ✨ Process completed successfully!

CI/CD 集成

GitHub Actions 示例:

yaml
name: OpenAPI Workflow

on:
  push:
    paths: ['api-docs/**']

jobs:
  merge-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          
      - name: Install dependencies
        run: |
          npm install -g @redocly/cli
          npm install -g @openapitools/openapi-generator-cli
          
      - name: Merge OpenAPI files
        run: |
          npx @redocly/cli bundle api-docs/openapi.yaml \
            --remove-unused-components \
            --pretty \
            -o api-docs/openapi-bundled.yaml
            
      - name: Validate merged file
        run: npx @redocly/cli lint api-docs/openapi-bundled.yaml
        
      - name: Generate clients
        run: |
          npx @openapitools/openapi-generator-cli generate \
            -i api-docs/openapi-bundled.yaml \
            -g typescript-fetch \
            -o clients/typescript
            
      - name: Generate documentation
        run: |
          npx @redocly/cli build-docs api-docs/openapi-bundled.yaml \
            -o docs/index.html
            
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs

🔍 质量检查

验证合并结果

bash
# 验证合并后的文件
npx @redocly/cli lint openapi-bundled.yaml

# 对比原文件和合并文件 (检查是否有遗漏)
npx @openapitools/openapi-generator-cli validate -i api-docs/openapi.yaml
npx @openapitools/openapi-generator-cli validate -i openapi-bundled.yaml

统计信息

bash
# 查看文件统计
npx @redocly/cli stats openapi-bundled.yaml

# 检查文件大小
ls -lh *.yaml

⚠️ 常见问题

1. 引用路径错误

bash
# 问题:$ref 路径无法解析
# 解决:检查文件路径和相对路径
npx @redocly/cli lint api-docs/openapi.yaml

2. 循环引用

bash
# Redocly 会自动检测循环引用
npx @redocly/cli bundle api-docs/openapi.yaml --lint

3. 编码问题

bash
# 确保所有文件使用 UTF-8 编码
file -bi api-docs/**/*.yaml

4. 内存不足

bash
# 对于大型项目,增加内存限制
NODE_OPTIONS="--max-old-space-size=4096" npx @redocly/cli bundle ...

📝 最佳实践

1. 文件组织

  • 保持原始分割文件的版本控制
  • 将合并文件添加到 .gitignore
  • 使用清晰的文件命名约定

2. 自动化

  • 在 CI/CD 中自动合并
  • 合并前进行验证
  • 生成多种格式以满足不同需求

3. 版本管理

bash
# 为合并文件添加版本信息
npx @redocly/cli bundle api-docs/openapi.yaml \
  --remove-unused-components \
  -o dist/openapi-v1.2.3.yaml

4. 质量保证

  • 合并后立即验证
  • 检查文件大小是否合理
  • 确保所有引用都已解析

🎯 选择建议

  • 开发阶段:使用 Redocly CLI,功能最全面
  • 生产环境:使用 Redocly CLI 或自定义脚本
  • CI/CD 集成:推荐 Redocly CLI
  • 简单项目:OpenAPI Generator 的合并功能就足够

通过合适的合并策略,你可以在保持代码组织性的同时,获得单文件的便利性和兼容性。

SOLO Development Guide