一键生成 OpenAPI 接口:让前端开发更高效
前言
在现代前端开发中,与后端 API 的对接是一项重复且容易出错的工作。手动编写接口调用代码不仅效率低下,还容易因为接口变更导致前后端不一致。本文将介绍一个基于 OpenAPI/Swagger 的自动化接口生成工具,帮助你告别手写接口代码的烦恼。
功能特性
这个工具具有以下核心功能:
- ✅ 多源支持:可同时配置多个 API 源,一次性生成所有接口
- ✅ 自动下载:从 Swagger 文档地址自动下载 OpenAPI 规范
- ✅ 智能处理:自动移除路径前缀,按目录结构重组 tags
- ✅ 目录化管理:将生成的接口按模块转换为目录结构,便于维护
- ✅ 类型安全:基于 TypeScript,提供完整的类型提示
快速开始
1. 项目结构
首先,确保你的项目具有以下结构:
your-project/
├── scripts/
│ ├── generate-api.js # 主脚本
│ └── openapi.config.js # 配置文件
├── config/
│ └── config.ts # UmiMax 配置
└── src/
├── api/ # 临时存放下载的 OpenAPI 文档
└── services/ # 生成的接口代码
2. 安装依赖
确保项目已安装必要的依赖:
npm install @umijs/max --save-dev
# 或
pnpm add @umijs/max -D
3. 配置 API 源
在 scripts/openapi.config.js 中配置你的 API 源:
module.exports = [
{
// API 源名称(用于生成目录名)
name: 'zelos-api',
// Swagger 文档地址
url: 'https://openapi.apipost.net/swagger/v3/5ac414db5851000?locale=zh-cn',
// 生成的服务目录(相对于 src/services/)
outputDir: 'zelos-api',
// 需要移除的路径前缀(支持正则表达式)
removePrefix: '\{\{label-server\}\}',
},
// 可以添加更多 API 源
{
name: 'user-api',
url: 'https://api.example.com/swagger/v3/docs',
outputDir: 'user-api',
removePrefix: '/api/v1',
},
];
4. 配置 package.json
在 package.json 中添加快捷命令:
{
"scripts": {
"openapi": "node scripts/generate-api.js"
}
}
5. 执行生成
运行命令即可生成接口:
npm run openapi
# 或
pnpm openapi
工作原理
整个工具分为三个核心步骤:
步骤 1:下载并处理 OpenAPI 文档
async function downloadAndProcess(config) {
// 1. 从配置的 URL 下载 Swagger 文档
// 2. 移除路径中的指定前缀(如 {{label-server}})
// 3. 根据路径第一级目录自动设置 tags
// 4. 保存处理后的文档到 src/api/
}
处理示例:
原始路径:
{{label-server}}/user/login
{{label-server}}/user/profile
{{label-server}}/order/list
处理后:
/user/login → tag: user
/user/profile → tag: user
/order/list → tag: order
步骤 2:调用 OpenAPI 生成工具
function generateAPI(config) {
// 1. 临时修改 config/config.ts 配置
// 2. 调用 npx max openapi 生成接口代码
// 3. 恢复原配置文件
}
这一步会生成类似这样的文件:
src/services/zelos-api/
├── user.ts
├── order.ts
├── product.ts
├── index.ts
└── typings.d.ts
步骤 3:转换为目录结构
function convertToDirectories(config) {
// 将 user.ts → user/index.ts
// 将 order.ts → order/index.ts
// 更新主 index.ts 导出
}
最终结构:
src/services/zelos-api/
├── user/
│ └── index.ts
├── order/
│ └── index.ts
├── product/
│ └── index.ts
├── index.ts
└── typings.d.ts
使用生成的接口
生成完成后,你可以在项目中这样使用:
import zelosApi from '@/services/zelos-api';
// 调用用户登录接口
const login = async (username: string, password: string) => {
try {
const response = await zelosApi.user.postUserLogin({
username,
password,
});
return response;
} catch (error) {
console.error('登录失败', error);
}
};
// 获取订单列表
const getOrders = async () => {
const response = await zelosApi.order.getOrderList({
page: 1,
pageSize: 10,
});
return response.data;
};
配置说明
配置项详解
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | ✅ | API 源名称,用于生成目录和日志标识 |
url | string | ✅ | Swagger 文档的完整 URL |
outputDir | string | ✅ | 生成代码的输出目录(相对于 src/services/) |
removePrefix | string | ✅ | 需要从路径中移除的前缀(支持正则表达式) |
removePrefix 使用技巧
removePrefix 支持正则表达式,可以灵活处理各种前缀:
// 移除固定前缀
removePrefix: '/api/v1'
// 移除变量前缀(需要转义特殊字符)
removePrefix: '\{\{server\}\}'
// 移除多个可能的前缀
removePrefix: '(/api/v1|/api/v2)'
高级用法
1. 多环境配置
你可以为不同环境创建不同的配置文件:
// openapi.config.dev.js
module.exports = [
{
name: 'api',
url: 'https://dev-api.example.com/swagger',
outputDir: 'api',
removePrefix: '/dev',
},
];
// openapi.config.prod.js
module.exports = [
{
name: 'api',
url: 'https://api.example.com/swagger',
outputDir: 'api',
removePrefix: '/v1',
},
];
然后在 package.json 中添加:
{
"scripts": {
"openapi:dev": "node scripts/generate-api.js --config=openapi.config.dev.js",
"openapi:prod": "node scripts/generate-api.js --config=openapi.config.prod.js"
}
}
2. 自定义生成后处理
你可以在脚本中添加自定义的后处理逻辑:
// 在 convertToDirectories 函数后添加
function postProcess(config) {
const apiDir = path.join(PROJECT_ROOT, 'src/services', config.outputDir);
// 例如:添加自定义的请求拦截器
const interceptorContent = `
import { request } from '@umijs/max';
// 自定义请求拦截器
request.interceptors.request.use((config) => {
// 添加 token
config.headers.Authorization = localStorage.getItem('token');
return config;
});
`;
fs.writeFileSync(
path.join(apiDir, 'interceptor.ts'),
interceptorContent
);
}
3. 集成到 CI/CD
在持续集成流程中自动生成接口:
# .github/workflows/generate-api.yml
name: Generate API
on:
schedule:
- cron: '0 2 * * *' # 每天凌晨 2 点执行
workflow_dispatch: # 支持手动触发
jobs:
generate:
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: pnpm install
- name: Generate API
run: pnpm openapi
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
commit-message: 'chore: update API definitions'
title: '🤖 自动更新 API 定义'
branch: auto-update-api
常见问题
Q1: 生成失败,提示 "配置文件错误"
A: 检查 openapi.config.js 是否正确导出数组,且每个配置项都包含必填字段。
Q2: 生成的接口类型不准确
A: 这通常是 Swagger 文档本身的问题。建议:
- 检查后端 Swagger 文档的类型定义
- 联系后端开发完善文档
- 必要时可以手动修改生成的
typings.d.ts
Q3: 如何处理接口版本变更?
A:
- 为不同版本创建不同的
outputDir - 或者使用 Git 分支管理不同版本的接口代码
Q4: 生成的代码能否自定义?
A: 可以通过修改 UmiMax 的 OpenAPI 配置来自定义生成模板,参考 UmiMax OpenAPI 文档。
最佳实践
- 定期更新:建议每天或每次后端接口变更后重新生成
- 版本控制:将生成的代码纳入 Git 管理,便于追踪变更
- 代码审查:生成后检查 diff,确保变更符合预期
- 文档同步:要求后端团队及时更新 Swagger 文档
- 类型检查:开启 TypeScript 严格模式,充分利用类型安全
总结
这个 OpenAPI 自动化工具能够显著提升前端开发效率,减少手动编写接口代码的工作量。通过配置化管理多个 API 源,你可以轻松维护复杂的微服务架构项目。
核心优势:
- 🚀 效率提升:从手写到自动化,节省 80% 的接口对接时间
- 🛡️ 类型安全:TypeScript 类型提示,减少运行时错误
- 📦 易于维护:目录化结构,清晰的模块划分
- 🔄 持续同步:接口变更自动同步,保持前后端一致
希望这个工具能帮助你的团队提升开发效率!如果有任何问题或建议,欢迎在评论区讨论。
相关资源:
源码
generate-api.js
#!/usr/bin/env node
/**
* 一键生成 OpenAPI 接口(支持多源配置)
* 1. 下载并处理 Swagger 文档
* 2. 调用 OpenAPI 生成工具
* 3. 转换为目录结构
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// 加载配置
const apiConfigs = require('./openapi.config.js');
const PROJECT_ROOT = path.join(__dirname, '..');
// 步骤1: 下载并处理 OpenAPI 文档
async function downloadAndProcess(config) {
console.log(`📥 [${config.name}] 正在下载 OpenAPI 文档...`);
const openAPIData = await new Promise((resolve, reject) => {
https
.get(config.url, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
});
})
.on('error', reject);
});
console.log(`🔧 [${config.name}] 正在处理路径和标签...`);
const paths = openAPIData.paths;
const newPaths = {};
const prefixRegex = new RegExp(config.removePrefix, 'g');
Object.keys(paths).forEach((path) => {
// 移除路径中的配置的前缀
const newPath = path.replace(prefixRegex, '');
const pathMethods = paths[path];
// 动态识别所有 HTTP 方法并重新设置 tags
Object.keys(pathMethods).forEach((method) => {
// 跳过非方法属性
if (
method.startsWith('x-') ||
['parameters', 'servers', 'summary', 'description'].includes(method)
) {
return;
}
// 检查是否为有效的 HTTP 方法
if (
pathMethods[method] &&
typeof pathMethods[method] === 'object' &&
'responses' in pathMethods[method]
) {
// 根据路径第一级目录设置 tag
const pathParts = newPath
.split('/')
.filter((part) => part && !part.startsWith('{'));
const firstDir = pathParts[0] || 'root';
pathMethods[method].tags = [firstDir];
}
});
newPaths[newPath] = pathMethods;
});
openAPIData.paths = newPaths;
// 保存处理后的文档
const outputPath = path.join(PROJECT_ROOT, 'src/api', `${config.name}.json`);
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, JSON.stringify(openAPIData, null, 2));
console.log(`✅ [${config.name}] 文档处理完成`);
return outputPath;
}
// 步骤2: 调用 OpenAPI 生成工具
function generateAPI(config) {
console.log(`\n🔨 [${config.name}] 正在生成接口代码...`);
try {
// 临时更新配置文件以使用当前 API 源
const configPath = path.join(PROJECT_ROOT, 'config/config.ts');
const configContent = fs.readFileSync(configPath, 'utf-8');
// 备份原配置
const backupPath = configPath + '.backup';
fs.writeFileSync(backupPath, configContent);
try {
// 更新 schemaPath 和 projectName
const updatedConfig = configContent
.replace(
/schemaPath:\s*join\([^)]+\)/,
`schemaPath: join(__dirname, '../src/api/${config.name}.json')`,
)
.replace(
/projectName:\s*['"][^'"]*['"]/,
`projectName: '${config.outputDir}'`,
);
fs.writeFileSync(configPath, updatedConfig);
// 执行生成命令
execSync('npx max openapi', { stdio: 'inherit', cwd: PROJECT_ROOT });
console.log(`✅ [${config.name}] 接口代码生成完成`);
} finally {
// 恢复原配置
fs.writeFileSync(configPath, configContent);
fs.unlinkSync(backupPath);
}
} catch (error) {
console.error(`❌ [${config.name}] 生成失败:`, error.message);
throw error;
}
}
// 步骤3: 转换为目录结构
function convertToDirectories(config) {
console.log(`\n📁 [${config.name}] 正在转换为目录结构...`);
const apiDir = path.join(PROJECT_ROOT, 'src/services', config.outputDir);
if (!fs.existsSync(apiDir)) {
console.log(`⚠️ [${config.name}] 目录不存在: ${apiDir}`);
return;
}
const files = fs.readdirSync(apiDir);
const filesToConvert = files.filter(
(file) =>
file.endsWith('.ts') && file !== 'index.ts' && file !== 'typings.d.ts',
);
if (filesToConvert.length === 0) {
console.log(`⚠️ [${config.name}] 没有需要转换的文件`);
return;
}
const modules = [];
filesToConvert.forEach((file) => {
const moduleName = file.replace('.ts', '');
const filePath = path.join(apiDir, file);
const dirPath = path.join(apiDir, moduleName);
const newFilePath = path.join(dirPath, 'index.ts');
// 读取并移动文件
const content = fs.readFileSync(filePath, 'utf-8');
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFileSync(newFilePath, content);
fs.unlinkSync(filePath);
modules.push(moduleName);
console.log(` ✓ ${file} → ${moduleName}/index.ts`);
});
// 更新主 index.ts
if (modules.length > 0) {
const imports = modules
.sort()
.map((m) => `import * as ${m} from './${m}';`)
.join('\n');
const exports = modules
.sort()
.map((m) => ` ${m},`)
.join('\n');
const indexContent = `// @ts-ignore
/* eslint-disable */
// API 更新时间:
// API 唯一标识:
${imports}
export default {
${exports}
};
`;
fs.writeFileSync(path.join(apiDir, 'index.ts'), indexContent);
console.log(` ✓ 更新 index.ts`);
}
console.log(`✅ [${config.name}] 目录结构转换完成`);
}
// 主流程
async function main() {
console.log('🚀 开始生成 OpenAPI 接口\n');
if (!Array.isArray(apiConfigs) || apiConfigs.length === 0) {
console.error(
'❌ 配置文件错误:请在 scripts/openapi.config.js 中配置至少一个 API 源',
);
process.exit(1);
}
console.log(`📋 共找到 ${apiConfigs.length} 个 API 源\n`);
try {
// 处理每个 API 源
for (const config of apiConfigs) {
console.log(`${'='.repeat(60)}`);
console.log(`开始处理: ${config.name}`);
console.log(`${'='.repeat(60)}\n`);
// 验证配置
if (
!config.name ||
!config.url ||
!config.outputDir ||
!config.removePrefix
) {
console.error(`❌ [${config.name || '未命名'}] 配置不完整,跳过`);
continue;
}
await downloadAndProcess(config);
generateAPI(config);
convertToDirectories(config);
console.log(`\n✅ ${config.name} 处理完成\n`);
}
console.log('='.repeat(60));
console.log('✨ 全部完成!');
console.log('='.repeat(60));
} catch (error) {
console.error('\n❌ 生成失败:', error.message);
process.exit(1);
}
}
main();
openapi.config.js
/**
* OpenAPI 配置文件
* 支持配置多个 API 源
*/
module.exports = [
{
// API 源名称(用于生成目录名)
name: 'zelos-api',
// Swagger 文档地址
url: 'https://openapi.apipost.net/swagger/v3/5ac414db5851000?locale=zh-cn',
// 生成的服务目录(相对于 src/services/)
outputDir: 'zelos-api',
// 需要移除的路径前缀(支持正则表达式字符串)
removePrefix: '\\{\\{label-server\\}\\}',
},
// 如果有其他 API 源,在这里添加
// {
// name: 'another-api',
// url: 'https://openapi.apipost.net/swagger/v3/5ad464cd8052000?locale=zh-cn',
// outputDir: 'another-api',
// removePrefix: '\\{\\{dap-server\\}\\}',
// },
];