314 lines
13 KiB
JavaScript
314 lines
13 KiB
JavaScript
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;
|
||
|