feat: 完成sys模块迁移,对齐PHP/Java框架
- 重构sys模块架构,严格按admin/api/core分层 - 对齐所有sys实体与数据库表结构 - 实现完整的adminapi控制器,匹配PHP/Java契约 - 修复依赖注入问题,确保服务正确注册 - 添加自动迁移工具和契约验证 - 完善多租户支持和审计功能 - 统一命名规范,与PHP业务逻辑保持一致
This commit is contained in:
342
tools/structure-validator.js
Normal file
342
tools/structure-validator.js
Normal file
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* NestJS项目结构验证器
|
||||
* 检查项目目录结构、分层规范、命名规范等
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class StructureValidator {
|
||||
constructor() {
|
||||
this.projectRoot = process.cwd();
|
||||
this.srcRoot = path.join(this.projectRoot, 'wwjcloud', 'src');
|
||||
this.commonRoot = path.join(this.srcRoot, 'common');
|
||||
this.issues = [];
|
||||
this.stats = {
|
||||
modules: 0,
|
||||
controllers: 0,
|
||||
services: 0,
|
||||
entities: 0,
|
||||
dtos: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加问题记录
|
||||
*/
|
||||
addIssue(type, message, path = '') {
|
||||
this.issues.push({
|
||||
type,
|
||||
message,
|
||||
path,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查基础目录结构
|
||||
*/
|
||||
checkBaseStructure() {
|
||||
console.log('🏗️ 检查基础目录结构...');
|
||||
|
||||
const requiredDirs = [
|
||||
'wwjcloud/src',
|
||||
'wwjcloud/src/common',
|
||||
'wwjcloud/src/config',
|
||||
'wwjcloud/src/core',
|
||||
'wwjcloud/src/vendor'
|
||||
];
|
||||
|
||||
for (const dir of requiredDirs) {
|
||||
const fullPath = path.join(this.projectRoot, dir);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
this.addIssue('structure', `缺少必需目录: ${dir}`, fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块结构
|
||||
*/
|
||||
checkModuleStructure() {
|
||||
console.log('📦 检查模块结构...');
|
||||
|
||||
if (!fs.existsSync(this.commonRoot)) {
|
||||
this.addIssue('structure', 'common目录不存在', this.commonRoot);
|
||||
return;
|
||||
}
|
||||
|
||||
const modules = fs.readdirSync(this.commonRoot, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => entry.name);
|
||||
|
||||
this.stats.modules = modules.length;
|
||||
|
||||
for (const moduleName of modules) {
|
||||
this.validateModule(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证单个模块
|
||||
*/
|
||||
validateModule(moduleName) {
|
||||
const modulePath = path.join(this.commonRoot, moduleName);
|
||||
const moduleFile = path.join(modulePath, `${moduleName}.module.ts`);
|
||||
|
||||
// 检查模块文件
|
||||
if (!fs.existsSync(moduleFile)) {
|
||||
this.addIssue('module', `缺少模块文件: ${moduleName}.module.ts`, moduleFile);
|
||||
}
|
||||
|
||||
// 检查标准目录结构
|
||||
const expectedDirs = ['controllers', 'services', 'entities', 'dto'];
|
||||
const optionalDirs = ['guards', 'decorators', 'interfaces', 'enums'];
|
||||
|
||||
for (const dir of expectedDirs) {
|
||||
const dirPath = path.join(modulePath, dir);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
this.addIssue('structure', `模块 ${moduleName} 缺少 ${dir} 目录`, dirPath);
|
||||
} else {
|
||||
this.validateModuleDirectory(moduleName, dir, dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查控制器分层
|
||||
this.checkControllerLayers(moduleName, modulePath);
|
||||
|
||||
// 检查服务分层
|
||||
this.checkServiceLayers(moduleName, modulePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证模块目录
|
||||
*/
|
||||
validateModuleDirectory(moduleName, dirType, dirPath) {
|
||||
const files = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const file of files) {
|
||||
if (file.isFile() && file.name.endsWith('.ts')) {
|
||||
this.validateFileName(moduleName, dirType, file.name, path.join(dirPath, file.name));
|
||||
|
||||
// 统计文件数量
|
||||
if (dirType === 'controllers') this.stats.controllers++;
|
||||
else if (dirType === 'services') this.stats.services++;
|
||||
else if (dirType === 'entities') this.stats.entities++;
|
||||
else if (dirType === 'dto') this.stats.dtos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件命名
|
||||
*/
|
||||
validateFileName(moduleName, dirType, fileName, filePath) {
|
||||
const expectedPatterns = {
|
||||
controllers: /^[a-z][a-zA-Z0-9]*\.controller\.ts$/,
|
||||
services: /^[a-z][a-zA-Z0-9]*\.service\.ts$/,
|
||||
entities: /^[a-z][a-zA-Z0-9]*\.entity\.ts$/,
|
||||
dto: /^[A-Z][a-zA-Z0-9]*Dto\.ts$/
|
||||
};
|
||||
|
||||
const pattern = expectedPatterns[dirType];
|
||||
if (pattern && !pattern.test(fileName)) {
|
||||
this.addIssue('naming',
|
||||
`文件命名不符合规范: ${fileName} (应符合 ${pattern})`,
|
||||
filePath
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查控制器分层
|
||||
*/
|
||||
checkControllerLayers(moduleName, modulePath) {
|
||||
const controllersPath = path.join(modulePath, 'controllers');
|
||||
if (!fs.existsSync(controllersPath)) return;
|
||||
|
||||
const expectedLayers = ['adminapi', 'api'];
|
||||
let hasLayers = false;
|
||||
|
||||
for (const layer of expectedLayers) {
|
||||
const layerPath = path.join(controllersPath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
hasLayers = true;
|
||||
this.validateLayerFiles(moduleName, 'controllers', layer, layerPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有直接在controllers目录下的文件
|
||||
const directFiles = fs.readdirSync(controllersPath, { withFileTypes: true })
|
||||
.filter(entry => entry.isFile() && entry.name.endsWith('.controller.ts'));
|
||||
|
||||
if (directFiles.length > 0 && hasLayers) {
|
||||
this.addIssue('structure',
|
||||
`模块 ${moduleName} 的控制器既有分层又有直接文件,建议统一结构`,
|
||||
controllersPath
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务分层
|
||||
*/
|
||||
checkServiceLayers(moduleName, modulePath) {
|
||||
const servicesPath = path.join(modulePath, 'services');
|
||||
if (!fs.existsSync(servicesPath)) return;
|
||||
|
||||
const expectedLayers = ['admin', 'api', 'core'];
|
||||
|
||||
for (const layer of expectedLayers) {
|
||||
const layerPath = path.join(servicesPath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
this.validateLayerFiles(moduleName, 'services', layer, layerPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证分层文件
|
||||
*/
|
||||
validateLayerFiles(moduleName, dirType, layer, layerPath) {
|
||||
const files = fs.readdirSync(layerPath, { withFileTypes: true })
|
||||
.filter(entry => entry.isFile() && entry.name.endsWith('.ts'));
|
||||
|
||||
if (files.length === 0) {
|
||||
this.addIssue('structure',
|
||||
`模块 ${moduleName} 的 ${dirType}/${layer} 目录为空`,
|
||||
layerPath
|
||||
);
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
this.validateFileName(moduleName, dirType, file.name, path.join(layerPath, file.name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查依赖关系
|
||||
*/
|
||||
checkDependencies() {
|
||||
console.log('🔗 检查依赖关系...');
|
||||
|
||||
// 这里可以添加更复杂的依赖关系检查
|
||||
// 例如检查循环依赖、不当的跨层依赖等
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查代码质量
|
||||
*/
|
||||
checkCodeQuality() {
|
||||
console.log('✨ 检查代码质量...');
|
||||
|
||||
// 检查是否有空的类或方法
|
||||
// 检查是否有TODO注释
|
||||
// 检查是否有硬编码值等
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成报告
|
||||
*/
|
||||
generateReport() {
|
||||
console.log('\n📊 验证报告');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
// 统计信息
|
||||
console.log('📈 项目统计:');
|
||||
console.log(` 模块数量: ${this.stats.modules}`);
|
||||
console.log(` 控制器数量: ${this.stats.controllers}`);
|
||||
console.log(` 服务数量: ${this.stats.services}`);
|
||||
console.log(` 实体数量: ${this.stats.entities}`);
|
||||
console.log(` DTO数量: ${this.stats.dtos}`);
|
||||
|
||||
// 问题分类统计
|
||||
const issuesByType = this.issues.reduce((acc, issue) => {
|
||||
acc[issue.type] = (acc[issue.type] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
console.log('\n🚨 问题统计:');
|
||||
if (Object.keys(issuesByType).length === 0) {
|
||||
console.log(' ✅ 未发现问题');
|
||||
} else {
|
||||
for (const [type, count] of Object.entries(issuesByType)) {
|
||||
console.log(` ${type}: ${count} 个问题`);
|
||||
}
|
||||
}
|
||||
|
||||
// 详细问题列表
|
||||
if (this.issues.length > 0) {
|
||||
console.log('\n📋 详细问题列表:');
|
||||
|
||||
const groupedIssues = this.issues.reduce((acc, issue) => {
|
||||
if (!acc[issue.type]) acc[issue.type] = [];
|
||||
acc[issue.type].push(issue);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const [type, issues] of Object.entries(groupedIssues)) {
|
||||
console.log(`\n${type.toUpperCase()} 问题:`);
|
||||
for (const issue of issues) {
|
||||
console.log(` ❌ ${issue.message}`);
|
||||
if (issue.path) {
|
||||
console.log(` 路径: ${issue.path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 建议
|
||||
console.log('\n💡 改进建议:');
|
||||
if (this.issues.length === 0) {
|
||||
console.log(' 🎉 项目结构良好,继续保持!');
|
||||
} else {
|
||||
console.log(' 1. 优先解决结构性问题');
|
||||
console.log(' 2. 统一命名规范');
|
||||
console.log(' 3. 完善缺失的文件和目录');
|
||||
console.log(' 4. 定期运行此工具进行检查');
|
||||
}
|
||||
|
||||
return this.issues.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行验证
|
||||
*/
|
||||
async run() {
|
||||
console.log('🔍 NestJS项目结构验证器');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
try {
|
||||
this.checkBaseStructure();
|
||||
this.checkModuleStructure();
|
||||
this.checkDependencies();
|
||||
this.checkCodeQuality();
|
||||
|
||||
const isValid = this.generateReport();
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
if (isValid) {
|
||||
console.log('✅ 验证通过!项目结构符合规范。');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('❌ 验证失败!发现结构问题,请查看上述报告。');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 验证过程中出现错误:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行验证器
|
||||
if (require.main === module) {
|
||||
const validator = new StructureValidator();
|
||||
validator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = StructureValidator;
|
||||
Reference in New Issue
Block a user