Files
wwjcloud/admin/docs/scripts/sync-api-docs.js
万物街 1cd5d3bdef feat: 完成 NestJS 后端核心底座开发 (M1-M6) 和 Ant Design Vue 前端迁移
主要更新:
1. 后端核心底座完成 (M1-M6):
   - 健康检查、指标监控、分布式锁
   - 事件总线、队列系统、事务管理
   - 安全守卫、多租户隔离、存储适配器
   - 审计日志、配置管理、多语言支持

2. 前端迁移到 Ant Design Vue:
   - 从 Element Plus 迁移到 Ant Design Vue
   - 完善 system 模块 (role/menu/dept)
   - 修复依赖和配置问题

3. 文档完善:
   - AI 开发工作流文档
   - 架构约束和开发规范
   - 项目进度跟踪

4. 其他改进:
   - 修复编译错误和类型问题
   - 完善测试用例
   - 优化项目结构
2025-08-27 11:24:22 +08:00

419 lines
10 KiB
JavaScript
Raw 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
};