Files
wwjcloud-nest-v1/admin-vben/docs/scripts/sync-api-docs.js
wanwu e7a1d6b4d6 🧹 清理重复配置文件
- 删除根目录中重复的 NestJS 配置文件
- 删除 tsconfig.json, tsconfig.build.json, eslint.config.mjs, .prettierrc
- 保留 wwjcloud-nest/ 目录中的完整配置
- 避免配置冲突,确保项目结构清晰
2025-10-14 23:56:20 +08:00

419 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
};