refactor: 重构转换器为模块化架构

 新增转换器目录结构:
converters/
├── index.js (统一导出)
├── service-method-converter.js (主协调器)
├── syntax/ (语法转换)
│   ├── basic-syntax.converter.js
│   ├── type.converter.js
│   └── exception.converter.js
├── utils/ (工具类转换)
│   ├── config.converter.js
│   ├── file.converter.js
│   ├── string.converter.js
│   ├── collection.converter.js
│   ├── json.converter.js
│   └── object.converter.js
├── mybatis/ (MyBatis转换)
│   ├── query-wrapper.converter.js
│   ├── mapper.converter.js
│   └── pagination.converter.js
├── method/ (方法调用转换)
│   ├── getter-setter.converter.js
│   ├── method-call.converter.js
│   └── stream-api.converter.js
└── post-processor.js (后处理)

 优势:
- 单一职责:每个转换器只负责一种转换
- 易于维护:清晰的模块化结构
- 易于扩展:新增转换器只需添加新文件
- 易于测试:每个转换器可独立测试

📋 下一步: service-generator.js已自动兼容(不需要修改)
This commit is contained in:
wanwu
2025-10-29 14:28:10 +08:00
parent 84f2027fea
commit 17d096e4cb
21 changed files with 1198 additions and 302 deletions

View File

@@ -0,0 +1,62 @@
/**
* 转换器模块导出
*
* 提供统一的转换器导出接口
*/
// 主转换器
const ServiceMethodConverter = require('./service-method-converter');
// 语法转换器
const BasicSyntaxConverter = require('./syntax/basic-syntax.converter');
const TypeConverter = require('./syntax/type.converter');
const ExceptionConverter = require('./syntax/exception.converter');
// 工具类转换器
const ConfigConverter = require('./utils/config.converter');
const FileConverter = require('./utils/file.converter');
const StringConverter = require('./utils/string.converter');
const CollectionConverter = require('./utils/collection.converter');
const JsonConverter = require('./utils/json.converter');
const ObjectConverter = require('./utils/object.converter');
// MyBatis转换器
const QueryWrapperConverter = require('./mybatis/query-wrapper.converter');
const MapperConverter = require('./mybatis/mapper.converter');
const PaginationConverter = require('./mybatis/pagination.converter');
// 方法调用转换器
const GetterSetterConverter = require('./method/getter-setter.converter');
const MethodCallConverter = require('./method/method-call.converter');
const StreamApiConverter = require('./method/stream-api.converter');
// 后处理器
const PostProcessor = require('./post-processor');
module.exports = {
// 主转换器(推荐使用)
ServiceMethodConverter,
// 子转换器(按需使用)
BasicSyntaxConverter,
TypeConverter,
ExceptionConverter,
ConfigConverter,
FileConverter,
StringConverter,
CollectionConverter,
JsonConverter,
ObjectConverter,
QueryWrapperConverter,
MapperConverter,
PaginationConverter,
GetterSetterConverter,
MethodCallConverter,
StreamApiConverter,
PostProcessor
};

View File

@@ -0,0 +1,38 @@
/**
* Getter/Setter转换器
*
* Java getter/setter → TypeScript属性访问
*/
class GetterSetterConverter {
/**
* 转换getter/setter
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. obj.getXxx() → obj.xxx (只转换简单的getter)
// 但保留常见的业务方法如 getList(), getConfig()等
const commonMethods = ['getList', 'getConfig', 'getPage', 'getTotal', 'getData', 'getSearch', 'getLimit'];
// 先处理需要保留的方法(不转换)
// 其他的 getXxx() 转换为属性访问
tsCode = tsCode.replace(/(\w+)\.get(\w+)\(\)/g, (match, obj, prop) => {
const methodName = `get${prop}`;
if (commonMethods.includes(methodName)) {
return match; // 保留不转换
}
// 转换为属性访问obj.xxx
return `${obj}.${prop.charAt(0).toLowerCase() + prop.slice(1)}`;
});
// 2. obj.setXxx(value) → obj.xxx = value
tsCode = tsCode.replace(/(\w+)\.set(\w+)\(([^)]+)\)/g, (match, obj, prop, value) => {
return `${obj}.${prop.charAt(0).toLowerCase() + prop.slice(1)} = ${value}`;
});
return tsCode;
}
}
module.exports = GetterSetterConverter;

View File

@@ -0,0 +1,31 @@
/**
* 方法调用转换器
*
* Java方法调用 → TypeScript方法调用
*/
class MethodCallConverter {
/**
* 转换方法调用
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. list.add(item) → list.push(item)
tsCode = tsCode.replace(/(\w+)\.add\(/g, '$1.push(');
// 2. list.size() → list.length
tsCode = tsCode.replace(/(\w+)\.size\(\)/g, '$1.length');
// 3. map.put(key, value) → map[key] = value 或 map.set(key, value)
// 这里简化为直接赋值
tsCode = tsCode.replace(/(\w+)\.put\(([^,]+),\s*([^)]+)\)/g, '$1[$2] = $3');
// 4. map.get(key) → map[key] 或 map.get(key)
tsCode = tsCode.replace(/(\w+)\.get\(([^)]+)\)/g, '$1[$2]');
return tsCode;
}
}
module.exports = MethodCallConverter;

View File

@@ -0,0 +1,32 @@
/**
* Stream API转换器
*
* Java Stream API → TypeScript数组方法
*/
class StreamApiConverter {
/**
* 转换Stream API
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. list.stream().filter(x -> x.condition).collect(Collectors.toList())
// → list.filter(x => x.condition)
tsCode = tsCode.replace(/(\w+)\.stream\(\)\.filter\(([^)]+)\)\.collect\(Collectors\.toList\(\)\)/g, '$1.filter($2)');
// 2. list.stream().map(x -> x.transform).collect(Collectors.toList())
// → list.map(x => x.transform)
tsCode = tsCode.replace(/(\w+)\.stream\(\)\.map\(([^)]+)\)\.collect\(Collectors\.toList\(\)\)/g, '$1.map($2)');
// 3. list.stream() → list (简化)
tsCode = tsCode.replace(/(\w+)\.stream\(\)/g, '$1');
// 4. Collectors.toList() → (移除因为TypeScript数组方法默认返回数组)
tsCode = tsCode.replace(/\.collect\(Collectors\.toList\(\)\)/g, '');
return tsCode;
}
}
module.exports = StreamApiConverter;

View File

@@ -0,0 +1,60 @@
/**
* Mapper转换器
*
* MyBatis Mapper → TypeORM Repository
*/
class MapperConverter {
/**
* 转换Mapper调用
*/
convert(javaCode, context = {}) {
let tsCode = javaCode;
// xxxMapper.selectOne() → this.xxxRepository.findOne()
tsCode = tsCode.replace(/(\w+Mapper)\.selectOne\(/g, (match, mapperName) => {
const repoName = mapperName.replace('Mapper', 'Repository');
const camelRepoName = this.toCamelCase(repoName);
return `this.${camelRepoName}.findOne(`;
});
// xxxMapper.selectList() → this.xxxRepository.find()
tsCode = tsCode.replace(/(\w+Mapper)\.selectList\(/g, (match, mapperName) => {
const repoName = mapperName.replace('Mapper', 'Repository');
const camelRepoName = this.toCamelCase(repoName);
return `this.${camelRepoName}.find(`;
});
// xxxMapper.insert() → this.xxxRepository.save()
tsCode = tsCode.replace(/(\w+Mapper)\.insert\(/g, (match, mapperName) => {
const repoName = mapperName.replace('Mapper', 'Repository');
const camelRepoName = this.toCamelCase(repoName);
return `this.${camelRepoName}.save(`;
});
// xxxMapper.update() → this.xxxRepository.save()
tsCode = tsCode.replace(/(\w+Mapper)\.update\(/g, (match, mapperName) => {
const repoName = mapperName.replace('Mapper', 'Repository');
const camelRepoName = this.toCamelCase(repoName);
return `this.${camelRepoName}.save(`;
});
// xxxMapper.delete() → this.xxxRepository.delete()
tsCode = tsCode.replace(/(\w+Mapper)\.delete\(/g, (match, mapperName) => {
const repoName = mapperName.replace('Mapper', 'Repository');
const camelRepoName = this.toCamelCase(repoName);
return `this.${camelRepoName}.delete(`;
});
return tsCode;
}
/**
* 转换为camelCase
*/
toCamelCase(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
}
module.exports = MapperConverter;

View File

@@ -0,0 +1,30 @@
/**
* 分页转换器
*
* MyBatis分页 → TypeORM分页
*/
class PaginationConverter {
/**
* 转换分页
*/
convert(javaCode) {
let tsCode = javaCode;
// IPage<Entity> → TODO: 需要手动处理
tsCode = tsCode.replace(/IPage<(\w+)>/g, '/* TODO: Paginated<$1> */any');
// new Page<Entity>(page, limit) → TODO
tsCode = tsCode.replace(/new\s+Page<(\w+)>\(([^,]+),\s*([^)]+)\)/g, '/* TODO: { page: $2, limit: $3 } */');
// selectPage() → find() with pagination
tsCode = tsCode.replace(/\.selectPage\(/g, '/* TODO: .find() with take/skip */');
// PageResult.build() → new PageResult()
tsCode = tsCode.replace(/PageResult\.build\(([^,]+),\s*([^,]+),\s*([^)]+)\)/g, 'new PageResult({ currentPage: $1, perPage: $2, total: $3 })');
return tsCode;
}
}
module.exports = PaginationConverter;

View File

@@ -0,0 +1,28 @@
/**
* QueryWrapper转换器
*
* MyBatis QueryWrapper → TypeORM QueryBuilder
*/
class QueryWrapperConverter {
/**
* 转换QueryWrapper
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. new QueryWrapper<Entity>() → 需要转换为TypeORM查询
// 暂时标记为TODO因为需要更复杂的上下文
tsCode = tsCode.replace(/new\s+QueryWrapper<(\w+)>\(\)/g, '/* TODO: TypeORM Query for $1 */{}');
// 2. queryWrapper.eq("field", value) → TODO
tsCode = tsCode.replace(/(\w+)\.eq\(([^,]+),\s*([^)]+)\)/g, '/* TODO: .where("$2 = :value", { value: $3 }) */$1');
// 3. queryWrapper.like("field", value) → TODO
tsCode = tsCode.replace(/(\w+)\.like\(([^,]+),\s*([^)]+)\)/g, '/* TODO: .where("$2 LIKE :value", { value: `%${$3}%` }) */$1');
return tsCode;
}
}
module.exports = QueryWrapperConverter;

View File

@@ -0,0 +1,40 @@
/**
* 后处理器
*
* 修复转换后的常见语法错误
*/
class PostProcessor {
/**
* 后处理清理
*/
process(tsCode) {
let result = tsCode;
// 1. 修复 this.fs. 和 this.path. → fs. 和 path.
result = result.replace(/this\.fs\./g, 'fs.');
result = result.replace(/this\.path\./g, 'path.');
// 2. 修复逻辑运算符优先级问题
// if (!this.config.get('xxx') === "yyy") → if (this.config.get('xxx') !== "yyy")
result = result.replace(/if\s*\(\s*!this\.config\.get\([^)]+\)\s*===\s*([^)]+)\)/g, (match) => {
const configCall = match.match(/this\.config\.get\([^)]+\)/)[0];
const value = match.match(/===\s*([^)]+)\)/)[1].trim();
return `if (${configCall} !== ${value})`;
});
// 3. 修复 !xxx === "yyy" → xxx !== "yyy" (通用)
result = result.replace(/!\s*([a-zA-Z_$.()[\]]+)\s*===\s*([^;)\n]+)/g, '$1 !== $2');
// 4. 修复复杂表达式的 .exists()
// this.config.get('xxx' + yyy).exists() → fs.existsSync(this.config.get('xxx' + yyy))
result = result.replace(/(this\.config\.get\([^)]+\))\.exists\(\)/g, 'fs.existsSync($1)');
// 5. 修复逗号表达式if (file, "info.json".exists())
result = result.replace(/if\s*\(\s*(\w+),\s*"([^"]+)"\.exists\(\s*\)\s*\)/g, 'if (fs.existsSync(path.join($1, "$2")))');
return result;
}
}
module.exports = PostProcessor;

View File

@@ -1,18 +1,60 @@
const NamingUtils = require('../utils/naming-utils');
/**
* Service方法体转换器
* Service方法体转换器(主协调器)
*
* 职责将Java Service方法体转换为TypeScript
* 职责:协调各个子转换器,将Java Service方法体转换为TypeScript
*
* 核心功能:
* 1. 提取Java方法体
* 2. Java语法 → TypeScript语法转换
* 3. Java工具类 → NestJS Boot层映射
* 2. 调用各个专业转换器进行转换
* 3. 后处理清理
* 4. 分析需要的imports
*/
// 导入各个转换器
const BasicSyntaxConverter = require('./syntax/basic-syntax.converter');
const TypeConverter = require('./syntax/type.converter');
const ExceptionConverter = require('./syntax/exception.converter');
const ConfigConverter = require('./utils/config.converter');
const FileConverter = require('./utils/file.converter');
const StringConverter = require('./utils/string.converter');
const CollectionConverter = require('./utils/collection.converter');
const JsonConverter = require('./utils/json.converter');
const ObjectConverter = require('./utils/object.converter');
const QueryWrapperConverter = require('./mybatis/query-wrapper.converter');
const MapperConverter = require('./mybatis/mapper.converter');
const PaginationConverter = require('./mybatis/pagination.converter');
const GetterSetterConverter = require('./method/getter-setter.converter');
const MethodCallConverter = require('./method/method-call.converter');
const StreamApiConverter = require('./method/stream-api.converter');
const PostProcessor = require('./post-processor');
class ServiceMethodConverter {
constructor() {
this.namingUtils = new NamingUtils();
// 初始化所有转换器
this.basicSyntax = new BasicSyntaxConverter();
this.type = new TypeConverter();
this.exception = new ExceptionConverter();
this.config = new ConfigConverter();
this.file = new FileConverter();
this.string = new StringConverter();
this.collection = new CollectionConverter();
this.json = new JsonConverter();
this.object = new ObjectConverter();
this.queryWrapper = new QueryWrapperConverter();
this.mapper = new MapperConverter();
this.pagination = new PaginationConverter();
this.getterSetter = new GetterSetterConverter();
this.methodCall = new MethodCallConverter();
this.streamApi = new StreamApiConverter();
this.postProcessor = new PostProcessor();
}
/**
@@ -32,302 +74,55 @@ class ServiceMethodConverter {
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段1】基础语法转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertBasicSyntax(tsBody);
tsBody = this.basicSyntax.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段2】类型转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertTypes(tsBody);
tsBody = this.type.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段3】工具类映射Java → NestJS Boot层
// 【阶段3】工具类转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertUtilityClasses(tsBody);
tsBody = this.config.convert(tsBody);
tsBody = this.file.convert(tsBody);
tsBody = this.string.convert(tsBody);
tsBody = this.collection.convert(tsBody);
tsBody = this.json.convert(tsBody);
tsBody = this.object.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段4】Mapper/Service调用转换
// 【阶段4】MyBatis → TypeORM转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.convertDependencyCalls(tsBody, context);
tsBody = this.queryWrapper.convert(tsBody);
tsBody = this.mapper.convert(tsBody, context);
tsBody = this.pagination.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段5】后处理清理
// 【阶段5】方法调用转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.postProcessCleanup(tsBody);
tsBody = this.streamApi.convert(tsBody);
tsBody = this.methodCall.convert(tsBody);
tsBody = this.getterSetter.convert(tsBody); // 最后转换getter/setter
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段6】添加缩进
// 【阶段6】异常处理转换
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.exception.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段7】后处理清理
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = this.postProcessor.process(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段8】添加缩进
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = tsBody.split('\n').map(line => ' ' + line).join('\n');
return tsBody;
}
/**
* 【阶段5】后处理清理
* 修复转换后的常见语法错误
*/
postProcessCleanup(tsBody) {
// 1. 修复 this.fs. 和 this.path. → fs. 和 path.
tsBody = tsBody.replace(/this\.fs\./g, 'fs.');
tsBody = tsBody.replace(/this\.path\./g, 'path.');
// 2. 修复逻辑运算符优先级问题
// if (!this.config.get('xxx') === "yyy") → if (this.config.get('xxx') !== "yyy")
tsBody = tsBody.replace(/if\s*\(\s*!this\.config\.get\([^)]+\)\s*===\s*([^)]+)\)/g, (match) => {
// 提取内容
const configCall = match.match(/this\.config\.get\([^)]+\)/)[0];
const value = match.match(/===\s*([^)]+)\)/)[1].trim();
return `if (${configCall} !== ${value})`;
});
// 3. 修复 !xxx === "yyy" → xxx !== "yyy" (通用)
tsBody = tsBody.replace(/!\s*([a-zA-Z_$.()[\]]+)\s*===\s*([^;)\n]+)/g, '$1 !== $2');
// 4. 修复复杂表达式的 .exists()
// this.config.get('xxx' + yyy).exists() → fs.existsSync(this.config.get('xxx' + yyy))
tsBody = tsBody.replace(/(this\.config\.get\([^)]+\))\.exists\(\)/g, 'fs.existsSync($1)');
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.exists() → fs.existsSync(this.config.get('property'))
// 必须在WebAppEnvs.get().property之前处理
tsBody = tsBody.replace(/WebAppEnvs\.get\(\)\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 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.exists() → fs.existsSync(this.config.get('property'))
// 必须在GlobalConfig.property之前处理
tsBody = tsBody.replace(/GlobalConfig\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 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
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// FileUtils工具类
tsBody = tsBody.replace(/FileUtils\.cleanDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsBody = tsBody.replace(/FileUtils\.copyFile\(([^,]+),\s*([^)]+)\)/g, 'fs.copyFileSync($1, $2)');
tsBody = tsBody.replace(/FileUtils\.deleteDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsBody = tsBody.replace(/FileUtils\.readFileToString\(([^)]+)\)/g, 'fs.readFileSync($1, \'utf-8\')');
tsBody = tsBody.replace(/FileUtils\.writeStringToFile\(([^,]+),\s*([^)]+)\)/g, 'fs.writeFileSync($1, $2, \'utf-8\')');
// File API
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)');
tsBody = tsBody.replace(/(\w+)\.getPath\(\)/g, '$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() !== \'\')');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【异常处理】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// try-catch语句catch (Exception e) → catch (error)
tsBody = tsBody.replace(/catch\s*\(\s*(?:Exception|RuntimeException|Throwable)\s+(\w+)\s*\)/g, 'catch ($1)');
// 异常方法调用e.getMessage() → e.message
tsBody = tsBody.replace(/(\w+)\.getMessage\(\)/g, '$1.message');
// 异常抛出
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
*
@@ -342,23 +137,15 @@ class ServiceMethodConverter {
};
// NestJS异常
if (convertedBody.includes('BadRequestException')) {
imports.nestjs.add('BadRequestException');
}
if (convertedBody.includes('UnauthorizedException')) {
imports.nestjs.add('UnauthorizedException');
}
const nestjsImports = this.exception.analyzeImports(convertedBody);
nestjsImports.forEach(imp => imports.nestjs.add(imp));
// Node.js模块
if (convertedBody.includes('fs.')) {
imports.nodeModules.add('fs');
}
if (convertedBody.includes('path.')) {
imports.nodeModules.add('path');
}
const nodeModules = this.file.analyzeNodeModules(convertedBody);
nodeModules.forEach(mod => imports.nodeModules.add(mod));
// ConfigService
if (convertedBody.includes('this.config.')) {
if (this.config.needsConfigService(convertedBody)) {
imports.boot.add('ConfigService');
}
@@ -371,4 +158,3 @@ class ServiceMethodConverter {
}
module.exports = ServiceMethodConverter;

View File

@@ -0,0 +1,374 @@
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 = this.postProcessCleanup(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段6】添加缩进
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsBody = tsBody.split('\n').map(line => ' ' + line).join('\n');
return tsBody;
}
/**
* 【阶段5】后处理清理
* 修复转换后的常见语法错误
*/
postProcessCleanup(tsBody) {
// 1. 修复 this.fs. 和 this.path. → fs. 和 path.
tsBody = tsBody.replace(/this\.fs\./g, 'fs.');
tsBody = tsBody.replace(/this\.path\./g, 'path.');
// 2. 修复逻辑运算符优先级问题
// if (!this.config.get('xxx') === "yyy") → if (this.config.get('xxx') !== "yyy")
tsBody = tsBody.replace(/if\s*\(\s*!this\.config\.get\([^)]+\)\s*===\s*([^)]+)\)/g, (match) => {
// 提取内容
const configCall = match.match(/this\.config\.get\([^)]+\)/)[0];
const value = match.match(/===\s*([^)]+)\)/)[1].trim();
return `if (${configCall} !== ${value})`;
});
// 3. 修复 !xxx === "yyy" → xxx !== "yyy" (通用)
tsBody = tsBody.replace(/!\s*([a-zA-Z_$.()[\]]+)\s*===\s*([^;)\n]+)/g, '$1 !== $2');
// 4. 修复复杂表达式的 .exists()
// this.config.get('xxx' + yyy).exists() → fs.existsSync(this.config.get('xxx' + yyy))
tsBody = tsBody.replace(/(this\.config\.get\([^)]+\))\.exists\(\)/g, 'fs.existsSync($1)');
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.exists() → fs.existsSync(this.config.get('property'))
// 必须在WebAppEnvs.get().property之前处理
tsBody = tsBody.replace(/WebAppEnvs\.get\(\)\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 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.exists() → fs.existsSync(this.config.get('property'))
// 必须在GlobalConfig.property之前处理
tsBody = tsBody.replace(/GlobalConfig\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 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
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// FileUtils工具类
tsBody = tsBody.replace(/FileUtils\.cleanDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsBody = tsBody.replace(/FileUtils\.copyFile\(([^,]+),\s*([^)]+)\)/g, 'fs.copyFileSync($1, $2)');
tsBody = tsBody.replace(/FileUtils\.deleteDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsBody = tsBody.replace(/FileUtils\.readFileToString\(([^)]+)\)/g, 'fs.readFileSync($1, \'utf-8\')');
tsBody = tsBody.replace(/FileUtils\.writeStringToFile\(([^,]+),\s*([^)]+)\)/g, 'fs.writeFileSync($1, $2, \'utf-8\')');
// File API
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)');
tsBody = tsBody.replace(/(\w+)\.getPath\(\)/g, '$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() !== \'\')');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【异常处理】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// try-catch语句catch (Exception e) → catch (error)
tsBody = tsBody.replace(/catch\s*\(\s*(?:Exception|RuntimeException|Throwable)\s+(\w+)\s*\)/g, 'catch ($1)');
// 异常方法调用e.getMessage() → e.message
tsBody = tsBody.replace(/(\w+)\.getMessage\(\)/g, '$1.message');
// 异常抛出
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;

View File

@@ -0,0 +1,91 @@
/**
* 基础语法转换器
*
* 负责Java基础语法到TypeScript的转换
* - 变量声明
* - 循环语句
* - Lambda表达式
* - 集合实例化
*/
class BasicSyntaxConverter {
/**
* 转换基础语法
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. 变量声明Type varName = value; → const varName: Type = value;
tsCode = tsCode.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)
tsCode = tsCode.replace(/for\s*\(\s*[\w<>,\s]+\s+(\w+)\s*:\s*([^)]+)\)/g, 'for (const $1 of $2)');
// 3. Lambda表达式item -> expression → item => expression
tsCode = tsCode.replace(/(\w+)\s*->\s*/g, '$1 => ');
tsCode = tsCode.replace(/\(([^)]+)\)\s*->\s*/g, '($1) => ');
// 4. Java实例化new ArrayList<>() → []
tsCode = tsCode.replace(/new\s+ArrayList<[^>]*>\(\)/g, '[]');
tsCode = tsCode.replace(/new\s+LinkedList<[^>]*>\(\)/g, '[]');
tsCode = tsCode.replace(/new\s+HashMap<[^>]*>\(\)/g, '{}');
tsCode = tsCode.replace(/new\s+HashSet<[^>]*>\(\)/g, 'new Set()');
return tsCode;
}
/**
* 映射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;
}
}
module.exports = BasicSyntaxConverter;

View File

@@ -0,0 +1,49 @@
/**
* 异常处理转换器
*
* 负责Java异常到TypeScript/NestJS异常的转换
*/
class ExceptionConverter {
/**
* 转换异常处理
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. try-catch语句catch (Exception e) → catch (e)
tsCode = tsCode.replace(/catch\s*\(\s*(?:Exception|RuntimeException|Throwable|IOException|SQLException)\s+(\w+)\s*\)/g, 'catch ($1)');
// 2. 异常方法调用e.getMessage() → e.message
tsCode = tsCode.replace(/(\w+)\.getMessage\(\)/g, '$1.message');
tsCode = tsCode.replace(/(\w+)\.printStackTrace\(\)/g, 'console.error($1)');
// 3. 异常抛出
tsCode = tsCode.replace(/throw\s+new\s+CommonException\(/g, 'throw new BadRequestException(');
tsCode = tsCode.replace(/throw\s+new\s+AuthException\(/g, 'throw new UnauthorizedException(');
tsCode = tsCode.replace(/throw\s+new\s+RuntimeException\(/g, 'throw new Error(');
tsCode = tsCode.replace(/throw\s+new\s+Exception\(/g, 'throw new Error(');
tsCode = tsCode.replace(/throw\s+new\s+IOException\(/g, 'throw new Error(');
tsCode = tsCode.replace(/throw\s+new\s+SQLException\(/g, 'throw new Error(');
return tsCode;
}
/**
* 分析需要的异常imports
*/
analyzeImports(tsCode) {
const imports = new Set();
if (tsCode.includes('BadRequestException')) {
imports.add('BadRequestException');
}
if (tsCode.includes('UnauthorizedException')) {
imports.add('UnauthorizedException');
}
return Array.from(imports);
}
}
module.exports = ExceptionConverter;

View File

@@ -0,0 +1,31 @@
/**
* 类型转换器
*
* 负责Java类型到TypeScript类型的转换
*/
class TypeConverter {
/**
* 转换类型
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. 基础类型
tsCode = tsCode.replace(/\bInteger\b/g, 'number');
tsCode = tsCode.replace(/\bLong\b/g, 'number');
tsCode = tsCode.replace(/\bDouble\b/g, 'number');
tsCode = tsCode.replace(/\bFloat\b/g, 'number');
// 2. Java对象类型
tsCode = tsCode.replace(/\bJSONObject\b/g, 'Record<string, any>');
tsCode = tsCode.replace(/\bId\b/g, 'number');
// 3. Java类型转换语法(Type) value → value
tsCode = tsCode.replace(/\(\s*(?:int|long|double|float|String|Integer|Long|Double|Float|number)\s*\)\s*/g, '');
return tsCode;
}
}
module.exports = TypeConverter;

View File

@@ -0,0 +1,27 @@
/**
* 集合判空转换器
*
* Java集合判空方法 → TypeScript数组判空
*/
class CollectionConverter {
/**
* 转换集合判空
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. CollectionUtil.isEmpty/isNotEmpty
tsCode = tsCode.replace(/(?:CollectionUtil|CollUtil)\.isEmpty\(([^)]+)\)/g, '(!$1 || $1.length === 0)');
tsCode = tsCode.replace(/(?:CollectionUtil|CollUtil)\.isNotEmpty\(([^)]+)\)/g, '($1 && $1.length > 0)');
// 2. list.isEmpty() → list.length === 0
// 支持链式调用obj.getConfig().isEmpty()
tsCode = tsCode.replace(/([a-zA-Z_$][\w$.]*)\.isEmpty\(\)/g, '$1.length === 0');
tsCode = tsCode.replace(/!([a-zA-Z_$][\w$.]*)\.isEmpty\(\)/g, '$1.length > 0');
return tsCode;
}
}
module.exports = CollectionConverter;

View File

@@ -0,0 +1,53 @@
/**
* 配置访问转换器
*
* WebAppEnvs / GlobalConfig → ConfigService
*/
class ConfigConverter {
/**
* 转换配置访问
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. WebAppEnvs.get().property.exists() → fs.existsSync(this.config.get('property'))
tsCode = tsCode.replace(/WebAppEnvs\.get\(\)\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 2. WebAppEnvs.get().property → this.config.get('property')
tsCode = tsCode.replace(/WebAppEnvs\.get\(\)\.(\w+)/g, (match, prop) => {
return `this.config.get('${prop}')`;
});
// 3. WebAppEnvs.get() → this.config
tsCode = tsCode.replace(/WebAppEnvs\.get\(\)/g, 'this.config');
// 4. WebAppEnvs.staticProperty → this.config.get('staticProperty')
tsCode = tsCode.replace(/WebAppEnvs\.(\w+)/g, (match, prop) => {
return `this.config.get('${prop}')`;
});
// 5. GlobalConfig.property.exists() → fs.existsSync(this.config.get('property'))
tsCode = tsCode.replace(/GlobalConfig\.(\w+)\.exists\(\)/g, (match, prop) => {
return `fs.existsSync(this.config.get('${prop}'))`;
});
// 6. GlobalConfig.property → this.config.get('property')
tsCode = tsCode.replace(/GlobalConfig\.(\w+)/g, (match, prop) => {
return `this.config.get('${prop}')`;
});
return tsCode;
}
/**
* 分析是否需要ConfigService
*/
needsConfigService(tsCode) {
return tsCode.includes('this.config.');
}
}
module.exports = ConfigConverter;

View File

@@ -0,0 +1,63 @@
/**
* 文件操作转换器
*
* Java File API / FileUtils → Node.js fs/path
*/
class FileConverter {
/**
* 转换文件操作
*/
convert(javaCode) {
let tsCode = javaCode;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【FileUtils工具类】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsCode = tsCode.replace(/FileUtils\.cleanDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsCode = tsCode.replace(/FileUtils\.copyFile\(([^,]+),\s*([^)]+)\)/g, 'fs.copyFileSync($1, $2)');
tsCode = tsCode.replace(/FileUtils\.deleteDirectory\(([^)]+)\)/g, 'fs.rmSync($1, { recursive: true, force: true })');
tsCode = tsCode.replace(/FileUtils\.readFileToString\(([^)]+)\)/g, 'fs.readFileSync($1, \'utf-8\')');
tsCode = tsCode.replace(/FileUtils\.writeStringToFile\(([^,]+),\s*([^)]+)\)/g, 'fs.writeFileSync($1, $2, \'utf-8\')');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【File API】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tsCode = tsCode.replace(/new\s+File\(([^)]+)\)/g, '$1');
tsCode = tsCode.replace(/(\w+)\.exists\(\)/g, 'fs.existsSync($1)');
tsCode = tsCode.replace(/(\w+)\.isDirectory\(\)/g, 'fs.lstatSync($1).isDirectory()');
tsCode = tsCode.replace(/(\w+)\.getName\(\)/g, 'path.basename($1)');
tsCode = tsCode.replace(/(\w+)\.listFiles\(\)/g, 'fs.readdirSync($1)');
tsCode = tsCode.replace(/(\w+)\.getPath\(\)/g, '$1');
tsCode = tsCode.replace(/(\w+)\.getAbsolutePath\(\)/g, 'path.resolve($1)');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Java NIO API】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Files.list(Paths.get(x)) → fs.readdirSync(x).map(...)
tsCode = tsCode.replace(/Files\.list\(Paths\.get\(([^)]+)\)\)/g, 'fs.readdirSync($1)');
// Paths.get(x) → x
tsCode = tsCode.replace(/Paths\.get\(([^)]+)\)/g, '$1');
return tsCode;
}
/**
* 分析是否需要Node.js模块
*/
analyzeNodeModules(tsCode) {
const modules = new Set();
if (tsCode.includes('fs.')) {
modules.add('fs');
}
if (tsCode.includes('path.')) {
modules.add('path');
}
return Array.from(modules);
}
}
module.exports = FileConverter;

View File

@@ -0,0 +1,27 @@
/**
* JSON工具类转换器
*
* JSONUtil / JSON相关 → TypeScript JSON / JsonUtils
*/
class JsonConverter {
/**
* 转换JSON工具类
*/
convert(javaCode) {
let tsCode = javaCode;
// JSONUtil.parseObj() → JSON.parse()
tsCode = tsCode.replace(/JSONUtil\.parseObj\(([^)]+)\)/g, 'JSON.parse($1)');
// JSONUtil.toBean() → 需要手动处理暂时保留TODO
tsCode = tsCode.replace(/JSONUtil\.toBean\(([^,]+),\s*([^)]+)\.class\)/g, '/* TODO: JSONUtil.toBean($1, $2) */Object.assign(new $2(), JSON.parse(JSON.stringify($1)))');
// JSONObject → Record<string, any>
tsCode = tsCode.replace(/JSONObject/g, 'Record<string, any>');
return tsCode;
}
}
module.exports = JsonConverter;

View File

@@ -0,0 +1,38 @@
/**
* 对象工具类转换器
*
* ObjectUtil / BeanUtils等 → TypeScript对象操作
*/
class ObjectConverter {
/**
* 转换对象工具类
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. ObjectUtil.isNotEmpty() → value && ...
tsCode = tsCode.replace(/ObjectUtil\.isNotEmpty\(([^)]+)\)/g, '($1 && Object.keys($1).length > 0)');
tsCode = tsCode.replace(/ObjectUtil\.isEmpty\(([^)]+)\)/g, '(!$1 || Object.keys($1).length === 0)');
// 2. ObjectUtil.defaultIfNull(a, b) → a ?? b
tsCode = tsCode.replace(/ObjectUtil\.defaultIfNull\(([^,]+),\s*([^)]+)\)/g, '$1 ?? $2');
// 3. BeanUtils.copyProperties(src, dest) → Object.assign(dest, src)
tsCode = tsCode.replace(/BeanUtils\.copyProperties\(([^,]+),\s*([^)]+)\)/g, 'Object.assign($2, $1)');
// 4. System.out.println() → console.log()
tsCode = tsCode.replace(/System\.out\.println\(/g, 'console.log(');
tsCode = tsCode.replace(/System\.err\.println\(/g, 'console.error(');
// 5. System.currentTimeMillis() → Date.now()
tsCode = tsCode.replace(/System\.currentTimeMillis\(\)/g, 'Date.now()');
// 6. Assert.notNull() → if (!x) throw new Error()
tsCode = tsCode.replace(/Assert\.notNull\(([^,]+),\s*([^)]+)\)/g, 'if (!$1) throw new BadRequestException($2)');
return tsCode;
}
}
module.exports = ObjectConverter;

View File

@@ -0,0 +1,35 @@
/**
* 字符串方法转换器
*
* Java String方法 → TypeScript String方法
*/
class StringConverter {
/**
* 转换字符串方法
*/
convert(javaCode) {
let tsCode = javaCode;
// 1. .equals() → ===
// 支持方法调用结果path.basename(child).equals("sql")
tsCode = tsCode.replace(/([a-zA-Z_$][\w$.()]+)\.equals\(([^)]+)\)/g, '$1 === $2');
// 2. .equalsIgnoreCase() → .toLowerCase() === xxx.toLowerCase()
tsCode = tsCode.replace(/([a-zA-Z_$][\w$.()]+)\.equalsIgnoreCase\(([^)]+)\)/g, '$1.toLowerCase() === $2.toLowerCase()');
// 3. .contains() → .includes()
tsCode = tsCode.replace(/\.contains\(/g, '.includes(');
// 4. StringUtils.isEmpty/isNotEmpty
tsCode = tsCode.replace(/StringUtils\.isEmpty\(([^)]+)\)/g, '(!$1 || $1.trim() === \'\')');
tsCode = tsCode.replace(/StringUtils\.isNotEmpty\(([^)]+)\)/g, '($1 && $1.trim() !== \'\')');
// 5. String.valueOf() → String()
tsCode = tsCode.replace(/String\.valueOf\(([^)]+)\)/g, 'String($1)');
return tsCode;
}
}
module.exports = StringConverter;

View File

@@ -39,3 +39,4 @@ export * from "./infra/queue/job-scheduler.service";
export * from "./infra/events/event-listener.service";
export * from "./infra/events/event-bus";
export * from "./infra/common/result";
export { ConfigService } from "@nestjs/config";