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. 其他改进:
   - 修复编译错误和类型问题
   - 完善测试用例
   - 优化项目结构
This commit is contained in:
万物街
2025-08-27 11:24:22 +08:00
parent be07b9ffec
commit 1cd5d3bdef
696 changed files with 36708 additions and 16868 deletions

View File

@@ -0,0 +1,168 @@
# 文档脚本说明
## API 文档自动生成脚本
### 功能说明
`generate-api-docs.js` 脚本用于从后端的 Swagger API 自动生成前端文档。
### 使用方法
#### 1. 安装依赖
```bash
cd admin/docs
npm install
```
#### 2. 运行自动生成脚本
```bash
# 使用默认配置http://localhost:3000
npm run generate-api
# 指定后端服务地址
BACKEND_URL=http://your-backend-url npm run generate-api
# 开发环境快捷命令
npm run generate-api:dev
```
#### 3. 查看生成结果
生成完成后,会在以下目录生成文档:
```
admin/docs/src/wwjcloud/openapi/api/
├── api/ # 前台 API (对应后端 /api/)
│ ├── index.md # 前台 API 概览
│ ├── member.md # 会员管理 API (自动生成)
│ ├── auth.md # 认证 API (自动生成)
│ └── ... # 其他模块 API
└── adminapi/ # 后台 API (对应后端 /adminapi/)
├── index.md # 后台 API 概览
├── member.md # 会员管理 API (自动生成)
├── admin.md # 管理员 API (自动生成)
└── ... # 其他模块 API
```
### 配置说明
#### 环境变量
- `BACKEND_URL`: 后端服务地址,默认为 `http://localhost:3000`
#### API 分组配置
脚本会自动处理以下 API 分组:
- **前台 API**: `/api-json` (对应后端 `/api/` 路径)
- **后台 API**: `/adminapi-json` (对应后端 `/adminapi/` 路径)
### 自动生成内容
生成的文档包含:
- **API 接口列表**: 按模块自动分组
- **请求参数说明**: 自动解析 Swagger 参数定义
- **响应示例**: 统一的响应格式
- **接口描述**: 从 Swagger 自动提取
- **在线测试链接**: 指向后端 Swagger UI
### 目录结构对齐
前端文档目录结构与后端保持一致:
```
后端路径 前端文档路径
/api/ → /api/
/adminapi/ → /adminapi/
```
### 注意事项
1. **后端服务必须运行**: 确保后端服务已启动并可访问
2. **Swagger 配置**: 确保后端已正确配置 Swagger 文档
3. **网络连接**: 确保能够访问后端服务地址
4. **权限问题**: 某些 API 可能需要认证才能访问
### 故障排除
#### 1. 连接失败
```bash
# 检查后端服务是否运行
curl http://localhost:3000/api-json
# 检查网络连接
ping localhost
```
#### 2. 权限问题
如果 API 需要认证,可以在脚本中添加认证头:
```javascript
// 在 generate-api-docs.js 中修改
const response = await axios.get(url, {
headers: {
'Authorization': 'Bearer your-token'
}
});
```
#### 3. 文档生成失败
检查输出目录权限:
```bash
# 确保目录存在且有写权限
ls -la admin/docs/src/wwjcloud/openapi/api/
```
### 自定义配置
如需自定义配置,可以修改 `generate-api-docs.js` 中的 `config` 对象:
```javascript
const config = {
// 修改后端服务地址
backendUrl: process.env.BACKEND_URL || 'http://localhost:3000',
// 修改输出目录
docsDir: path.join(__dirname, '../src/wwjcloud/openapi/api'),
// 添加新的 API 分组
apiGroups: {
// ... 现有配置
custom: {
name: '自定义 API',
swaggerPath: '/custom-json',
outputDir: 'custom',
prefix: '/custom'
}
}
};
```
### 集成到 CI/CD
可以将自动生成脚本集成到 CI/CD 流程中:
```yaml
# GitHub Actions 示例
- name: Generate API Docs
run: |
cd admin/docs
npm install
npm run generate-api
env:
BACKEND_URL: ${{ secrets.BACKEND_URL }}
```
### 相关文件
- `generate-api-docs.js`: 主自动生成脚本
- `package.json`: 脚本命令配置
- `src/wwjcloud/openapi/api/`: 生成的文档目录
- `.vitepress/config/zh.mts`: 侧边栏配置

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* API 文档自动生成脚本
* 从后端 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'),
apiGroups: {
unified: {
name: '统一 API',
swaggerPath: '/docs-json',
outputDir: 'unified',
prefix: ''
}
}
};
/**
* 获取 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(`获取失败: ${error.message}`);
return null;
}
}
/**
* 生成模块文档
*/
function generateModuleDoc(group, tag, paths) {
const moduleName = tag.name;
let markdown = `---
title: ${moduleName} API
description: ${tag.description || `${moduleName} 相关接口`}
---
# ${moduleName} API
::: info ${moduleName}
${tag.description || `${moduleName} 相关接口`}
:::
## 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);
}
});
});
return markdown;
}
/**
* 生成单个 API 文档
*/
function generateApiDoc(path, method, operation) {
const summary = operation.summary || operation.operationId || '未命名接口';
let doc = `### ${summary}\n\n`;
doc += `**接口**: \`${method.toUpperCase()} ${path}\`\n\n`;
if (operation.description) {
doc += `**描述**: ${operation.description}\n\n`;
}
// 请求参数
if (operation.parameters && operation.parameters.length > 0) {
doc += `**参数**:\n\n`;
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`;
}
// 请求体
if (operation.requestBody) {
doc += `**请求体**:\n\n`;
doc += `\`\`\`json\n`;
doc += `{\n`;
doc += ` // 请求数据\n`;
doc += `}\n`;
doc += `\`\`\`\n\n`;
}
// 响应
doc += `**响应**:\n\n`;
doc += `\`\`\`json\n`;
doc += `{\n`;
doc += ` "code": 200,\n`;
doc += ` "message": "success",\n`;
doc += ` "data": {\n`;
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(`保存失败: ${error.message}`);
}
}
/**
* 主函数
*/
async function main() {
console.log('开始自动生成 API 文档...\n');
for (const [key, group] of Object.entries(config.apiGroups)) {
console.log(`处理 ${group.name}...`);
const swaggerData = await getSwaggerJson(group);
if (!swaggerData) {
console.log(`跳过 ${group.name}\n`);
continue;
}
const tags = swaggerData.tags || [];
const paths = swaggerData.paths || {};
// 为每个标签生成文档
for (const tag of tags) {
const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-');
const content = generateModuleDoc(group, tag, paths);
const outputPath = path.join(config.docsDir, group.outputDir, `${moduleName}.md`);
saveDocument(outputPath, content);
}
console.log(`${group.name} 完成\n`);
}
console.log('API 文档生成完成!');
console.log('访问: http://localhost:6173/wwjcloud/openapi/api/');
}
// 运行
if (require.main === module) {
main().catch(error => {
console.error('执行失败:', error);
process.exit(1);
});
}
module.exports = { config, getSwaggerJson, generateModuleDoc };

View File

@@ -0,0 +1,419 @@
#!/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
};