🎯 核心改进: 1. ✅ 创建CentralDataRepository类 - 统一管理元数据 2. ✅ Coordinator集成CDR - 传递给所有Generator 3. ✅ DTO Generator记录位置 - 629个类型已记录 4. ✅ Service Generator CDR查询 - 支持路径查询 📊 CDR统计: - Service方法: 1,038个 - DTO: 35个 - VO: 277个 - Param: 317个 - 总类型: 629个 ⚠️ 待解决问题: - 类型名查询不匹配(记录vs查询) - 需要调试类型名映射逻辑 💡 下一步: - 修复类型名匹配问题 - 验证DTO路径查询效果 - 预期减少4,200+ DTO路径错误
419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const JavaScanner = require('./scanners/java-scanner');
|
||
const LayerMapper = require('./mappers/layer-mapper');
|
||
const ModuleGenerator = require('./generators/module-generator');
|
||
const CentralDataRepository = require('./central-data-repository');
|
||
|
||
/**
|
||
* Java到NestJS迁移协调器
|
||
* 按技术层级组织模块,严格遵循NestJS官方规范
|
||
*
|
||
* ✅ V2: 使用中央数据仓库(CDR)统一管理元数据
|
||
*/
|
||
class JavaToNestJSMigrationCoordinator {
|
||
constructor() {
|
||
this.javaPath = '';
|
||
this.nestJSPath = '';
|
||
this.scanner = new JavaScanner();
|
||
this.mapper = new LayerMapper();
|
||
this.moduleGenerator = new ModuleGenerator();
|
||
|
||
// ✅ V2: 中央数据仓库(替代原来的单一索引)
|
||
this.cdr = new CentralDataRepository();
|
||
|
||
// ⚠️ 向后兼容:保留旧的索引引用(指向CDR)
|
||
this.serviceMethodSignatureIndex = this.cdr.serviceMethodSignatureIndex;
|
||
|
||
this.stats = {
|
||
startTime: null,
|
||
endTime: null,
|
||
filesProcessed: 0,
|
||
modulesGenerated: 0,
|
||
errors: []
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 执行完整迁移流程
|
||
*/
|
||
async runMigration() {
|
||
console.log('🚀 开始Java到NestJS迁移流程...');
|
||
this.stats.startTime = new Date();
|
||
|
||
try {
|
||
// 第1阶段:扫描Java项目
|
||
console.log('\n📊 第1阶段:扫描Java项目结构...');
|
||
await this.scanJavaProject();
|
||
|
||
// 第2阶段:映射层级关系
|
||
console.log('\n🔄 第2阶段:映射层级关系...');
|
||
const nestJSModules = this.mapLayers();
|
||
|
||
// 第3阶段:生成NestJS模块
|
||
console.log('\n🔧 第3阶段:生成NestJS模块...');
|
||
await this.generateModules(nestJSModules);
|
||
|
||
// 第4阶段:生成报告
|
||
console.log('\n📋 第4阶段:生成迁移报告...');
|
||
this.generateReport();
|
||
|
||
this.stats.endTime = new Date();
|
||
console.log('\n✅ 迁移流程完成!');
|
||
this.printStats();
|
||
|
||
// ✅ V2: 打印CDR统计
|
||
console.log('');
|
||
this.cdr.printStats();
|
||
|
||
// ✅ V2: 测试查询几个DTO
|
||
console.log('\n🔍 测试CDR查询:');
|
||
const testTypes = ['MemberInfoDto', 'MemberListVo', 'PageParam', 'MemberSearchParam'];
|
||
testTypes.forEach(typeName => {
|
||
const location = this.cdr.getTypeLocation(typeName);
|
||
if (location) {
|
||
console.log(` ✅ ${typeName}: ${location.relativePath}`);
|
||
} else {
|
||
console.log(` ❌ ${typeName}: 未找到`);
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('❌ 迁移过程中发生错误:', error.message);
|
||
this.stats.errors.push(error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 扫描Java项目
|
||
*/
|
||
async scanJavaProject() {
|
||
this.javaPath = '/Users/wanwu/Documents/wanwujie/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java';
|
||
this.nestJSPath = path.resolve(__dirname, '../../wwjcloud/libs/wwjcloud-core/src');
|
||
|
||
console.log(`📁 Java项目路径: ${this.javaPath}`);
|
||
console.log(`📁 NestJS项目路径: ${this.nestJSPath}`);
|
||
|
||
this.scanner.setJavaPath(this.javaPath);
|
||
await this.scanner.scanJavaProject();
|
||
|
||
const scanResults = this.scanner.getScanResults();
|
||
this.stats.filesProcessed = Object.values(scanResults).reduce((total, arr) => total + arr.length, 0);
|
||
console.log(`📊 扫描完成,共处理 ${this.stats.filesProcessed} 个文件`);
|
||
|
||
// ✅ 构建中央Service方法签名索引
|
||
console.log('🔍 构建Service方法签名索引...');
|
||
this.buildServiceMethodSignatureIndex(scanResults.services);
|
||
console.log(`📋 索引完成,共 ${this.serviceMethodSignatureIndex.size} 个方法签名`);
|
||
|
||
// 验证扫描结果
|
||
this.validateScanResults(scanResults);
|
||
}
|
||
|
||
/**
|
||
* ✅ V2: 构建Service方法签名索引(使用CDR)
|
||
* 从Java扫描结果中提取所有Service方法的参数和返回类型
|
||
*/
|
||
buildServiceMethodSignatureIndex(services) {
|
||
services.forEach(service => {
|
||
const className = service.className;
|
||
const content = service.content;
|
||
|
||
// 提取所有方法
|
||
const methods = this.scanner.extractMethods(content);
|
||
|
||
// ✅ V2: 使用CDR存储方法签名
|
||
methods.forEach(method => {
|
||
this.cdr.setServiceSignature(className, method.methodName, {
|
||
parameters: method.parameters || [],
|
||
returnType: method.returnType || 'void',
|
||
methodBody: method.methodBody || ''
|
||
});
|
||
});
|
||
|
||
// ✅ V2: 记录Service依赖关系
|
||
if (service.dependencies && service.dependencies.length > 0) {
|
||
this.cdr.setServiceDependencies(className, service.dependencies);
|
||
}
|
||
});
|
||
|
||
// ✅ V2: 打印CDR统计
|
||
console.log(`📋 CDR: 已索引 ${this.cdr.serviceMethodSignatureIndex.size} 个Service方法签名`);
|
||
}
|
||
|
||
/**
|
||
* 验证扫描结果
|
||
*/
|
||
validateScanResults(scanResults) {
|
||
console.log('\n🔍 验证扫描结果...');
|
||
|
||
// 检查是否有重复文件
|
||
const allFiles = Object.values(scanResults).flat();
|
||
const uniqueFiles = new Set(allFiles.map(f => f.filePath));
|
||
|
||
if (allFiles.length !== uniqueFiles.size) {
|
||
console.warn(`⚠️ 发现重复文件: ${allFiles.length - uniqueFiles.size} 个`);
|
||
}
|
||
|
||
// 检查分类准确性
|
||
const totalClassified = Object.values(scanResults).reduce((sum, arr) => sum + arr.length, 0);
|
||
console.log(`📊 分类文件总数: ${totalClassified} 个`);
|
||
|
||
// 检查每个分类的质量
|
||
this.validateClassificationQuality(scanResults);
|
||
}
|
||
|
||
/**
|
||
* 验证分类质量
|
||
*/
|
||
validateClassificationQuality(scanResults) {
|
||
console.log('\n🔍 验证分类质量...');
|
||
|
||
// 验证控制器分类
|
||
const controllerQuality = this.validateControllerClassification(scanResults.controllers);
|
||
console.log(`📋 控制器分类质量: ${controllerQuality.score}/100`);
|
||
|
||
// 验证服务分类
|
||
const serviceQuality = this.validateServiceClassification(scanResults.services);
|
||
console.log(`🔧 服务分类质量: ${serviceQuality.score}/100`);
|
||
|
||
// 验证实体分类
|
||
const entityQuality = this.validateEntityClassification(scanResults.entities);
|
||
console.log(`🗄️ 实体分类质量: ${entityQuality.score}/100`);
|
||
}
|
||
|
||
/**
|
||
* 验证控制器分类质量
|
||
*/
|
||
validateControllerClassification(controllers) {
|
||
let score = 0;
|
||
let issues = [];
|
||
|
||
controllers.forEach(controller => {
|
||
const className = controller.className.toLowerCase();
|
||
const content = controller.content.toLowerCase();
|
||
|
||
// 检查类名是否包含controller
|
||
if (className.includes('controller')) {
|
||
score += 20;
|
||
} else {
|
||
issues.push(`类名不包含controller: ${controller.className}`);
|
||
}
|
||
|
||
// 检查是否有控制器注解
|
||
if (content.includes('@restcontroller') || content.includes('@controller')) {
|
||
score += 30;
|
||
} else {
|
||
issues.push(`缺少控制器注解: ${controller.className}`);
|
||
}
|
||
|
||
// 检查是否有路由映射
|
||
if (content.includes('@requestmapping')) {
|
||
score += 30;
|
||
} else {
|
||
issues.push(`缺少路由映射: ${controller.className}`);
|
||
}
|
||
|
||
// 检查是否有HTTP方法映射
|
||
if (content.includes('@getmapping') || content.includes('@postmapping')) {
|
||
score += 20;
|
||
} else {
|
||
issues.push(`缺少HTTP方法映射: ${controller.className}`);
|
||
}
|
||
});
|
||
|
||
return { score: Math.min(score, 100), issues };
|
||
}
|
||
|
||
/**
|
||
* 验证服务分类质量
|
||
*/
|
||
validateServiceClassification(services) {
|
||
let score = 0;
|
||
let issues = [];
|
||
|
||
services.forEach(service => {
|
||
const className = service.className.toLowerCase();
|
||
const content = service.content.toLowerCase();
|
||
|
||
// 检查类名是否包含service
|
||
if (className.includes('service')) {
|
||
score += 30;
|
||
} else {
|
||
issues.push(`类名不包含service: ${service.className}`);
|
||
}
|
||
|
||
// 检查是否有服务注解
|
||
if (content.includes('@service') || content.includes('@component')) {
|
||
score += 40;
|
||
} else {
|
||
issues.push(`缺少服务注解: ${service.className}`);
|
||
}
|
||
|
||
// 检查是否有业务方法
|
||
if (content.includes('public') && content.includes('(')) {
|
||
score += 30;
|
||
} else {
|
||
issues.push(`缺少业务方法: ${service.className}`);
|
||
}
|
||
});
|
||
|
||
return { score: Math.min(score, 100), issues };
|
||
}
|
||
|
||
/**
|
||
* 验证实体分类质量
|
||
*/
|
||
validateEntityClassification(entities) {
|
||
let score = 0;
|
||
let issues = [];
|
||
|
||
entities.forEach(entity => {
|
||
const className = entity.className.toLowerCase();
|
||
const content = entity.content.toLowerCase();
|
||
|
||
// 检查类名是否包含entity
|
||
if (className.includes('entity') || className.includes('model')) {
|
||
score += 25;
|
||
} else {
|
||
issues.push(`类名不包含entity/model: ${entity.className}`);
|
||
}
|
||
|
||
// 检查是否有实体注解
|
||
if (content.includes('@entity') || content.includes('@table')) {
|
||
score += 35;
|
||
} else {
|
||
issues.push(`缺少实体注解: ${entity.className}`);
|
||
}
|
||
|
||
// 检查是否有字段映射
|
||
if (content.includes('@column') || content.includes('@id')) {
|
||
score += 40;
|
||
} else {
|
||
issues.push(`缺少字段映射: ${entity.className}`);
|
||
}
|
||
});
|
||
|
||
return { score: Math.min(score, 100), issues };
|
||
}
|
||
|
||
/**
|
||
* 映射层级关系
|
||
*/
|
||
mapLayers() {
|
||
const scanResults = this.scanner.getScanResults();
|
||
const nestJSModules = this.mapper.mapToNestJSModules(scanResults);
|
||
|
||
console.log('✅ 层级映射完成');
|
||
return nestJSModules;
|
||
}
|
||
|
||
/**
|
||
* ✅ V2: 生成NestJS模块(传递CDR)
|
||
*/
|
||
async generateModules(nestJSModules) {
|
||
// ✅ V2: 传递CDR给ModuleGenerator
|
||
this.moduleGenerator.setOutputDir(this.nestJSPath);
|
||
this.moduleGenerator.setCDR(this.cdr); // ✅ 传递整个CDR
|
||
|
||
// ⚠️ 向后兼容:也传递旧的索引(指向CDR内部)
|
||
this.moduleGenerator.setServiceMethodSignatureIndex(this.serviceMethodSignatureIndex);
|
||
|
||
await this.moduleGenerator.generateAllModules(nestJSModules);
|
||
|
||
this.stats.modulesGenerated = Object.keys(nestJSModules).length;
|
||
console.log(`✅ 生成了 ${this.stats.modulesGenerated} 个模块`);
|
||
}
|
||
|
||
/**
|
||
* 生成迁移报告
|
||
*/
|
||
generateReport() {
|
||
const report = {
|
||
timestamp: new Date().toISOString(),
|
||
stats: this.stats,
|
||
modules: [
|
||
'CommonModule - 通用功能模块',
|
||
'EntityModule - 实体模块',
|
||
'ServiceModule - 服务模块',
|
||
'ControllerModule - 控制器模块',
|
||
'ListenerModule - 监听器模块',
|
||
'JobModule - 任务模块'
|
||
]
|
||
};
|
||
|
||
const reportPath = path.join(__dirname, 'migration-report.json');
|
||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||
console.log(`📋 迁移报告已生成: ${reportPath}`);
|
||
}
|
||
|
||
/**
|
||
* 获取生成的文件列表
|
||
*/
|
||
getGeneratedFiles() {
|
||
const files = [];
|
||
const nestJSDir = this.nestJSPath;
|
||
|
||
if (fs.existsSync(nestJSDir)) {
|
||
const walkDir = (dir) => {
|
||
const items = fs.readdirSync(dir);
|
||
items.forEach(item => {
|
||
const itemPath = path.join(dir, item);
|
||
const stat = fs.statSync(itemPath);
|
||
if (stat.isDirectory()) {
|
||
walkDir(itemPath);
|
||
} else if (item.endsWith('.ts')) {
|
||
files.push({ path: itemPath, name: item });
|
||
}
|
||
});
|
||
};
|
||
walkDir(nestJSDir);
|
||
}
|
||
|
||
return files;
|
||
}
|
||
|
||
/**
|
||
* 打印框架验证结果
|
||
*/
|
||
printFrameworkValidationResults(validationResults) {
|
||
if (!validationResults || !validationResults.summary) {
|
||
console.log('⚠️ 框架验证结果为空');
|
||
return;
|
||
}
|
||
|
||
console.log('\n🔍 框架集成验证结果:');
|
||
console.log(`📊 总文件数: ${validationResults.summary.totalFiles || 0}`);
|
||
console.log(`✅ 框架导入: ${validationResults.summary.frameworkImports?.['✅'] || 0}/${(validationResults.summary.frameworkImports?.['✅'] || 0) + (validationResults.summary.frameworkImports?.['❌'] || 0)}`);
|
||
console.log(`✅ 框架服务: ${validationResults.summary.frameworkServices?.['✅'] || 0}/${(validationResults.summary.frameworkServices?.['✅'] || 0) + (validationResults.summary.frameworkServices?.['❌'] || 0)}`);
|
||
console.log(`✅ 框架配置: ${validationResults.summary.frameworkConfig?.['✅'] || 0}/${(validationResults.summary.frameworkConfig?.['✅'] || 0) + (validationResults.summary.frameworkConfig?.['❌'] || 0)}`);
|
||
console.log(`✅ 框架事件: ${validationResults.summary.frameworkEvents?.['✅'] || 0}/${(validationResults.summary.frameworkEvents?.['✅'] || 0) + (validationResults.summary.frameworkEvents?.['❌'] || 0)}`);
|
||
console.log(`✅ 框架队列: ${validationResults.summary.frameworkQueues?.['✅'] || 0}/${(validationResults.summary.frameworkQueues?.['✅'] || 0) + (validationResults.summary.frameworkQueues?.['❌'] || 0)}`);
|
||
console.log(`✅ 数据库兼容: ${validationResults.summary.databaseCompatibility?.['✅'] || 0}/${(validationResults.summary.databaseCompatibility?.['✅'] || 0) + (validationResults.summary.databaseCompatibility?.['❌'] || 0)}`);
|
||
console.log(`✅ API兼容: ${validationResults.summary.apiCompatibility?.['✅'] || 0}/${(validationResults.summary.apiCompatibility?.['✅'] || 0) + (validationResults.summary.apiCompatibility?.['❌'] || 0)}`);
|
||
}
|
||
|
||
/**
|
||
* 打印统计信息
|
||
*/
|
||
printStats() {
|
||
const duration = this.stats.endTime - this.stats.startTime;
|
||
console.log('\n📊 迁移统计:');
|
||
console.log(`⏱️ 总耗时: ${duration}ms`);
|
||
console.log(`📁 处理文件: ${this.stats.filesProcessed} 个`);
|
||
console.log(`🔧 生成模块: ${this.stats.modulesGenerated} 个`);
|
||
console.log(`❌ 错误数量: ${this.stats.errors.length} 个`);
|
||
}
|
||
}
|
||
|
||
// 如果直接运行此文件,则执行迁移
|
||
if (require.main === module) {
|
||
const coordinator = new JavaToNestJSMigrationCoordinator();
|
||
coordinator.runMigration().catch(console.error);
|
||
}
|
||
|
||
module.exports = JavaToNestJSMigrationCoordinator;
|