Files

419 lines
10 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* API 文档自动生成脚本
* 使用 OpenAPI 从后端 Swagger 自动生成前端文档
*/
const fs = require('fs');
const path = require('path');
const axios = require('axios');
// 配置
const config = {
// 后端服务地址
backendUrl: process.env.BACKEND_URL || 'http://localhost:3000',
// 文档输出目录
docsDir: path.join(__dirname, '../src/wwjcloud/openapi/api'),
// API 分组配置
apiGroups: {
api: {
name: '前台 API',
swaggerPath: '/api/api-json',
outputDir: 'frontend',
description: '前台用户访问的 API 接口',
prefix: '/api'
},
adminapi: {
name: '后台 API',
swaggerPath: '/api/adminapi-json',
outputDir: 'adminapi',
description: '后台管理员访问的 API 接口',
prefix: '/adminapi'
}
}
};
/**
* 获取 Swagger JSON
*/
async function getSwaggerJson(group) {
try {
const url = `${config.backendUrl}${group.swaggerPath}`;
console.log(`正在获取 ${group.name} API 文档: ${url}`);
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(`获取 ${group.name} API 文档失败:`, error.message);
return null;
}
}
/**
* 生成 API 概览文档
*/
function generateOverview(group) {
return `---
title: ${group.name}
description: ${group.description}
---
# ${group.name}
::: info ${group.name} 说明
${group.description}
:::
## API 基础信息
- **基础路径**: \`${group.prefix}/\`
- **认证方式**: JWT Bearer Token
- **内容类型**: \`application/json\`
- **字符编码**: \`UTF-8\`
## 模块列表
::: tip 自动生成
此文档由后端 Swagger 自动生成包含最新的 API 接口信息
:::
## 认证流程
### 获取 Token
\`\`\`http
POST ${group.prefix}/auth/login
Content-Type: application/json
{
"username": "user@example.com",
"password": "password123"
}
\`\`\`
## 响应格式
所有 API 都遵循统一的响应格式
\`\`\`json
{
"code": 200,
"message": "success",
"data": {
// 具体数据
}
}
\`\`\`
## 错误处理
API 错误响应格式
\`\`\`json
{
"code": 400,
"message": "参数错误",
"data": null
}
\`\`\`
## 状态码说明
| 状态码 | 说明 |
|--------|------|
| 200 | 请求成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 |
| 401 | 未授权 |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
## 开发工具
### Swagger UI
访问 \`${config.backendUrl}${group.prefix}\` 查看完整的 API 文档和在线测试功能。
### 在线测试
每个 API 分组都提供 Swagger UI 界面支持
- API 文档查看
- 在线测试
- 参数验证
- 响应示例
`;
}
/**
* 生成模块 API 文档
*/
function generateModuleDoc(group, tag, paths) {
const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-');
const moduleDesc = tag.description || `${moduleName} 相关接口`;
let markdown = `---
title: ${moduleName} API
description: ${moduleDesc}
---
# ${moduleName} API
::: info ${moduleName} API
${moduleDesc}
:::
## 基础信息
- **模块路径**: \`${group.prefix}/${moduleName.toLowerCase()}/\`
- **认证方式**: JWT Bearer Token部分接口需要
- **内容类型**: \`application/json\`
## API 列表
`;
// 查找该标签下的所有接口
Object.keys(paths).forEach(path => {
Object.keys(paths[path]).forEach(method => {
const operation = paths[path][method];
if (operation.tags && operation.tags.includes(tag.name)) {
markdown += generateApiDoc(path, method, operation, group.prefix);
}
});
});
return markdown;
}
/**
* 生成单个 API 文档
*/
function generateApiDoc(path, method, operation, prefix) {
const summary = operation.summary || operation.operationId || '未命名接口';
const description = operation.description || '';
let doc = `### ${summary}\n\n`;
// 接口地址
doc += `**接口地址**: \`${method.toUpperCase()} ${path}\`\n\n`;
// 接口描述
if (description) {
doc += `**接口描述**: ${description}\n\n`;
}
// 请求参数
if (operation.parameters && operation.parameters.length > 0) {
doc += `**请求参数**:\n\n`;
if (method === 'get') {
doc += `| 参数 | 类型 | 必填 | 说明 |\n`;
doc += `|------|------|------|------|\n`;
operation.parameters.forEach(param => {
const required = param.required ? '是' : '否';
const type = param.type || param.schema?.type || 'string';
doc += `| ${param.name} | ${type} | ${required} | ${param.description || ''} |\n`;
});
doc += `\n`;
} else {
doc += `\`\`\`json\n`;
doc += `{\n`;
operation.parameters.forEach((param, index) => {
const comma = index < operation.parameters.length - 1 ? ',' : '';
const type = param.type || param.schema?.type || 'string';
doc += ` "${param.name}": "${type}"${comma}\n`;
});
doc += `}\n`;
doc += `\`\`\`\n\n`;
}
}
// 请求体
if (operation.requestBody) {
doc += `**请求体**:\n\n`;
doc += `\`\`\`json\n`;
if (operation.requestBody.content && operation.requestBody.content['application/json']) {
const schema = operation.requestBody.content['application/json'].schema;
if (schema && schema.properties) {
doc += `{\n`;
Object.keys(schema.properties).forEach((prop, index) => {
const comma = index < Object.keys(schema.properties).length - 1 ? ',' : '';
const type = schema.properties[prop].type || 'string';
doc += ` "${prop}": "${type}"${comma}\n`;
});
doc += `}\n`;
} else {
doc += `{\n`;
doc += ` // 请求体内容\n`;
doc += `}\n`;
}
} else {
doc += `{\n`;
doc += ` // 请求体内容\n`;
doc += `}\n`;
}
doc += `\`\`\`\n\n`;
}
// 响应示例
if (operation.responses) {
doc += `**响应示例**:\n\n`;
const successResponse = operation.responses['200'] || operation.responses['201'];
if (successResponse) {
doc += `\`\`\`json\n`;
doc += `{\n`;
doc += ` "code": 200,\n`;
doc += ` "message": "success",\n`;
doc += ` "data": {\n`;
if (successResponse.content && successResponse.content['application/json']) {
const schema = successResponse.content['application/json'].schema;
if (schema && schema.properties) {
Object.keys(schema.properties).forEach((prop, index) => {
const comma = index < Object.keys(schema.properties).length - 1 ? ',' : '';
const type = schema.properties[prop].type || 'string';
doc += ` "${prop}": "${type}"${comma}\n`;
});
} else {
doc += ` // 响应数据\n`;
}
} else {
doc += ` // 响应数据\n`;
}
doc += ` }\n`;
doc += `}\n`;
doc += `\`\`\`\n\n`;
}
}
doc += `---\n\n`;
return doc;
}
/**
* 保存文档到文件
*/
function saveDocument(outputPath, content) {
try {
// 确保目录存在
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, content, 'utf8');
console.log(`文档已保存: ${outputPath}`);
} catch (error) {
console.error(`保存文档失败: ${outputPath}`, error.message);
}
}
/**
* 更新侧边栏配置
*/
function updateSidebar(group, tags) {
const sidebarPath = path.join(__dirname, '../.vitepress/config/zh.mts');
let sidebarContent = fs.readFileSync(sidebarPath, 'utf8');
// 生成模块链接
const moduleLinks = tags.map(tag => {
const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-');
return ` { link: 'openapi/api/${group.outputDir}/${moduleName}', text: '${tag.name} API' }`;
}).join(',\n');
// 更新侧边栏配置
const sidebarPattern = new RegExp(`(text: '${group.name}',\\s*items: \\[\\s*\\{[^}]+\\},?\\s*)(\\])`, 's');
const replacement = `$1,\n${moduleLinks}\n $2`;
if (sidebarContent.match(sidebarPattern)) {
sidebarContent = sidebarContent.replace(sidebarPattern, replacement);
fs.writeFileSync(sidebarPath, sidebarContent, 'utf8');
console.log(`侧边栏配置已更新: ${group.name}`);
}
}
/**
* 主函数
*/
async function main() {
console.log('开始自动生成 API 文档...\n');
for (const [key, group] of Object.entries(config.apiGroups)) {
console.log(`处理 ${group.name}...`);
// 获取 Swagger JSON统一
const swaggerData = await getSwaggerJson(group);
if (!swaggerData) {
console.log(`跳过 ${group.name},无法获取数据\n`);
continue;
}
// 基于分组前缀过滤 paths
const filterByPrefix = (doc, prefix) => {
const cloned = JSON.parse(JSON.stringify(doc));
const filteredPaths = {};
Object.keys(cloned.paths || {}).forEach((p) => {
if (p.startsWith(prefix)) filteredPaths[p] = cloned.paths[p];
});
cloned.paths = filteredPaths;
return cloned;
};
const filtered = filterByPrefix(swaggerData, group.prefix + '/');
// 生成概览文档
const overviewContent = generateOverview(group);
const overviewPath = path.join(config.docsDir, group.outputDir, 'index.md');
saveDocument(overviewPath, overviewContent);
// 按标签生成模块文档
const tags = filtered.tags || [];
const paths = filtered.paths || {};
for (const tag of tags) {
const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-');
const moduleContent = generateModuleDoc(group, tag, paths);
const modulePath = path.join(config.docsDir, group.outputDir, `${moduleName}.md`);
saveDocument(modulePath, moduleContent);
}
// 更新侧边栏配置
updateSidebar(group, tags);
console.log(`${group.name} 处理完成\n`);
}
console.log('API 文档自动生成完成!');
console.log('\n下一步');
console.log('1. 启动文档服务: npm run dev');
console.log('2. 访问: http://localhost:6173/wwjcloud/openapi/api/');
}
// 运行脚本
if (require.main === module) {
main().catch(error => {
console.error('脚本执行失败:', error);
process.exit(1);
});
}
module.exports = {
config,
getSwaggerJson,
generateOverview,
generateModuleDoc,
saveDocument,
updateSidebar
};