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