From 7e6cf74808c552cb611798387522142ed6d88e87 Mon Sep 17 00:00:00 2001 From: wanwu Date: Wed, 29 Oct 2025 15:12:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BAJava=E2=86=92V1?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E5=85=A8=E5=B1=80=E6=98=A0=E5=B0=84=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JAVA_TO_BOOT_MAPPING.md | 258 +++++++++++++++ .../converters/java-to-v1-mapper.js | 300 ++++++++++++++++++ .../mybatis/query-wrapper.converter.js | 34 +- .../converters/service-method-converter.js | 131 +++----- 4 files changed, 622 insertions(+), 101 deletions(-) create mode 100644 wwjcloud-nest-v1/tools/java-to-nestjs-migration/JAVA_TO_BOOT_MAPPING.md create mode 100644 wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/java-to-v1-mapper.js diff --git a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/JAVA_TO_BOOT_MAPPING.md b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/JAVA_TO_BOOT_MAPPING.md new file mode 100644 index 00000000..175a46dd --- /dev/null +++ b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/JAVA_TO_BOOT_MAPPING.md @@ -0,0 +1,258 @@ +# Java → wwjcloud-boot 能力映射表 + +> 本文档定义Java常用工具类/API与wwjcloud-boot框架能力的映射关系 +> +> **核心原则**: 不改业务逻辑,只替换Java写法为V1框架写法 + +## 📦 工具类映射 + +### 1. JSON处理 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `JSONUtil.parseObj(str)` | `JSON.parse(str)` | 原生JS | 解析JSON字符串 | +| `JSONUtil.toJsonStr(obj)` | `JSON.stringify(obj)` | 原生JS | 对象转JSON字符串 | +| `JSONUtil.toBean(json, Class)` | `Object.assign(new Class(), json)` | 原生JS | JSON转对象 | +| `JSONObject` | `Record` | 原生TS | JSON对象类型 | +| `JsonLoadUtils.loadJsonString(file, name)` | `JSON.parse(fs.readFileSync(path.join(file, name), 'utf-8'))` | `fs`, `path` | 读取JSON文件 | + +### 2. 字符串工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `StringUtils.isEmpty(str)` | `StringUtils.isEmpty(str)` | `@wwjBoot` | 字符串为空判断 | +| `StringUtils.isNotEmpty(str)` | `StringUtils.isNotEmpty(str)` | `@wwjBoot` | 字符串非空判断 | +| `StringUtils.isBlank(str)` | `StringUtils.isEmpty(str)` | `@wwjBoot` | 同isEmpty | +| `String.equals(str)` | `=== str` | 原生JS | 字符串相等 | +| `String.equalsIgnoreCase(str)` | `.toLowerCase() === str.toLowerCase()` | 原生JS | 忽略大小写相等 | +| `String.contains(str)` | `.includes(str)` | 原生JS | 包含字符串 | + +### 3. 集合工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `CollectionUtil.isEmpty(list)` | `StringUtils.isEmptyArray(list)` | `@wwjBoot` | 数组为空判断 | +| `CollectionUtil.isNotEmpty(list)` | `StringUtils.isNotEmptyArray(list)` | `@wwjBoot` | 数组非空判断 | +| `list.add(item)` | `list.push(item)` | 原生JS | 添加元素 | +| `list.size()` | `list.length` | 原生JS | 数组长度 | +| `list.isEmpty()` | `list.length === 0` | 原生JS | 数组为空 | + +### 4. 对象工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `ObjectUtil.isEmpty(obj)` | `!obj` | 原生JS | 对象为空 | +| `ObjectUtil.isNotEmpty(obj)` | `!!obj` | 原生JS | 对象非空 | +| `BeanUtils.copyProperties(src, dest)` | `Object.assign(dest, src)` | 原生JS | 属性复制 | + +### 5. 文件工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `Files.list(Paths.get(dir))` | `fs.readdirSync(dir)` | `fs` | 列出目录 | +| `Paths.get(path)` | `path.join(path)` | `path` | 路径拼接 | +| `file.exists()` | `fs.existsSync(file)` | `fs` | 文件是否存在 | +| `file.isDirectory()` | `fs.statSync(file).isDirectory()` | `fs` | 是否目录 | +| `file.getName()` | `path.basename(file)` | `path` | 文件名 | +| `file.listFiles()` | `fs.readdirSync(file)` | `fs` | 列出文件 | +| `FileUtils.cleanDirectory(dir)` | `fs.rmSync(dir, { recursive: true, force: true })` | `fs` | 清空目录 | +| `FileUtils.copyFile(src, dest)` | `fs.copyFileSync(src, dest)` | `fs` | 复制文件 | +| `FileUtils.deleteDirectory(dir)` | `fs.rmSync(dir, { recursive: true, force: true })` | `fs` | 删除目录 | +| `FileUtils.readFileToString(file)` | `fs.readFileSync(file, 'utf-8')` | `fs` | 读取文件 | +| `FileUtils.writeStringToFile(file, data)` | `fs.writeFileSync(file, data, 'utf-8')` | `fs` | 写入文件 | + +### 6. Stream API + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `list.stream()` | `list` | 原生JS | 数组本身支持map/filter | +| `.filter(predicate)` | `.filter(predicate)` | 原生JS | 过滤 | +| `.map(mapper)` | `.map(mapper)` | 原生JS | 映射 | +| `.collect(Collectors.toList())` | 无需操作 | 原生JS | 数组方法返回数组 | +| `Collectors.toList()` | 无需操作 | 原生JS | 删除即可 | + +### 7. 断言工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `Assert.notNull(obj, msg)` | `if (!obj) throw new BadRequestException(msg)` | `@nestjs/common` | 非空断言 | +| `Assert.isTrue(condition, msg)` | `if (!condition) throw new BadRequestException(msg)` | `@nestjs/common` | 条件断言 | + +### 8. 系统工具 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `System.out.println(msg)` | `console.log(msg)` | 原生JS | 控制台输出 | +| `System.err.println(msg)` | `console.error(msg)` | 原生JS | 错误输出 | +| `System.currentTimeMillis()` | `Date.now()` | 原生JS | 当前时间戳(毫秒) | +| `System.currentTimeMillis() / 1000` | `Math.floor(Date.now() / 1000)` | 原生JS | 当前时间戳(秒) | + +### 9. 异常处理 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `throw new CommonException(msg)` | `throw new BadRequestException(msg)` | `@nestjs/common` | 业务异常 | +| `throw new AuthException(msg)` | `throw new UnauthorizedException(msg)` | `@nestjs/common` | 认证异常 | +| `catch (Exception e)` | `catch (e)` | 原生JS | 捕获异常 | +| `catch (IOException e)` | `catch (e)` | 原生JS | 捕获异常 | +| `e.getMessage()` | `e.message` | 原生JS | 异常消息 | +| `e.printStackTrace()` | `console.error(e)` | 原生JS | 打印堆栈 | + +### 10. 日期时间 + +| Java | wwjcloud-boot | 导入路径 | 说明 | +|------|---------------|---------|------| +| `LocalDateTime.now()` | `new Date()` | 原生JS | 当前时间 | +| `LocalDate.now()` | `new Date()` | 原生JS | 当前日期 | +| `Date` | `Date` | 原生JS | 日期类型 | +| `Timestamp` | `Date` | 原生JS | 时间戳类型 | + +## 🗄️ MyBatis → TypeORM 映射 + +### 1. 查询构造器 + +| Java (MyBatis) | wwjcloud-boot (TypeORM) | 说明 | +|----------------|------------------------|------| +| `QueryWrapper` | `查询条件对象` | 需要转换为TypeORM查询 | +| `new QueryWrapper<>()` | `{}` | 初始化为空对象 | +| `.eq("field", value)` | `{ field: value }` | 等于条件 | +| `.like("field", value)` | `{ field: Like(\`%${value}%\`) }` | 模糊查询 | +| `.in("field", list)` | `{ field: In(list) }` | IN查询 | + +### 2. 分页查询 + +| Java (MyBatis) | wwjcloud-boot (TypeORM) | 说明 | +|----------------|------------------------|------| +| `IPage` | `[Entity[], number]` | 分页结果 | +| `new Page<>(page, limit)` | `{ skip: (page-1)*limit, take: limit }` | 分页参数 | +| `mapper.selectPage(page, wrapper)` | `repository.findAndCount({ where, skip, take })` | 分页查询 | +| `iPage.getTotal()` | `total` | 总数 | +| `iPage.getRecords()` | `records` | 记录列表 | + +### 3. Mapper方法 + +| Java (MyBatis) | wwjcloud-boot (TypeORM) | 说明 | +|----------------|------------------------|------| +| `mapper.selectOne(wrapper)` | `repository.findOne({ where })` | 查询单条 | +| `mapper.selectList(wrapper)` | `repository.find({ where })` | 查询列表 | +| `mapper.insert(entity)` | `repository.save(entity)` | 插入 | +| `mapper.updateById(entity)` | `repository.save(entity)` | 更新 | +| `mapper.deleteById(id)` | `repository.delete(id)` | 删除 | +| `mapper.delete(wrapper)` | `repository.delete({ where })` | 条件删除 | +| `mapper.selectCount(wrapper)` | `repository.count({ where })` | 计数 | + +## 🎯 特殊业务工具映射 + +### Java特有工具 → Core层实现 + +以下Java工具是业务特定的,需要在Core层实现或标记TODO: + +| Java工具 | 处理方式 | 说明 | +|---------|---------|------| +| `ImageUtils.imageToBase64()` | TODO | 图片转Base64,需Core层实现 | +| `AddonInstallJavaTools` | TODO | 插件安装工具,需Core层实现 | +| `LocalMavenTools` | TODO | Maven工具,需Core层实现 | +| `niucloudService` | 注入Service | 业务Service,通过DI注入 | +| `coreAddonService` | 注入Service | 业务Service,通过DI注入 | + +## 🔄 转换示例 + +### 示例1: 文件操作 + +**Java代码**: +```java +String addonPath = WebAppEnvs.get().webRootDownAddon + addon + "/"; +if (!Files.exists(Paths.get(addonPath))) { + throw new CommonException("插件不存在"); +} +FileUtils.cleanDirectory(addonPath); +``` + +**V1代码**: +```typescript +const addonPath = this.appConfig.webRootDownAddon + addon + "/"; +if (!fs.existsSync(addonPath)) { + throw new BadRequestException("插件不存在"); +} +fs.rmSync(addonPath, { recursive: true, force: true }); +``` + +### 示例2: JSON操作 + +**Java代码**: +```java +JSONObject info = JSONUtil.parseObj(JsonLoadUtils.loadJsonString(file, "info.json")); +AddonInfo addonInfo = JSONUtil.toBean(info, AddonInfo.class); +``` + +**V1代码**: +```typescript +const info = JSON.parse(fs.readFileSync(path.join(file, "info.json"), 'utf-8')); +const addonInfo = Object.assign(new AddonInfo(), info); +``` + +### 示例3: 集合操作 + +**Java代码**: +```java +List localAddons = Files.list(Paths.get(addonPath)) + .filter(file -> file.isDirectory()) + .map(file -> file.getName()) + .collect(Collectors.toList()); + +if (CollectionUtil.isNotEmpty(searchParam.getSearch()) && list.size() > 0) { + list = list.stream() + .filter(item -> item.getTitle().contains(searchParam.getSearch())) + .collect(Collectors.toList()); +} +``` + +**V1代码**: +```typescript +const localAddons = fs.readdirSync(addonPath) + .filter(file => fs.statSync(path.join(addonPath, file)).isDirectory()); + +if (StringUtils.isNotEmpty(searchParam.getSearch()) && list.length > 0) { + list = list.filter(item => item.getTitle().includes(searchParam.getSearch())); +} +``` + +### 示例4: MyBatis查询 + +**Java代码**: +```java +QueryWrapper queryWrapper = new QueryWrapper<>(); +queryWrapper.eq("site_id", siteId); + +IPage iPage = addonLogMapper.selectPage( + new Page<>(page, limit), + queryWrapper +); + +return PageResult.build(page, limit, iPage.getTotal()).setData(iPage.getRecords()); +``` + +**V1代码**: +```typescript +const [records, total] = await this.addonLogRepository.findAndCount({ + where: { siteId }, + skip: (page - 1) * limit, + take: limit +}); + +return PageResult.build(page, limit, total).setData(records); +``` + +## 📌 转换优先级 + +1. **原生JS/TS能力** (最高优先级) +2. **wwjcloud-boot工具类** +3. **NestJS内置功能** +4. **TypeORM API** +5. **Core层业务实现** (最低优先级,标记TODO) + +--- + +**更新日期**: 2025-01-XX +**维护者**: Migration Tool Team + diff --git a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/java-to-v1-mapper.js b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/java-to-v1-mapper.js new file mode 100644 index 00000000..be1e6b1e --- /dev/null +++ b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/java-to-v1-mapper.js @@ -0,0 +1,300 @@ +/** + * Java → wwjcloud-boot V1框架 全局映射器 + * + * 核心原则:不改业务逻辑,只替换Java写法为V1框架写法 + * + * 参考文档:JAVA_TO_BOOT_MAPPING.md + */ +class JavaToV1Mapper { + constructor() { + this.bootImports = new Set(); + this.nodeModules = new Set(); + this.typeormImports = new Set(); + this.nestjsImports = new Set(); + } + + /** + * 核心转换方法 + */ + convert(javaCode) { + let tsCode = javaCode; + + // 执行转换(按依赖顺序) + tsCode = this.convertTypes(tsCode); + tsCode = this.convertMyBatis(tsCode); + tsCode = this.convertJavaUtils(tsCode); + tsCode = this.convertStreamAPI(tsCode); + tsCode = this.convertExceptions(tsCode); + tsCode = this.convertSystemCalls(tsCode); + + return tsCode; + } + + /** + * 1. 类型转换 + */ + convertTypes(code) { + let result = code; + + // 基础类型 + result = result.replace(/\bInteger\b/g, 'number'); + result = result.replace(/\bLong\b/g, 'number'); + result = result.replace(/\bDouble\b/g, 'number'); + result = result.replace(/\bFloat\b/g, 'number'); + result = result.replace(/\bBoolean\b/g, 'boolean'); + + // 集合类型 + result = result.replace(/List<([^>]+)>/g, '$1[]'); + result = result.replace(/ArrayList<([^>]+)>/g, '$1[]'); + result = result.replace(/LinkedList<([^>]+)>/g, '$1[]'); + result = result.replace(/Set<([^>]+)>/g, '$1[]'); + result = result.replace(/HashSet<([^>]+)>/g, '$1[]'); + result = result.replace(/Map<([^,]+),\s*([^>]+)>/g, 'Record<$1, $2>'); + result = result.replace(/HashMap<([^,]+),\s*([^>]+)>/g, 'Record<$1, $2>'); + + // JSON类型 + result = result.replace(/\bJSONObject\b/g, 'Record'); + + // 日期类型 + result = result.replace(/\bLocalDateTime\b/g, 'Date'); + result = result.replace(/\bLocalDate\b/g, 'Date'); + result = result.replace(/\bTimestamp\b/g, 'Date'); + + // ID类型 + result = result.replace(/\bId\b/g, 'number'); + + return result; + } + + /** + * 2. MyBatis → TypeORM + */ + convertMyBatis(code) { + let result = code; + + // QueryWrapper + result = result.replace(/QueryWrapper<[^>]+>\s*/g, ''); + result = result.replace(/new\s+QueryWrapper<[^>]*>\(\)/g, '{}'); + + // Page/IPage + result = result.replace(/IPage<[^>]+>\s*/g, ''); + result = result.replace(/new\s+Page<[^>]*>\([^)]+\)/g, '{ /* pagination */ }'); + + // Mapper调用 → Repository调用 + // xxxMapper.selectOne() → this.xxxRepository.findOne() + result = result.replace(/(\w+)Mapper\.selectOne\(/g, 'this.$1Repository.findOne('); + result = result.replace(/(\w+)Mapper\.selectList\(/g, 'this.$1Repository.find('); + result = result.replace(/(\w+)Mapper\.selectPage\(/g, 'this.$1Repository.findAndCount('); + result = result.replace(/(\w+)Mapper\.insert\(/g, 'this.$1Repository.save('); + result = result.replace(/(\w+)Mapper\.updateById\(/g, 'this.$1Repository.save('); + result = result.replace(/(\w+)Mapper\.deleteById\(/g, 'this.$1Repository.delete('); + result = result.replace(/(\w+)Mapper\.delete\(/g, 'this.$1Repository.delete('); + result = result.replace(/(\w+)Mapper\.selectCount\(/g, 'this.$1Repository.count('); + + // PageResult保持不变(来自@wwjBoot) + // iPage.getTotal() → total + result = result.replace(/(\w+)\.getTotal\(\)/g, 'total'); + result = result.replace(/(\w+)\.getRecords\(\)/g, 'records'); + + this.bootImports.add('PageResult'); + + return result; + } + + /** + * 3. Java工具类 → V1框架工具类 + */ + convertJavaUtils(code) { + let result = code; + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【JSON工具】 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // JSONUtil.parseObj() → JSON.parse() + result = result.replace(/JSONUtil\.parseObj\(/g, 'JSON.parse('); + + // JSONUtil.toJsonStr() → JSON.stringify() + result = result.replace(/JSONUtil\.toJsonStr\(/g, 'JSON.stringify('); + + // JSONUtil.toBean(json, Class.class) → Object.assign(new Class(), json) + result = result.replace(/JSONUtil\.toBean\(([^,]+),\s*(\w+)\.class\)/g, 'Object.assign(new $2(), $1)'); + + // JsonLoadUtils.loadJsonString(file, name) → JSON.parse(fs.readFileSync(path.join(file, name), 'utf-8')) + if (result.includes('JsonLoadUtils.loadJsonString')) { + result = result.replace(/JsonLoadUtils\.loadJsonString\(([^,]+),\s*([^)]+)\)/g, + 'JSON.parse(fs.readFileSync(path.join($1, $2), \'utf-8\'))'); + this.nodeModules.add('fs'); + this.nodeModules.add('path'); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【Bean工具】 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // BeanUtils.copyProperties() → Object.assign() + result = result.replace(/BeanUtils\.copyProperties\(([^,]+),\s*([^)]+)\)/g, 'Object.assign($2, $1)'); + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【断言工具】 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // Assert.notNull(obj, msg) → if (!obj) throw new BadRequestException(msg) + result = result.replace(/Assert\.notNull\(([^,]+),\s*([^)]+)\);?/g, + 'if (!$1) throw new BadRequestException($2);'); + + // Assert.isTrue(condition, msg) → if (!condition) throw new BadRequestException(msg) + result = result.replace(/Assert\.isTrue\(([^,]+),\s*([^)]+)\);?/g, + 'if (!($1)) throw new BadRequestException($2);'); + + this.nestjsImports.add('BadRequestException'); + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【文件工具】(已在file.converter.js中处理) + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // Files.list(Paths.get()) → fs.readdirSync() + if (result.includes('Files.list(Paths.get(')) { + result = result.replace(/Files\.list\(Paths\.get\(([^)]+)\)\)/g, 'fs.readdirSync($1)'); + this.nodeModules.add('fs'); + } + + // Paths.get() → path.join() + result = result.replace(/Paths\.get\(/g, 'path.join('); + if (result.includes('path.join')) { + this.nodeModules.add('path'); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【对象工具】 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // ObjectUtil.isEmpty() → !obj + result = result.replace(/ObjectUtil\.isEmpty\(([^)]+)\)/g, '!$1'); + + // ObjectUtil.isNotEmpty() → !!obj + result = result.replace(/ObjectUtil\.isNotEmpty\(([^)]+)\)/g, '!!$1'); + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【字符串工具】保留StringUtils from wwjBoot + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // StringUtils保持不变,已在string.converter.js中处理 + if (result.includes('StringUtils.')) { + this.bootImports.add('StringUtils'); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【集合工具】保留StringUtils from wwjBoot + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + // CollectionUtil转换为StringUtils (已在collection.converter.js中处理) + // CollectionUtil.isEmpty() → StringUtils.isEmptyArray() + // CollectionUtil.isNotEmpty() → StringUtils.isNotEmptyArray() + + return result; + } + + /** + * 4. Stream API → 数组方法 + */ + convertStreamAPI(code) { + let result = code; + + // list.stream() → 移除 + result = result.replace(/\.stream\(\)/g, ''); + + // .collect(Collectors.toList()) → 移除 + result = result.replace(/\.collect\(Collectors\.toList\(\)\)/g, ''); + + // .filter() / .map() 保持不变(JS原生支持) + + // list.add() → list.push() + result = result.replace(/(\w+)\.add\(/g, '$1.push('); + + // list.size() → list.length + result = result.replace(/(\w+)\.size\(\)/g, '$1.length'); + + // list.isEmpty() → list.length === 0 + result = result.replace(/(\w+)\.isEmpty\(\)/g, '$1.length === 0'); + + // !list.isEmpty() → list.length > 0 + result = result.replace(/!(\w+)\.length === 0/g, '$1.length > 0'); + + return result; + } + + /** + * 5. 异常处理 + */ + convertExceptions(code) { + let result = code; + + // throw new CommonException() → throw new BadRequestException() + result = result.replace(/throw new CommonException\(/g, 'throw new BadRequestException('); + + // throw new AuthException() → throw new UnauthorizedException() + result = result.replace(/throw new AuthException\(/g, 'throw new UnauthorizedException('); + + // catch (Exception e) → catch (e) + // catch (IOException e) → catch (e) + result = result.replace(/catch\s*\(\s*\w+Exception\s+(\w+)\s*\)/g, 'catch ($1)'); + + // e.getMessage() → e.message + result = result.replace(/(\w+)\.getMessage\(\)/g, '$1.message'); + + // e.printStackTrace() → console.error(e) + result = result.replace(/(\w+)\.printStackTrace\(\);?/g, 'console.error($1);'); + + this.nestjsImports.add('BadRequestException'); + this.nestjsImports.add('UnauthorizedException'); + + return result; + } + + /** + * 6. 系统调用 + */ + convertSystemCalls(code) { + let result = code; + + // System.out.println() → console.log() + result = result.replace(/System\.out\.println\(/g, 'console.log('); + + // System.err.println() → console.error() + result = result.replace(/System\.err\.println\(/g, 'console.error('); + + // System.currentTimeMillis() / 1000 → Math.floor(Date.now() / 1000) + result = result.replace(/System\.currentTimeMillis\(\)\s*\/\s*1000/g, 'Math.floor(Date.now() / 1000)'); + + // System.currentTimeMillis() → Date.now() + result = result.replace(/System\.currentTimeMillis\(\)/g, 'Date.now()'); + + return result; + } + + /** + * 获取所有需要导入的模块 + */ + getImports() { + return { + boot: Array.from(this.bootImports), + typeorm: Array.from(this.typeormImports), + nestjs: Array.from(this.nestjsImports), + nodeModules: Array.from(this.nodeModules) + }; + } + + /** + * 重置imports + */ + resetImports() { + this.bootImports.clear(); + this.nodeModules.clear(); + this.typeormImports.clear(); + this.nestjsImports.clear(); + } +} + +module.exports = JavaToV1Mapper; + diff --git a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/mybatis/query-wrapper.converter.js b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/mybatis/query-wrapper.converter.js index ffd2a3b6..c1dea1c3 100644 --- a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/mybatis/query-wrapper.converter.js +++ b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/mybatis/query-wrapper.converter.js @@ -1,7 +1,9 @@ /** * QueryWrapper转换器 * - * MyBatis QueryWrapper → TypeORM QueryBuilder + * MyBatis QueryWrapper → 移除(TypeORM直接使用where对象) + * + * 策略:QueryWrapper是MyBatis的查询构造器,在TypeORM中直接使用where条件对象即可 */ class QueryWrapperConverter { /** @@ -10,19 +12,33 @@ class QueryWrapperConverter { convert(javaCode) { let tsCode = javaCode; - // 1. new QueryWrapper() → 需要转换为TypeORM查询 - // 暂时标记为TODO,因为需要更复杂的上下文 - tsCode = tsCode.replace(/new\s+QueryWrapper<(\w+)>\(\)/g, '/* TODO: TypeORM Query for $1 */{}'); + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【策略】直接移除QueryWrapper相关代码,简化为where条件 + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 2. queryWrapper.eq("field", value) → TODO - tsCode = tsCode.replace(/(\w+)\.eq\(([^,]+),\s*([^)]+)\)/g, '/* TODO: .where("$2 = :value", { value: $3 }) */$1'); + // 1. 移除QueryWrapper类型声明 + // QueryWrapper → 删除 + tsCode = tsCode.replace(/QueryWrapper<[^>]+>\s*/g, ''); - // 3. queryWrapper.like("field", value) → TODO - tsCode = tsCode.replace(/(\w+)\.like\(([^,]+),\s*([^)]+)\)/g, '/* TODO: .where("$2 LIKE :value", { value: `%${$3}%` }) */$1'); + // 2. 移除new QueryWrapper<>() + // new QueryWrapper<>() → {} + // new QueryWrapper() → {} + tsCode = tsCode.replace(/new\s+QueryWrapper<[^>]*>\(\)/g, '{}'); + + // 3. 简化链式调用 + // queryWrapper.eq("field", value) → 保留但简化处理 + // 实际在mapper.converter.js中会将整个查询转换为TypeORM findOne/find return tsCode; } + + /** + * 分析需要的imports + */ + analyzeImports(tsCode) { + // QueryWrapper不需要额外import + return []; + } } module.exports = QueryWrapperConverter; - diff --git a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/service-method-converter.js b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/service-method-converter.js index 054712e6..9a85f8c1 100644 --- a/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/service-method-converter.js +++ b/wwjcloud-nest-v1/tools/java-to-nestjs-migration/converters/service-method-converter.js @@ -10,49 +10,29 @@ * 4. 分析需要的imports */ -// 导入各个转换器 -const BasicSyntaxConverter = require('./syntax/basic-syntax.converter'); -const TypeConverter = require('./syntax/type.converter'); -const ExceptionConverter = require('./syntax/exception.converter'); +// 导入全局映射器(新) +const JavaToV1Mapper = require('./java-to-v1-mapper'); +// 导入各个转换器(保留用于特定场景) +const BasicSyntaxConverter = require('./syntax/basic-syntax.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.basicSyntax = new BasicSyntaxConverter(); - this.type = new TypeConverter(); - this.exception = new ExceptionConverter(); + // 初始化全局映射器(优先使用) + this.v1Mapper = new JavaToV1Mapper(); + // 初始化特定转换器(用于特殊场景) + this.basicSyntax = new BasicSyntaxConverter(); 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(); } @@ -72,52 +52,26 @@ class ServiceMethodConverter { let tsBody = javaMethodBody; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段1】基础语法转换 + // 【核心转换】使用全局映射器(Java → V1框架) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - tsBody = this.basicSyntax.convert(tsBody); + this.v1Mapper.resetImports(); + tsBody = this.v1Mapper.convert(tsBody); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段2】类型转换 + // 【补充转换】特定场景处理 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - tsBody = this.type.convert(tsBody); + tsBody = this.basicSyntax.convert(tsBody); // Lambda、for-each等语法 + tsBody = this.config.convert(tsBody); // WebAppEnvs/GlobalConfig → AppConfigService + tsBody = this.file.convert(tsBody); // 文件操作补充转换 + tsBody = this.string.convert(tsBody); // String工具补充 + tsBody = this.collection.convert(tsBody); // Collection工具补充 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段3】工具类转换 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 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】MyBatis → TypeORM转换 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - tsBody = this.queryWrapper.convert(tsBody); - tsBody = this.mapper.convert(tsBody, context); - tsBody = this.pagination.convert(tsBody); - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段5】方法调用转换 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - tsBody = this.streamApi.convert(tsBody); - tsBody = this.methodCall.convert(tsBody); - tsBody = this.getterSetter.convert(tsBody); // 最后转换getter/setter - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段6】异常处理转换 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - tsBody = this.exception.convert(tsBody); - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段7】后处理清理 + // 【后处理】清理与格式化 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ tsBody = this.postProcessor.process(tsBody); - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【阶段8】添加缩进 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 添加缩进 tsBody = tsBody.split('\n').map(line => ' ' + line).join('\n'); return tsBody; @@ -130,49 +84,42 @@ class ServiceMethodConverter { * @returns {object} 需要导入的模块 */ analyzeImports(convertedBody) { + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // 【核心】从全局映射器获取imports + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const v1Imports = this.v1Mapper.getImports(); + const imports = { - nestjs: new Set(), - boot: new Set(), - nodeModules: new Set() + nestjs: new Set(v1Imports.nestjs), + boot: new Set(v1Imports.boot), + typeorm: new Set(v1Imports.typeorm), + nodeModules: new Set(v1Imports.nodeModules) }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【NestJS异常】 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - const nestjsImports = this.exception.analyzeImports(convertedBody); - nestjsImports.forEach(imp => imports.nestjs.add(imp)); - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【Node.js模块】 - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - const nodeModules = this.file.analyzeNodeModules(convertedBody); - nodeModules.forEach(mod => imports.nodeModules.add(mod)); - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【Boot层工具类】 + // 【补充】从特定转换器获取额外imports // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // StringUtils + // AppConfigService (from config.converter.js) + const configImports = this.config.analyzeImports(convertedBody); + configImports.forEach(imp => imports.boot.add(imp)); + + // StringUtils (from string.converter.js) const stringImports = this.string.analyzeImports(convertedBody); stringImports.forEach(imp => imports.boot.add(imp)); - // Collection判空(可能需要StringUtils) + // Collection utils (from collection.converter.js) const collectionImports = this.collection.analyzeImports(convertedBody); collectionImports.forEach(imp => imports.boot.add(imp)); - // JsonUtils - const jsonImports = this.json.analyzeImports(convertedBody); - jsonImports.forEach(imp => imports.boot.add(imp)); - - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - // 【配置访问】AppConfigService (Boot层) - // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - const configImports = this.config.analyzeImports(convertedBody); - configImports.forEach(imp => imports.boot.add(imp)); + // Node modules (from file.converter.js) + const nodeModules = this.file.analyzeNodeModules(convertedBody); + nodeModules.forEach(mod => imports.nodeModules.add(mod)); return { nestjs: Array.from(imports.nestjs), boot: Array.from(imports.boot), + typeorm: Array.from(imports.typeorm), nodeModules: Array.from(imports.nodeModules) }; }