Files
wwjcloud-nest-v1/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/service-method-converter.js
2025-10-29 13:47:25 +08:00

314 lines
13 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 NamingUtils = require('../utils/naming-utils');
/**
* Service方法体转换器
*
* 职责将Java Service方法体转换为TypeScript
*
* 核心功能:
* 1. 提取Java方法体
* 2. Java语法 → TypeScript语法转换
* 3. Java工具类 → NestJS Boot层映射
*/
class ServiceMethodConverter {
constructor() {
this.namingUtils = new NamingUtils();
}
/**
* 转换Java方法体为TypeScript
*
* @param {string} javaMethodBody - Java方法体代码
* @param {object} context - 上下文信息Service类信息、依赖等
* @returns {string} 转换后的TypeScript方法体
*/
convertMethodBody(javaMethodBody, context = {}) {
if (!javaMethodBody || javaMethodBody.trim() === '') {
return ' // TODO: 实现业务逻辑\n return null;';
}
let tsBody = javaMethodBody;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段1】基础语法转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertBasicSyntax(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段2】类型转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertTypes(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段3】工具类映射Java → NestJS Boot层
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertUtilityClasses(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段4】Mapper/Service调用转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertDependencyCalls(tsBody, context);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段5】添加缩进
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = tsBody.split('\n').map(line => ' ' + line).join('\n');
return tsBody;
}
/**
* 【阶段1】基础语法转换
*/
convertBasicSyntax(javaBody) {
let tsBody = javaBody;
// 1. 变量声明Type varName = value; → const varName: Type = value;
tsBody = tsBody.replace(
/(\b(?:Integer|Long|String|Boolean|Double|Float|BigDecimal|Object|List<[^>]+>|Map<[^>]+>|[\w<>,\s]+))\s+(\w+)\s*=\s*([^;]+);/g,
(match, type, varName, value) => {
const tsType = this.mapJavaTypeToTypeScript(type);
return `const ${varName}: ${tsType} = ${value};`;
}
);
// 2. for-each循环for (Type item : collection) → for (const item of collection)
tsBody = tsBody.replace(/for\s*\(\s*[\w<>,\s]+\s+(\w+)\s*:\s*([^)]+)\)/g, 'for (const $1 of $2)');
// 3. Java实例化new ArrayList<>() → []
tsBody = tsBody.replace(/new\s+ArrayList<[^>]*>\(\)/g, '[]');
tsBody = tsBody.replace(/new\s+LinkedList<[^>]*>\(\)/g, '[]');
tsBody = tsBody.replace(/new\s+HashMap<[^>]*>\(\)/g, '{}');
tsBody = tsBody.replace(/new\s+HashSet<[^>]*>\(\)/g, 'new Set()');
// 4. Lambda表达式item -> expression → item => expression
tsBody = tsBody.replace(/(\w+)\s*->\s*/g, '$1 => ');
tsBody = tsBody.replace(/\(([^)]+)\)\s*->\s*/g, '($1) => ');
return tsBody;
}
/**
* 【阶段2】类型转换
*/
convertTypes(javaBody) {
let tsBody = javaBody;
// 1. 基础类型
tsBody = tsBody.replace(/\bInteger\b/g, 'number');
tsBody = tsBody.replace(/\bLong\b/g, 'number');
tsBody = tsBody.replace(/\bDouble\b/g, 'number');
tsBody = tsBody.replace(/\bFloat\b/g, 'number');
// 2. Java对象类型
tsBody = tsBody.replace(/\bJSONObject\b/g, 'Record<string, any>');
tsBody = tsBody.replace(/\bId\b/g, 'number');
// 3. Java类型转换语法(Type) value → value
tsBody = tsBody.replace(/\(\s*(?:int|long|double|float|String|Integer|Long|Double|Float|number)\s*\)\s*/g, '');
return tsBody;
}
/**
* 【阶段3】工具类映射
*/
convertUtilityClasses(javaBody) {
let tsBody = javaBody;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【配置访问】WebAppEnvs / GlobalConfig → ConfigService
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// WebAppEnvs.get().property → this.config.get('property')
tsBody = tsBody.replace(/WebAppEnvs\.get\(\)\.(\w+)/g, (match, prop) => {
return `this.config.get('${prop}')`;
});
tsBody = tsBody.replace(/WebAppEnvs\.get\(\)/g, 'this.config');
// GlobalConfig.property → this.config.get('property')
tsBody = tsBody.replace(/GlobalConfig\.(\w+)/g, (match, prop) => {
return `this.config.get('${prop}')`;
});
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【文件操作】Java File API → Node.js fs/path
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = tsBody.replace(/new\s+File\(([^)]+)\)/g, '$1');
tsBody = tsBody.replace(/(\w+)\.exists\(\)/g, 'fs.existsSync($1)');
tsBody = tsBody.replace(/(\w+)\.isDirectory\(\)/g, 'fs.lstatSync($1).isDirectory()');
tsBody = tsBody.replace(/(\w+)\.getName\(\)/g, 'path.basename($1)');
tsBody = tsBody.replace(/(\w+)\.listFiles\(\)/g, 'fs.readdirSync($1)');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【字符串方法】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// .equals() → ===
tsBody = tsBody.replace(/([^!;,\s]+)\.equals\(([^)]+)\)/g, '$1 === $2');
// .equalsIgnoreCase() → .toLowerCase() === xxx.toLowerCase()
tsBody = tsBody.replace(/([a-zA-Z_$][\w$.()]+)\.equalsIgnoreCase\(([^)]+)\)/g, '$1.toLowerCase() === $2.toLowerCase()');
// .contains() → .includes()
tsBody = tsBody.replace(/\.contains\(/g, '.includes(');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【集合判空】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// CollectionUtil.isEmpty(list) → !list || list.length === 0
tsBody = tsBody.replace(/(?:CollectionUtil|CollUtil)\.isEmpty\(([^)]+)\)/g, '(!$1 || $1.length === 0)');
tsBody = tsBody.replace(/(?:CollectionUtil|CollUtil)\.isNotEmpty\(([^)]+)\)/g, '($1 && $1.length > 0)');
// list.isEmpty() → list.length === 0
tsBody = tsBody.replace(/([a-zA-Z_$][\w$]*)\.isEmpty\(\)/g, '$1.length === 0');
tsBody = tsBody.replace(/!([a-zA-Z_$][\w$]*)\.isEmpty\(\)/g, '$1.length > 0');
// StringUtils.isEmpty(str) → !str || str.trim() === ''
tsBody = tsBody.replace(/StringUtils\.isEmpty\(([^)]+)\)/g, '(!$1 || $1.trim() === \'\')');
tsBody = tsBody.replace(/StringUtils\.isNotEmpty\(([^)]+)\)/g, '($1 && $1.trim() !== \'\')');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【异常处理】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = tsBody.replace(/throw\s+new\s+CommonException\(/g, 'throw new BadRequestException(');
tsBody = tsBody.replace(/throw\s+new\s+AuthException\(/g, 'throw new UnauthorizedException(');
tsBody = tsBody.replace(/throw\s+new\s+RuntimeException\(/g, 'throw new Error(');
tsBody = tsBody.replace(/throw\s+new\s+Exception\(/g, 'throw new Error(');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【其他】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// System.out.println → console.log
tsBody = tsBody.replace(/System\.out\.println\(/g, 'console.log(');
tsBody = tsBody.replace(/System\.err\.println\(/g, 'console.error(');
return tsBody;
}
/**
* 【阶段4】Mapper/Service调用转换
*/
convertDependencyCalls(javaBody, context) {
let tsBody = javaBody;
// Mapper调用xxxMapper.method() → this.xxxRepository.method()
if (context.dependencies) {
context.dependencies.forEach(dep => {
if (dep.includes('Mapper')) {
const mapperName = dep;
const repoName = this.namingUtils.toCamelCase(dep.replace('Mapper', 'Repository'));
tsBody = tsBody.replace(new RegExp(`${mapperName}\\.`, 'g'), `this.${repoName}.`);
}
});
}
// Service调用xxxService.method() → this.xxxService.method()
if (context.dependencies) {
context.dependencies.forEach(dep => {
if (dep.includes('Service') && !dep.includes('ServiceImpl')) {
const serviceName = this.namingUtils.toCamelCase(dep);
tsBody = tsBody.replace(new RegExp(`${dep}\\.`, 'g'), `this.${serviceName}.`);
}
});
}
return tsBody;
}
/**
* 映射Java类型到TypeScript
*/
mapJavaTypeToTypeScript(javaType) {
// 处理泛型
if (javaType.includes('<')) {
const genericMatch = javaType.match(/^(\w+)<(.+)>$/);
if (genericMatch) {
const baseType = genericMatch[1];
const genericType = genericMatch[2];
if (baseType === 'List' || baseType === 'ArrayList' || baseType === 'LinkedList') {
return `${this.mapJavaTypeToTypeScript(genericType)}[]`;
} else if (baseType === 'Map' || baseType === 'HashMap') {
return 'Record<string, any>';
}
}
}
const typeMap = {
'Integer': 'number',
'int': 'number',
'Long': 'number',
'long': 'number',
'Double': 'number',
'double': 'number',
'Float': 'number',
'float': 'number',
'BigDecimal': 'number',
'String': 'string',
'Boolean': 'boolean',
'boolean': 'boolean',
'Object': 'any',
'void': 'void',
'File': 'string',
'JSONObject': 'Record<string, any>',
'Id': 'number',
'Date': 'Date',
'LocalDateTime': 'Date',
'LocalDate': 'Date',
'List': 'any[]',
'Map': 'Record<string, any>'
};
return typeMap[javaType] || javaType;
}
/**
* 分析需要的imports
*
* @param {string} convertedBody - 转换后的TypeScript代码
* @returns {object} 需要导入的模块
*/
analyzeImports(convertedBody) {
const imports = {
nestjs: new Set(),
boot: new Set(),
nodeModules: new Set()
};
// NestJS异常
if (convertedBody.includes('BadRequestException')) {
imports.nestjs.add('BadRequestException');
}
if (convertedBody.includes('UnauthorizedException')) {
imports.nestjs.add('UnauthorizedException');
}
// Node.js模块
if (convertedBody.includes('fs.')) {
imports.nodeModules.add('fs');
}
if (convertedBody.includes('path.')) {
imports.nodeModules.add('path');
}
// ConfigService
if (convertedBody.includes('this.config.')) {
imports.boot.add('ConfigService');
}
return {
nestjs: Array.from(imports.nestjs),
boot: Array.from(imports.boot),
nodeModules: Array.from(imports.nodeModules)
};
}
}
module.exports = ServiceMethodConverter;