Files
wwjcloud/tools/structure-validator.js
万物街 127a4db1e3 feat: 完成sys模块迁移,对齐PHP/Java框架
- 重构sys模块架构,严格按admin/api/core分层
- 对齐所有sys实体与数据库表结构
- 实现完整的adminapi控制器,匹配PHP/Java契约
- 修复依赖注入问题,确保服务正确注册
- 添加自动迁移工具和契约验证
- 完善多租户支持和审计功能
- 统一命名规范,与PHP业务逻辑保持一致
2025-09-21 21:29:28 +08:00

342 lines
9.2 KiB
JavaScript

#!/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;