Files
wwjcloud-nest-v1/wwjcloud-nest-v1/tools/java-to-nestjs-migration/generators/service-generator.js
wanwu c730dca038 fix: 修复Import生成逻辑(自动导入V1框架工具类)
🐛 问题根源:
- 转换器已正确使用框架能力(JsonUtils/CommonUtils/StringUtils)
- 但service-generator未调用转换器的analyzeImports()
- 导致工具类import缺失

 修复内容:
1. service-generator.analyzeAdditionalImports()
   - 调用methodConverter.analyzeImports()获取完整import列表
   - 新增Boot层工具类检测: JsonUtils/CommonUtils/StringUtils
   - 新增服务检测: AppConfigService/RequestContextService

2. service-generator.generateImports()
   - 动态添加Boot层工具类到import语句

3. service-generator.generateConstructor()
   - 自动注入AppConfigService (this.appConfig)
   - 自动注入RequestContextService (this.requestContext)

🎯 预期效果:
- 15,474个错误 → 预计降到5000以内
- 'Cannot find name JsonUtils' → 0
- 'Cannot find name CommonUtils' → 0
- 'Cannot find name StringUtils' → 0
2025-10-29 16:23:28 +08:00

659 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const path = require('path');
const NamingUtils = require('../utils/naming-utils');
const ServiceMethodConverter = require('../converters/service-method-converter');
/**
* 服务生成器
* 将Java服务转换为NestJS服务
*/
class ServiceGenerator {
constructor(outputDir = null) {
this.namingUtils = new NamingUtils();
this.methodConverter = new ServiceMethodConverter();
this.outputDir = outputDir;
}
/**
* 检查Entity文件是否存在
*/
entityFileExists(entityName) {
if (!this.outputDir) return false;
const entityFileName = this.namingUtils.generateFileName(entityName, 'entity');
const entityPath = path.join(this.outputDir, 'entities', entityFileName);
return fs.existsSync(entityPath);
}
/**
* 生成服务文件
*/
generateService(javaService, outputDir) {
this.outputDir = outputDir; // 更新outputDir
// 检查服务数据是否有效
if (!javaService || !javaService.className) {
console.warn(`⚠️ 跳过无效服务: ${JSON.stringify(javaService)}`);
return null;
}
// 根据Java文件路径创建子目录结构
const subDir = this.getSubDirectoryFromJavaPath(javaService.filePath, 'service');
const fullOutputDir = path.join(outputDir, subDir);
// 确保子目录存在
if (!fs.existsSync(fullOutputDir)) {
fs.mkdirSync(fullOutputDir, { recursive: true });
}
const serviceName = this.namingUtils.generateServiceName(javaService.className);
const fileName = this.namingUtils.generateFileName(javaService.className, 'service');
const filePath = path.join(fullOutputDir, fileName);
// ✅ 修复检查Service是否已实现不覆盖已实现的Service
if (fs.existsSync(filePath)) {
const existingContent = fs.readFileSync(filePath, 'utf-8');
// 检查是否已实现不只是TODO占位符
const isImplemented = this.isServiceImplemented(existingContent);
if (isImplemented) {
console.log(`⏭️ 跳过已实现服务: ${filePath}`);
return { fileName, content: existingContent, skipped: true };
}
console.log(`🔄 覆盖TODO占位服务: ${filePath}`);
}
const content = this.generateServiceContent(javaService, serviceName);
fs.writeFileSync(filePath, content);
console.log(`✅ 生成服务: ${filePath}`);
return { fileName, content };
}
/**
* 检查Service是否已实现不只是TODO占位符
*/
isServiceImplemented(content) {
// 检查是否包含实际业务逻辑的标志:
// 1. 有TypeORM的@InjectRepository
// 2. 有JwtService
// 3. 有bcrypt
// 4. 方法体中不只是return null/undefined
// 5. 有实际的业务代码非TODO注释
const implementationMarkers = [
/@InjectRepository/,
/JwtService/,
/bcrypt/,
/await.*Repository/,
/this\..*Repository\./,
/async\s+\w+\([^)]*\)\s*:\s*Promise<[^>]+>\s*{\s*[^}]*(?!\/\/\s*TODO)(?!return\s+null)/,
];
// 如果包含任一实现标志,认为是已实现
return implementationMarkers.some(marker => marker.test(content));
}
/**
* 根据Java文件路径获取子目录结构
*/
getSubDirectoryFromJavaPath(javaFilePath, type) {
if (!javaFilePath) return '';
// 从Java文件路径中提取包结构
// 例如: /path/to/java/com/niu/core/service/core/aliapp/ICoreAliappConfigService.java
// 提取: service/core/aliapp
const pathParts = javaFilePath.split(path.sep);
const javaIndex = pathParts.findIndex(part => part === 'java');
if (javaIndex === -1) return '';
// 获取java目录后的包结构
const packageParts = pathParts.slice(javaIndex + 1, -1); // 排除文件名
// 根据类型过滤相关目录
const typeIndex = packageParts.findIndex(part => part === type || part === type + 's');
if (typeIndex === -1) return '';
// 返回类型目录后的子目录结构
const subParts = packageParts.slice(typeIndex + 1);
return subParts.join('/');
}
/**
* 计算从Service到entities目录的相对路径
*/
calculateEntityPath(javaFilePath) {
if (!javaFilePath) return '../../entities';
// 获取Service的子目录深度
const subDir = this.getSubDirectoryFromJavaPath(javaFilePath, 'service');
// 计算需要返回的层级数
// services目录本身算1层子目录每层+1
const depth = subDir ? subDir.split('/').length + 1 : 1;
const upLevels = '../'.repeat(depth + 1); // +1是因为要从services目录出去到src
return `${upLevels}entities`;
}
/**
* 生成服务内容
*
* ✅ 增强自动分析方法体添加需要的imports
*/
generateServiceContent(javaService, serviceName) {
// 先生成方法以便分析需要哪些imports
const methods = this.generateMethods(javaService);
// 分析方法体获取需要的imports
const additionalImports = this.analyzeAdditionalImports(methods);
const imports = this.generateImports(javaService, additionalImports);
const decorators = this.generateDecorators();
const constructor = this.generateConstructor(javaService, additionalImports);
return `${imports}
${decorators}
export class ${serviceName} {
${constructor}
${methods}
}
`;
}
/**
* 分析方法体识别需要的额外imports
*
* ✅ 调用 ServiceMethodConverter.analyzeImports 获取完整的import列表
*/
analyzeAdditionalImports(methodsCode) {
const imports = {
nestjs: new Set(),
nodeModules: new Set(),
boot: new Set()
};
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【调用转换器的analyzeImports获取V1框架能力的imports】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const converterImports = this.methodConverter.analyzeImports(methodsCode);
converterImports.forEach(importItem => {
if (importItem === 'JsonUtils' || importItem === 'CommonUtils' || importItem === 'StringUtils') {
imports.boot.add(importItem);
} else if (importItem === 'AppConfigService') {
imports.boot.add('AppConfigService');
} else if (importItem === 'nestjs:BadRequestException') {
imports.nestjs.add('BadRequestException');
} else if (importItem === 'nestjs:UnauthorizedException') {
imports.nestjs.add('UnauthorizedException');
} else if (importItem === 'node:fs') {
imports.nodeModules.add('fs');
} else if (importItem === 'node:path') {
imports.nodeModules.add('path');
} else if (importItem === 'boot:RequestContextService') {
imports.boot.add('RequestContextService');
}
});
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【兼容旧的检测逻辑(备用)】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// NestJS异常
if (methodsCode.includes('BadRequestException')) {
imports.nestjs.add('BadRequestException');
}
if (methodsCode.includes('UnauthorizedException')) {
imports.nestjs.add('UnauthorizedException');
}
// Node.js模块
if (methodsCode.includes('fs.')) {
imports.nodeModules.add('fs');
}
if (methodsCode.includes('path.')) {
imports.nodeModules.add('path');
}
// Boot层工具类
if (methodsCode.includes('JsonUtils.')) {
imports.boot.add('JsonUtils');
}
if (methodsCode.includes('CommonUtils.')) {
imports.boot.add('CommonUtils');
}
if (methodsCode.includes('StringUtils.')) {
imports.boot.add('StringUtils');
}
if (methodsCode.includes('this.appConfig.')) {
imports.boot.add('AppConfigService');
}
if (methodsCode.includes('this.requestContext.')) {
imports.boot.add('RequestContextService');
}
return {
nestjs: Array.from(imports.nestjs),
nodeModules: Array.from(imports.nodeModules),
boot: Array.from(imports.boot)
};
}
/**
* 生成导入语句
*
* ✅ 增强接收额外的importsNestJS异常、Node.js模块、Boot服务
*/
generateImports(javaService, additionalImports = {}) {
const imports = [
"import { Injectable } from '@nestjs/common';"
];
// ✅ NestJS异常按需导入
const nestjsImports = ['Injectable'];
if (additionalImports.nestjs && additionalImports.nestjs.length > 0) {
additionalImports.nestjs.forEach(item => {
if (!nestjsImports.includes(item)) {
nestjsImports.push(item);
}
});
}
if (nestjsImports.length > 1) {
imports[0] = `import { ${nestjsImports.join(', ')} } from '@nestjs/common';`;
}
// TypeORM
imports.push("import { InjectRepository } from '@nestjs/typeorm';");
imports.push("import { Repository } from 'typeorm';");
// ✅ Boot层按需导入
// 基础服务(总是导入)
const bootImports = ['QueueService', 'EventBus', 'Result'];
// 添加额外的Boot层imports工具类、服务等
if (additionalImports.boot && additionalImports.boot.length > 0) {
additionalImports.boot.forEach(item => {
if (!bootImports.includes(item)) {
bootImports.push(item);
}
});
}
// 生成import语句
if (bootImports.length > 0) {
imports.push(`import { ${bootImports.join(', ')} } from '@wwjBoot';`);
}
// ✅ Node.js模块按需导入
if (additionalImports.nodeModules && additionalImports.nodeModules.length > 0) {
additionalImports.nodeModules.forEach(module => {
if (module === 'fs') {
imports.push(`import * as fs from 'fs';`);
} else if (module === 'path') {
imports.push(`import * as path from 'path';`);
}
});
}
// 计算到entities目录的相对路径
const entityRelativePath = this.calculateEntityPath(javaService.filePath);
// 推断主实体并添加导入仅当Entity文件存在时
const entityName = this.inferEntityName(javaService.className);
if (this.entityFileExists(entityName)) {
const pascalEntityName = this.namingUtils.toPascalCase(entityName);
const entityFileName = this.namingUtils.generateFileName(entityName, 'entity');
imports.push(`import { ${pascalEntityName} } from '${entityRelativePath}/${entityFileName.replace('.ts', '')}';`);
} else {
console.warn(`⚠️ Entity文件不存在跳过导入: ${entityName} (Service: ${javaService.className})`);
}
// 添加额外实体导入仅当Entity文件存在时
if (javaService.entities && javaService.entities.length > 0) {
javaService.entities.forEach(entity => {
// 跳过已经导入的主实体
if (entity.toLowerCase() === entityName.toLowerCase()) {
return;
}
if (this.entityFileExists(entity)) {
const extraEntityName = this.namingUtils.generateEntityName(entity);
const extraEntityFileName = this.namingUtils.generateFileName(entity, 'entity');
imports.push(`import { ${extraEntityName} } from '${entityRelativePath}/${extraEntityFileName.replace('.ts', '')}';`);
} else {
console.warn(`⚠️ 额外Entity文件不存在跳过导入: ${entity} (Service: ${javaService.className})`);
}
});
}
// 添加DTO导入
if (javaService.dtos && javaService.dtos.length > 0) {
javaService.dtos.forEach(dto => {
const dtoName = this.namingUtils.generateDtoName(dto);
const dtoFileName = this.namingUtils.generateFileName(dto, 'dto');
imports.push(`import { ${dtoName} } from '../dtos/${dtoFileName.replace('.ts', '')}';`);
});
}
// 添加其他服务导入
if (javaService.dependencies && javaService.dependencies.length > 0) {
javaService.dependencies.forEach(dep => {
const serviceName = this.namingUtils.generateServiceName(dep);
const serviceFileName = this.namingUtils.generateFileName(dep, 'service');
imports.push(`import { ${serviceName} } from './${serviceFileName.replace('.service.ts', '')}';`);
});
}
return imports.join('\n');
}
/**
* 生成装饰器
*/
generateDecorators() {
return '@Injectable()';
}
/**
* 生成构造函数
*
* ✅ 增强根据additionalImports注入ConfigService
*/
generateConstructor(javaService, additionalImports = {}) {
const injections = [];
// ✅ 添加Boot层服务注入按需
if (additionalImports.boot && additionalImports.boot.includes('AppConfigService')) {
injections.push(' private readonly appConfig: AppConfigService,');
}
if (additionalImports.boot && additionalImports.boot.includes('RequestContextService')) {
injections.push(' private readonly requestContext: RequestContextService,');
}
if (additionalImports.boot && additionalImports.boot.includes('ConfigService')) {
injections.push(' private readonly config: ConfigService,');
}
// 添加框架服务注入(总是注入)
injections.push(' private readonly eventBus: EventBus,');
injections.push(' private readonly queueService: QueueService,');
// 推断主实体并注入 Repository仅当Entity文件存在时
const entityName = this.inferEntityName(javaService.className);
if (this.entityFileExists(entityName)) {
const pascalEntityName = this.namingUtils.toPascalCase(entityName);
const repositoryPropertyName = this.namingUtils.toCamelCase(entityName) + 'Repository';
injections.push(` @InjectRepository(${pascalEntityName})
private readonly ${repositoryPropertyName}: Repository<${pascalEntityName}>,`);
}
// 添加额外实体注入如果有且Entity文件存在
if (javaService.entities && javaService.entities.length > 0) {
javaService.entities.forEach(entity => {
// 跳过已经注入的主实体
if (entity.toLowerCase() === entityName.toLowerCase()) {
return;
}
if (this.entityFileExists(entity)) {
const extraEntityName = this.namingUtils.generateEntityName(entity);
const propertyName = this.namingUtils.toCamelCase(entity) + 'Repository';
injections.push(` @InjectRepository(${extraEntityName})
private readonly ${propertyName}: Repository<${extraEntityName}>,`);
}
});
}
// 添加其他服务注入
if (javaService.dependencies && javaService.dependencies.length > 0) {
javaService.dependencies.forEach(dep => {
const serviceName = this.namingUtils.generateServiceName(dep);
const propertyName = this.namingUtils.toCamelCase(dep) + 'Service';
injections.push(` private readonly ${propertyName}: ${serviceName},`);
});
}
if (injections.length === 0) {
return ' constructor() {}';
}
return ` constructor(
${injections.join('\n')}
) {}`;
}
/**
* 生成方法
*/
generateMethods(javaService) {
if (!javaService.methods || javaService.methods.length === 0) {
return ' // 无方法';
}
// 过滤并去重只生成public方法使用Set记录已生成的方法名
const generatedMethodNames = new Set();
const uniqueMethods = [];
for (const method of javaService.methods) {
// 只处理public方法排除构造函数
if (method.accessModifier === 'public' && method.methodName !== javaService.className) {
const methodName = this.namingUtils.generateMethodName(method.methodName);
if (!generatedMethodNames.has(methodName)) {
generatedMethodNames.add(methodName);
uniqueMethods.push(method);
}
}
}
if (uniqueMethods.length === 0) {
return ' // 无public方法';
}
return uniqueMethods.map(method => {
return this.generateMethod(method, javaService);
}).join('\n\n');
}
/**
* 生成单个方法
*/
generateMethod(method, javaService) {
const methodName = this.namingUtils.generateMethodName(method.methodName);
const parameters = this.generateMethodParameters(method);
const returnType = this.generateReturnType(method);
const body = this.generateMethodBody(method, javaService);
return ` /**
* ${method.methodName}
*/
async ${methodName}(${parameters}): Promise<${returnType}> {
${body}
}`;
}
/**
* 生成方法参数
*/
generateMethodParameters(method) {
// 简化:所有方法都接受任意参数,避免参数类型不匹配
return '...args: any[]';
}
/**
* 生成返回类型
*/
generateReturnType(method) {
// 检查methodName是否存在
const methodName = method.methodName || method.name || 'unknown';
if (methodName.includes('list') || methodName.includes('page')) {
return 'any[]';
}
if (methodName.includes('count') || methodName.includes('exists')) {
return 'number';
}
if (methodName.includes('delete') || methodName.includes('remove')) {
return 'void';
}
return 'any';
}
/**
* 生成方法体
*
* ✅ 新逻辑使用ServiceMethodConverter转换Java方法体
*/
generateMethodBody(method, javaService) {
// 如果Java方法有方法体使用转换器转换
if (method.methodBody && method.methodBody.trim() !== '') {
// 准备上下文信息
const context = {
dependencies: javaService.dependencies || [],
className: javaService.className
};
// 使用转换器转换Java方法体
return this.methodConverter.convertMethodBody(method.methodBody, context);
}
// 如果没有Java方法体返回TODO占位符按返回类型
const returnType = this.generateReturnType(method);
if (returnType === 'any[]') {
return ` // TODO: 实现业务逻辑\n return [];`;
} else if (returnType === 'number') {
return ` // TODO: 实现业务逻辑\n return 0;`;
} else if (returnType === 'void') {
return ` // TODO: 实现业务逻辑\n return;`;
} else if (returnType.includes('PageResult') || returnType.includes('{ items')) {
return ` // TODO: 实现业务逻辑\n return { items: [], total: 0 };`;
} else {
return ` // TODO: 实现业务逻辑\n return null;`;
}
}
/**
* 推断实体名称
*/
inferEntityName(serviceClassName) {
// 从 SiteServiceImpl -> Site
return serviceClassName
.replace(/ServiceImpl$/, '')
.replace(/Service$/, '')
.replace(/^I/, ''); // 移除接口前缀 I
}
/**
* 生成列表查询方法体
*/
generateListMethodBody(repositoryName) {
return ` const skip = (page - 1) * limit;
const [items, total] = await this.${repositoryName}.findAndCount({
skip,
take: limit,
order: { createTime: 'DESC' },
});
return { items, total };`;
}
/**
* 生成详情查询方法体
*/
generateGetByIdMethodBody(repositoryName) {
return ` const entity = await this.${repositoryName}.findOne({ where: { id } });
if (!entity) {
throw new Error('记录不存在');
}
return entity;`;
}
/**
* 生成创建方法体
*/
generateCreateMethodBody(repositoryName) {
return ` const entity = this.${repositoryName}.create(data);
const saved = await this.${repositoryName}.save(entity);
// 发布创建事件
await this.eventBus.publish('entity.created', { entity: saved });
return saved;`;
}
/**
* 生成更新方法体
*/
generateUpdateMethodBody(repositoryName) {
return ` const entity = await this.${repositoryName}.findOne({ where: { id } });
if (!entity) {
throw new Error('记录不存在');
}
Object.assign(entity, data);
const updated = await this.${repositoryName}.save(entity);
// 发布更新事件
await this.eventBus.publish('entity.updated', { entity: updated });
return updated;`;
}
/**
* 生成删除方法体
*/
generateDeleteMethodBody(repositoryName) {
return ` const entity = await this.${repositoryName}.findOne({ where: { id } });
if (!entity) {
throw new Error('记录不存在');
}
await this.${repositoryName}.remove(entity);
// 发布删除事件
await this.eventBus.publish('entity.deleted', { entity });`;
}
/**
* 生成计数方法体
*/
generateCountMethodBody(repositoryName) {
return ` return await this.${repositoryName}.count();`;
}
/**
* 生成存在性检查方法体
*/
generateExistsMethodBody(repositoryName) {
return ` const count = await this.${repositoryName}.count({ where: { id } });
return count > 0;`;
}
/**
* 验证服务一致性
*/
validateServiceConsistency(javaService, nestJSService) {
const issues = [];
// 验证方法数量
if (javaService.methods.length !== nestJSService.methods.length) {
issues.push('方法数量不一致');
}
// 验证每个方法
javaService.methods.forEach((javaMethod, index) => {
const nestJSMethod = nestJSService.methods[index];
if (nestJSMethod && javaMethod.methodName !== nestJSMethod.methodName) {
issues.push(`方法名不一致: ${javaMethod.methodName} vs ${nestJSMethod.methodName}`);
}
});
return issues;
}
}
module.exports = ServiceGenerator;