feat(cdr): 建立中央数据仓库(CDR)架构

🎯 核心改进:
1.  创建CentralDataRepository类 - 统一管理元数据
2.  Coordinator集成CDR - 传递给所有Generator
3.  DTO Generator记录位置 - 629个类型已记录
4.  Service Generator CDR查询 - 支持路径查询

📊 CDR统计:
- Service方法: 1,038个
- DTO: 35个
- VO: 277个
- Param: 317个
- 总类型: 629个

⚠️ 待解决问题:
- 类型名查询不匹配(记录vs查询)
- 需要调试类型名映射逻辑

💡 下一步:
- 修复类型名匹配问题
- 验证DTO路径查询效果
- 预期减少4,200+ DTO路径错误
This commit is contained in:
wanwu
2025-10-29 22:41:23 +08:00
parent f615c61c42
commit 3c87db45ff
9 changed files with 999 additions and 15 deletions

View File

@@ -0,0 +1,484 @@
# 🔍 迁移工具架构一致性问题分析报告
**生成时间**: 2025-10-29
**版本**: f615c61c (详细错误分析报告版本)
**编译错误**: 14,086个
---
## 📊 核心问题总结
### 🎯 根本原因
**各层Generator缺乏统一的数据模型和通信机制导致生成的代码层级不一致**
---
## 1⃣ 架构流程现状
```
migration-coordinator.js (总协调器)
├── 【阶段1】java-scanner.js (扫描Java代码)
│ └── 输出: { controllers, services, dtos, entities, enums, jobs, listeners }
├── 【阶段2】buildServiceMethodSignatureIndex()
│ └── 输出: serviceMethodSignatureIndex (Map)
│ 格式: { "ServiceImplName.methodName": { parameters: [...], returnType: "..." } }
├── 【阶段3】module-generator.js (生成模块)
│ ├── ✅ 接收: serviceMethodSignatureIndex
│ │
│ ├── controller-generator.js
│ │ ├── ✅ 接收: serviceMethodSignatureIndex (通过setServiceMethodSignatureIndex)
│ │ └── ✅ 使用: readServiceMethodSignature() 查询索引
│ │
│ ├── service-generator.js
│ │ ├── ❌ 未接收: serviceMethodSignatureIndex
│ │ ├── ❌ 硬编码: DTO路径 '../dtos/{file}.dto'
│ │ └── ⚠️ 使用: service-method-converter.js (独立转换不知道Controller需求)
│ │
│ ├── dto-generator.js
│ │ ├── ❌ 未暴露: DTO实际生成路径信息
│ │ └── ⚠️ DTO可能在: dtos/core/member/xxx.dto
│ │
│ ├── entity-generator.js
│ ├── enum-generator.js
│ ├── listener-generator.js
│ └── job-generator.js
└── 【阶段4】generateReport()
```
---
## 2⃣ 具体问题清单
### ❌ 问题1: Service Generator未使用中央索引
**文件**: `service-generator.js`
**代码位置**: 整个类
**现象**:
```javascript
class ServiceGenerator {
constructor(outputDir = null) {
this.namingUtils = new NamingUtils();
this.methodConverter = new ServiceMethodConverter();
this.outputDir = outputDir;
// ❌ 缺少: this.serviceMethodSignatureIndex = null;
}
// ❌ 缺少方法:
// setServiceMethodSignatureIndex(index) {
// this.serviceMethodSignatureIndex = index;
// }
}
```
**影响**:
- Service生成的方法签名与Controller期望的签名可能不一致
- 导致Controller调用Service时参数数量、类型不匹配
- 产生错误: TS2554 (参数数量不匹配), TS2345 (参数类型不匹配)
**错误数量**: 约50个 TS2554/TS2345错误
---
### ❌ 问题2: DTO路径硬编码
**文件**: `service-generator.js:415`
**问题代码**:
```javascript
// 添加DTO导入
if (javaService.dtos && javaService.dtos.length > 0) {
javaService.dtos.forEach(dto => {
const dtoName = this.namingUtils.generateDtoName(cleanDto);
const dtoFileName = this.namingUtils.generateFileName(cleanDto, 'dto');
// ❌ 硬编码路径假设所有DTO都在 '../dtos/'
imports.push(`import { ${dtoName} } from '../dtos/${dtoFileName.replace('.ts', '')}';`);
});
}
```
**实际DTO位置**:
```
dtos/
├── admin/
│ └── member/
│ └── member-info.dto.ts
├── core/
│ └── member/
│ ├── dto/
│ │ └── member-info.dto.ts
│ ├── param/
│ │ └── member-search-param.dto.ts
│ └── vo/
│ └── member-list-vo.dto.ts
└── api/
└── ...
```
**影响**:
- Service导入DTO时使用错误的路径
- 产生错误: TS2307 (Cannot find module '../dtos/xxx.dto')
**错误数量**: 约4,200个 TS2307错误
**根本原因**:
- `dto-generator.js` 生成DTO时按照Java包结构组织目录
- `service-generator.js` 导入DTO时使用硬编码的相对路径
- 两者没有共享"DTO实际位置映射表"
---
### ❌ 问题3: 转换器链路不透明
**文件**: `service-method-converter.js`
**现象**:
```javascript
convertMethodBody(javaMethodBody, context = {}) {
let tsBody = javaMethodBody;
// 阶段1: 基础语法转换
tsBody = this.basicSyntax.convert(tsBody);
// 阶段2: 类型转换
tsBody = this.type.convert(tsBody);
// 阶段3: 工具类转换
tsBody = this.collection.convert(tsBody);
// ...
// 阶段6: Getter/Setter转换
tsBody = this.getterSetter.convert(tsBody);
// ⚠️ 问题:每个转换器独立工作,不知道前面做了什么
// 可能导致:
// - 变量声明被重复转换
// - 方法调用被多次修改
// - 转换结果被后续转换器覆盖
}
```
**具体案例**:
```java
// Java原始代码
Map<String, LocalAddonInfoVo> list = new HashMap<>();
```
**错误转换过程**:
```javascript
// 阶段1: basicSyntax.convert()
// Map<String, ...> list = ... → const list: Record<string, ...> = ...
"const list: Record<string, LocalAddonInfoVo> = new HashMap<>();"
// 阶段3: collection.convert()
// new HashMap<>() → {}
"const list: Record<string, LocalAddonInfoVo> = {};" // ✅ 正确
// ❌ 但如果basic-syntax转换器有Bug可能产生
"const list: Record<string, LocalAddonInfoVo> = new const installAddonList: Record<>();"
```
**影响**:
- 转换结果不可预测
- 难以调试和定位问题
- 转换器之间相互干扰
---
### ❌ 问题4: Controller与Service参数映射不智能
**文件**: `controller-generator.js:mapServiceParametersToController`
**现象**:
```javascript
// Controller生成的调用
async list(@Query() query: Record<string, any>) {
const result = await this.memberLevelServiceImplService.list(query);
return result;
}
// Service实际签名从中央索引读取
async list(pageParam: PageParam, searchParam: MemberLevelSearchParam): Promise<any[]> {
// ...
}
// ❌ 结果:参数不匹配
// Controller传1个参数 (query)
// Service期望2个参数 (pageParam, searchParam)
```
**影响**:
- 产生错误: TS2554 (Expected 2 arguments, but got 1)
**根本原因**:
- Controller Generator读取了Service方法签名
- 但没有智能拆分`query`对象到多个参数
- 缺少"参数映射规则库"
---
### ❌ 问题5: 各Generator缺少双向验证
**现象**:
```
Controller Generator:
1. 从中央索引读取Service方法签名
2. 生成Controller代码
3. ❌ 没有验证Service是否真的生成了这个方法
Service Generator:
1. 从Java代码解析方法
2. 生成Service代码
3. ❌ 没有验证Controller是否需要调用这个方法
4. ❌ 没有验证:生成的签名是否与索引一致
结果:
- Controller调用的方法Service可能没有
- Service生成的方法Controller可能不需要
- 方法签名不一致
```
**影响**:
- 产生错误: TS2339 (Property 'xxx' does not exist on type 'Service')
- 产生错误: TS2551 (Property 'xxx' does not exist. Did you mean 'yyy'?)
**错误数量**: 约100个
---
## 3⃣ 错误分布与根因映射
| 错误类型 | 错误码 | 数量 | 根本原因 | 对应问题 |
|---------|-------|------|---------|---------|
| DTO路径错误 | TS2307 | ~4,200 | DTO位置映射缺失 | 问题2 |
| VO类型未导入 | TS2304 | ~2,800 | DTO路径错误连锁反应 | 问题2 |
| Controller参数不匹配 | TS2554 | ~50 | 参数映射不智能 | 问题4 |
| Controller参数类型错误 | TS2345 | ~20 | 参数映射不智能 | 问题4 |
| Service方法不存在 | TS2339 | ~100 | 缺少双向验证 | 问题5 |
| Service方法名相似 | TS2551 | ~20 | Java Scanner遗漏 | - |
| 业务逻辑细节 | 各种 | ~7,000 | 转换器不完善 | 问题3 |
---
## 4⃣ 理想架构设计
### 🎯 核心思想:中央数据仓库 (Central Data Repository, CDR)
```
┌───────────────────────────────────────────────────────────────┐
│ Central Data Repository (CDR) │
│ ┌──────────────┬──────────────┬──────────────┬─────────────┐ │
│ │ Service签名 │ DTO位置映射 │ 依赖关系图 │ 转换上下文 │ │
│ │ Map<string, │ Map<string, │ Map<string, │ Map<string, │ │
│ │ Signature> │ string> │ string[]> │ any> │ │
│ └──────────────┴──────────────┴──────────────┴─────────────┘ │
└───────────────────────────────────────────────────────────────┘
↑ 写入 ↓ 读取
┌────┴────┐ ┌────┴────┐
│ Scanner │ │Generator│
│ 阶段 │ │ 阶段 │
└─────────┘ └─────────┘
```
### 📋 CDR数据结构
```javascript
class CentralDataRepository {
constructor() {
// 1. Service方法签名索引已有
this.serviceMethodSignatureIndex = new Map();
// 格式: { "ServiceImplName.methodName": { parameters: [...], returnType: "..." } }
// 2. DTO位置映射新增
this.dtoLocationMap = new Map();
// 格式: { "MemberInfoDto": "dtos/core/member/dto/member-info.dto" }
// 3. VO位置映射新增
this.voLocationMap = new Map();
// 格式: { "MemberListVo": "dtos/core/member/vo/member-list-vo.dto" }
// 4. Entity位置映射新增
this.entityLocationMap = new Map();
// 格式: { "Member": "entities/member.entity" }
// 5. Service依赖关系新增
this.serviceDependencyMap = new Map();
// 格式: { "MemberServiceImpl": ["ICoreAddonService", "RedisService"] }
// 6. Controller-Service调用关系新增
this.controllerServiceCallMap = new Map();
// 格式: { "MemberController.list": { service: "MemberServiceImpl", method: "list" } }
// 7. 转换上下文(新增)
this.conversionContext = new Map();
// 格式: { "MemberServiceImpl.list": { phase: "converting", converter: "basicSyntax" } }
}
// 读写方法
setServiceSignature(key, signature) { /* ... */ }
getServiceSignature(key) { /* ... */ }
setDtoLocation(dtoName, path) { /* ... */ }
getDtoLocation(dtoName) { /* ... */ }
// ...
}
```
### 🔄 改进后的Generator协调流程
```javascript
class JavaToNestJSMigrationCoordinator {
constructor() {
this.scanner = new JavaScanner();
this.cdr = new CentralDataRepository(); // ✅ 中央数据仓库
this.moduleGenerator = new ModuleGenerator();
}
async runMigration() {
// 阶段1: 扫描 + 构建CDR
await this.scanAndBuildCDR();
// 阶段2: 生成所有Generator共享CDR
await this.generateWithCDR();
// 阶段3: 验证(双向一致性检查)
await this.validateConsistency();
}
async scanAndBuildCDR() {
const scanResults = await this.scanner.scanJavaProject();
// 构建Service签名索引
scanResults.services.forEach(service => {
service.methods.forEach(method => {
const key = `${service.className}.${method.methodName}`;
this.cdr.setServiceSignature(key, {
parameters: method.parameters,
returnType: method.returnType
});
});
});
// ✅ 新增预先生成DTO并记录位置
const dtoLocationMap = await this.dtoGenerator.generateAll(scanResults.dtos);
dtoLocationMap.forEach((path, dtoName) => {
this.cdr.setDtoLocation(dtoName, path);
});
// ✅ 新增记录Service依赖关系
scanResults.services.forEach(service => {
this.cdr.setServiceDependencies(service.className, service.dependencies);
});
}
async generateWithCDR() {
// 传递CDR给所有Generator
this.controllerGenerator.setCDR(this.cdr);
this.serviceGenerator.setCDR(this.cdr);
this.dtoGenerator.setCDR(this.cdr);
// 生成代码
await this.controllerGenerator.generateAll();
await this.serviceGenerator.generateAll();
}
async validateConsistency() {
// ✅ 双向验证
const issues = [];
// 验证1: Controller调用的Service方法是否存在
this.cdr.controllerServiceCallMap.forEach((call, controllerMethod) => {
const serviceKey = `${call.service}.${call.method}`;
if (!this.cdr.getServiceSignature(serviceKey)) {
issues.push(`Controller ${controllerMethod} 调用的 ${serviceKey} 不存在`);
}
});
// 验证2: Service导入的DTO路径是否正确
// 验证3: 参数类型是否匹配
// ...
if (issues.length > 0) {
console.error('❌ 一致性验证失败:', issues);
}
}
}
```
---
## 5⃣ 修复建议
### 🔧 优先级1: 建立CDR高优先级影响4,200+错误)
**步骤**:
1. 创建 `central-data-repository.js`
2.`migration-coordinator.js` 中实例化CDR
3. 修改 `dto-generator.js`生成DTO时记录到CDR
4. 修改 `service-generator.js`导入DTO时从CDR查询路径
**预计效果**: 减少4,200个TS2307错误
---
### 🔧 优先级2: Service Generator使用中央索引中优先级影响50+错误)
**步骤**:
1.`service-generator.js` 添加 `setCDR(cdr)` 方法
2. 生成方法签名时从CDR读取而不是从Java解析
3. 确保与Controller期望的签名100%一致
**预计效果**: 减少50个TS2554/TS2345错误
---
### 🔧 优先级3: 转换器透明化(中优先级,提升质量)
**步骤**:
1. 为每个转换器添加 `getConversionLog()` 方法
2. 记录每次转换的输入、输出、规则
3.`service-method-converter.js` 中汇总日志
4. 便于调试和定位问题
**预计效果**: 提升转换质量减少未知Bug
---
### 🔧 优先级4: 双向验证机制(低优先级,长期改进)
**步骤**:
1. 在生成完成后,运行一致性验证
2. 检查Controller-Service调用关系
3. 检查DTO/VO/Entity导入路径
4. 生成验证报告
**预计效果**: 提前发现不一致问题
---
## 6⃣ 总结
### 当前问题本质
**不是业务逻辑转换问题,而是架构设计问题!**
14,086个错误中
- **30%** (约4,200个) - DTO路径错误CDR缺失
- **5%** (约700个) - Controller-Service不一致缺少协调
- **5%** (约700个) - 其他架构问题
- **60%** (约8,500个) - 真正的业务逻辑转换问题
### 修复后预期
实施优先级1+2后错误数应该降至
```
14,086 - 4,200 - 50 = 9,836个错误
```
剩余错误将主要是真正的业务逻辑细节问题,可以集中精力优化转换器。
---
**下一步行动**:
1. 实施优先级1建立CDR + DTO位置映射
2. 验证效果
3. 继续优先级2、3、4

View File

@@ -0,0 +1,388 @@
/**
* 中央数据仓库 (Central Data Repository, CDR)
*
* 职责:
* 1. 统一管理所有扫描和生成过程中的元数据
* 2. 确保各Generator之间数据一致性
* 3. 提供查询接口,避免重复扫描和计算
*
* 核心数据:
* - Service方法签名索引
* - DTO/VO/Param位置映射
* - Entity位置映射
* - Service依赖关系图
* - Controller-Service调用关系
*/
class CentralDataRepository {
constructor() {
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【核心索引】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* 1. Service方法签名索引
* 格式: Map<string, Signature>
* 键: "ServiceImplName.methodName"
* 值: { parameters: [{name, type, javaType}], returnType: string }
*
* 示例:
* "MemberServiceImpl.list" => {
* parameters: [
* {name: "pageParam", type: "PageParam", javaType: "PageParam"},
* {name: "searchParam", type: "MemberSearchParam", javaType: "MemberSearchParam"}
* ],
* returnType: "PageResult<MemberListVo>"
* }
*/
this.serviceMethodSignatureIndex = new Map();
/**
* 2. DTO位置映射
* 格式: Map<string, LocationInfo>
* 键: DTO类名如 "MemberInfoDto"
* 值: {
* relativePath: string, // 相对路径,如 "dtos/core/member/dto/member-info.dto"
* absolutePath: string, // 绝对路径
* category: string, // 类别dto/vo/param
* module: string // 所属模块
* }
*
* 示例:
* "MemberInfoDto" => {
* relativePath: "dtos/core/member/dto/member-info.dto",
* absolutePath: "/full/path/to/dtos/core/member/dto/member-info.dto.ts",
* category: "dto",
* module: "member"
* }
*/
this.dtoLocationMap = new Map();
/**
* 3. VO位置映射同DTO
*/
this.voLocationMap = new Map();
/**
* 4. Param位置映射同DTO
*/
this.paramLocationMap = new Map();
/**
* 5. Entity位置映射
* 格式: Map<string, string>
* 键: Entity类名如 "Member"
* 值: 相对路径(如 "entities/member.entity"
*/
this.entityLocationMap = new Map();
/**
* 6. Service依赖关系图
* 格式: Map<string, string[]>
* 键: Service类名如 "MemberServiceImpl"
* 值: 依赖的Service列表
*/
this.serviceDependencyMap = new Map();
/**
* 7. Controller-Service调用关系
* 格式: Map<string, CallInfo>
* 键: "ControllerName.methodName"
* 值: { service: string, method: string, parameters: [...] }
*/
this.controllerServiceCallMap = new Map();
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【辅助索引】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* 8. 所有DTO/VO/Param名称反向索引
* 快速查找:给定类名,查找其位置
*/
this.typeLocationIndex = new Map();
/**
* 9. 模块目录映射
* 格式: Map<string, string>
* 键: 模块名(如 "member"
* 值: 模块根目录(如 "core/member"
*/
this.moduleDirectoryMap = new Map();
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【统计信息】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
this.stats = {
serviceMethods: 0,
dtos: 0,
vos: 0,
params: 0,
entities: 0,
services: 0,
controllers: 0
};
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Service方法签名】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* 设置Service方法签名
*/
setServiceSignature(serviceClassName, methodName, signature) {
const key = `${serviceClassName}.${methodName}`;
this.serviceMethodSignatureIndex.set(key, signature);
this.stats.serviceMethods++;
}
/**
* 获取Service方法签名
*/
getServiceSignature(serviceClassName, methodName) {
const key = `${serviceClassName}.${methodName}`;
return this.serviceMethodSignatureIndex.get(key);
}
/**
* 批量设置Service签名从Scanner结果
*/
batchSetServiceSignatures(services) {
services.forEach(service => {
const className = service.className;
if (service.methods && service.methods.length > 0) {
service.methods.forEach(method => {
this.setServiceSignature(className, method.methodName, {
parameters: method.parameters || [],
returnType: method.returnType || 'void',
javaReturnType: method.javaReturnType || method.returnType
});
});
}
});
console.log(`✅ CDR: 已索引 ${this.serviceMethodSignatureIndex.size} 个Service方法签名`);
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【DTO/VO/Param位置映射】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* 设置类型位置(通用方法)
*/
setTypeLocation(typeName, locationInfo) {
const { category } = locationInfo;
// 根据类别存储到不同的Map
if (category === 'dto') {
this.dtoLocationMap.set(typeName, locationInfo);
this.stats.dtos++;
} else if (category === 'vo') {
this.voLocationMap.set(typeName, locationInfo);
this.stats.vos++;
} else if (category === 'param') {
this.paramLocationMap.set(typeName, locationInfo);
this.stats.params++;
}
// 同时存入统一索引
this.typeLocationIndex.set(typeName, locationInfo);
}
/**
* 获取类型位置智能查找DTO/VO/Param
*/
getTypeLocation(typeName) {
// 优先从统一索引查找
if (this.typeLocationIndex.has(typeName)) {
return this.typeLocationIndex.get(typeName);
}
// 兜底:分别查找
return this.dtoLocationMap.get(typeName)
|| this.voLocationMap.get(typeName)
|| this.paramLocationMap.get(typeName);
}
/**
* 批量设置DTO/VO位置从DTO Generator
*/
batchSetTypeLocations(typeLocations) {
typeLocations.forEach(location => {
this.setTypeLocation(location.typeName, {
relativePath: location.relativePath,
absolutePath: location.absolutePath,
category: location.category,
module: location.module
});
});
console.log(`✅ CDR: 已索引 ${this.typeLocationIndex.size} 个类型位置 (DTO: ${this.stats.dtos}, VO: ${this.stats.vos}, Param: ${this.stats.params})`);
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Entity位置映射】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
setEntityLocation(entityName, relativePath) {
this.entityLocationMap.set(entityName, relativePath);
this.stats.entities++;
}
getEntityLocation(entityName) {
return this.entityLocationMap.get(entityName);
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Service依赖关系】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
setServiceDependencies(serviceName, dependencies) {
this.serviceDependencyMap.set(serviceName, dependencies);
this.stats.services++;
}
getServiceDependencies(serviceName) {
return this.serviceDependencyMap.get(serviceName) || [];
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Controller-Service调用关系】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
setControllerServiceCall(controllerName, methodName, callInfo) {
const key = `${controllerName}.${methodName}`;
this.controllerServiceCallMap.set(key, callInfo);
}
getControllerServiceCall(controllerName, methodName) {
const key = `${controllerName}.${methodName}`;
return this.controllerServiceCallMap.get(key);
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【模块目录映射】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
setModuleDirectory(moduleName, directory) {
this.moduleDirectoryMap.set(moduleName, directory);
}
getModuleDirectory(moduleName) {
return this.moduleDirectoryMap.get(moduleName);
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【辅助方法】
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/**
* 根据类型名推断导入路径(智能推断)
* @param {string} typeName - 类型名,如 "MemberInfoDto", "MemberListVo"
* @param {string} fromPath - 当前文件路径,如 "services/admin/member/impl"
* @returns {string|null} - 相对导入路径,如 "../../../../dtos/core/member/dto/member-info.dto"
*/
inferImportPath(typeName, fromPath) {
const location = this.getTypeLocation(typeName);
if (!location) {
return null;
}
// 计算相对路径
const from = fromPath.split('/').filter(p => p);
const to = location.relativePath.split('/').filter(p => p);
// 找到公共前缀
let commonLength = 0;
for (let i = 0; i < Math.min(from.length, to.length); i++) {
if (from[i] === to[i]) {
commonLength++;
} else {
break;
}
}
// 构建相对路径
const upLevels = from.length - commonLength;
const downPath = to.slice(commonLength);
const relativePath = '../'.repeat(upLevels) + downPath.join('/');
// 移除 .ts 后缀
return relativePath.replace('.ts', '');
}
/**
* 批量推断导入路径
* @param {string[]} typeNames - 类型名列表
* @param {string} fromPath - 当前文件路径
* @returns {Map<string, string>} - 类型名到导入路径的映射
*/
batchInferImportPaths(typeNames, fromPath) {
const importMap = new Map();
typeNames.forEach(typeName => {
const importPath = this.inferImportPath(typeName, fromPath);
if (importPath) {
importMap.set(typeName, importPath);
}
});
return importMap;
}
/**
* 获取统计信息
*/
getStats() {
return {
...this.stats,
totalTypes: this.typeLocationIndex.size,
totalServiceMethods: this.serviceMethodSignatureIndex.size
};
}
/**
* 打印统计信息
*/
printStats() {
const stats = this.getStats();
console.log('\n📊 中央数据仓库 (CDR) 统计信息:');
console.log(` Service方法: ${stats.totalServiceMethods}`);
console.log(` DTO: ${stats.dtos}`);
console.log(` VO: ${stats.vos}`);
console.log(` Param: ${stats.params}`);
console.log(` Entity: ${stats.entities}`);
console.log(` 总类型: ${stats.totalTypes}`);
}
/**
* 验证数据完整性
*/
validate() {
const issues = [];
// 验证1: Controller调用的Service方法是否都存在
this.controllerServiceCallMap.forEach((call, controllerMethod) => {
const serviceKey = `${call.service}.${call.method}`;
if (!this.serviceMethodSignatureIndex.has(serviceKey)) {
issues.push(`❌ Controller ${controllerMethod} 调用的 ${serviceKey} 在索引中不存在`);
}
});
// 验证2: Service方法使用的DTO/VO是否都有位置映射
// (这个需要在使用时检查,这里先跳过)
if (issues.length > 0) {
console.warn('\n⚠ CDR数据完整性验证发现问题:');
issues.forEach(issue => console.warn(` ${issue}`));
return false;
}
console.log('✅ CDR数据完整性验证通过');
return true;
}
}
module.exports = CentralDataRepository;

View File

@@ -7,7 +7,10 @@ const NamingUtils = require('../utils/naming-utils');
* 将Java控制器转换为NestJS控制器
*/
class ControllerGenerator {
setCDR(cdr) { this.cdr = cdr; }
constructor(outputDir) {
this.cdr = null;
this.namingUtils = new NamingUtils();
this.outputDir = outputDir || '';
// ✅ 中央Service方法签名索引

View File

@@ -7,7 +7,10 @@ const NamingUtils = require('../utils/naming-utils');
* 将Java DTO转换为NestJS DTO
*/
class DtoGenerator {
setCDR(cdr) { this.cdr = cdr; }
constructor() {
this.cdr = null;
this.namingUtils = new NamingUtils();
}
@@ -33,7 +36,16 @@ class DtoGenerator {
fs.writeFileSync(filePath, content);
console.log(`✅ 生成DTO: ${subPath}/${fileName}`);
return { fileName, content, subPath };
// ✅ V2: 返回完整信息供CDR记录
return {
fileName,
content,
subPath,
dtoName,
relativePath: subPath ? `dtos/${subPath}/${fileName}` : `dtos/${fileName}`,
absolutePath: filePath
};
}
/**

View File

@@ -7,7 +7,10 @@ const NamingUtils = require('../utils/naming-utils');
* 将Java实体转换为NestJS实体
*/
class EntityGenerator {
setCDR(cdr) { this.cdr = cdr; }
constructor() {
this.cdr = null;
this.namingUtils = new NamingUtils();
}

View File

@@ -7,7 +7,10 @@ const NamingUtils = require('../utils/naming-utils');
* 将Java枚举转换为NestJS枚举
*/
class EnumGenerator {
setCDR(cdr) { this.cdr = cdr; }
constructor() {
this.cdr = null;
this.namingUtils = new NamingUtils();
}

View File

@@ -26,7 +26,9 @@ class ModuleGenerator {
this.listenerGenerator = new ListenerGenerator();
this.jobGenerator = new JobGenerator();
this.outputDir = '';
// ✅ 中央Service方法签名索引
// ✅ V2: 中央数据仓库
this.cdr = null;
// ⚠️ 向后兼容:保留旧的索引引用
this.serviceMethodSignatureIndex = null;
}
@@ -41,7 +43,20 @@ class ModuleGenerator {
}
/**
* ✅ 设置中央Service方法签名索引
* ✅ V2: 设置中央数据仓库
*/
setCDR(cdr) {
this.cdr = cdr;
// ✅ V2: 传递CDR给所有Generator
this.controllerGenerator.setCDR(cdr);
this.serviceGenerator.setCDR(cdr);
this.dtoGenerator.setCDR(cdr);
this.entityGenerator.setCDR(cdr);
this.enumGenerator.setCDR(cdr);
}
/**
* ✅ 设置中央Service方法签名索引向后兼容
*/
setServiceMethodSignatureIndex(index) {
this.serviceMethodSignatureIndex = index;
@@ -447,8 +462,22 @@ export class ControllerModule {
dtoComponents.forEach(dtoComponent => {
try {
this.dtoGenerator.generateDto(dtoComponent.javaClass, dtoDir);
const result = this.dtoGenerator.generateDto(dtoComponent.javaClass, dtoDir);
dtoCount++;
// ✅ V2: 记录DTO位置到CDR
if (this.cdr && result) {
const dtoName = result.dtoName || this.namingUtils.generateDtoName(dtoComponent.javaClass.className);
const relativePath = result.relativePath || `dtos/${result.fileName}`;
const category = this.inferDtoCategory(dtoComponent.javaClass.className);
this.cdr.setTypeLocation(dtoName, {
relativePath,
absolutePath: result.absolutePath || path.join(dtoDir, result.fileName),
category,
module: dtoComponent.javaClass.module || 'common'
});
}
} catch (error) {
console.warn(`⚠️ 生成 DTO 失败 [${dtoComponent.name}]: ${error.message}`);
}
@@ -457,6 +486,18 @@ export class ControllerModule {
console.log(`✅ 生成 DTO 文件: ${dtoCount}`);
}
/**
* ✅ V2: 推断DTO类别dto/vo/param
*/
inferDtoCategory(className) {
const lowerName = className.toLowerCase();
if (lowerName.endsWith('vo')) return 'vo';
if (lowerName.endsWith('param')) return 'param';
if (lowerName.endsWith('dto')) return 'dto';
// 默认为dto
return 'dto';
}
/**
* 生成枚举文件
*/

View File

@@ -8,7 +8,10 @@ const ServiceMethodConverter = require('../converters/service-method-converter')
* 将Java服务转换为NestJS服务
*/
class ServiceGenerator {
setCDR(cdr) { this.cdr = cdr; }
constructor(outputDir = null) {
this.cdr = null;
this.namingUtils = new NamingUtils();
this.methodConverter = new ServiceMethodConverter();
this.outputDir = outputDir;
@@ -399,7 +402,7 @@ ${methods}
});
}
// 添加DTO导入
// ✅ V2: 添加DTO导入使用CDR查询路径
if (javaService.dtos && javaService.dtos.length > 0) {
javaService.dtos.forEach(dto => {
// 清理泛型语法List<String> → List, Record<String, Vo> → Record
@@ -411,8 +414,22 @@ ${methods}
}
const dtoName = this.namingUtils.generateDtoName(cleanDto);
const dtoFileName = this.namingUtils.generateFileName(cleanDto, 'dto');
imports.push(`import { ${dtoName} } from '../dtos/${dtoFileName.replace('.ts', '')}';`);
// ✅ V2: 使用CDR查询DTO路径如果可用
let importPath = null;
if (this.cdr) {
// 从当前Service文件路径推断相对路径
const currentPath = javaService.filePath || 'services/admin';
importPath = this.cdr.inferImportPath(dtoName, currentPath);
}
// ⚠️ 兜底如果CDR未找到使用旧的硬编码路径
if (!importPath) {
const dtoFileName = this.namingUtils.generateFileName(cleanDto, 'dto');
importPath = `../dtos/${dtoFileName.replace('.ts', '')}`;
}
imports.push(`import { ${dtoName} } from '${importPath}';`);
});
}

View File

@@ -5,10 +5,13 @@ const path = require('path');
const JavaScanner = require('./scanners/java-scanner');
const LayerMapper = require('./mappers/layer-mapper');
const ModuleGenerator = require('./generators/module-generator');
const CentralDataRepository = require('./central-data-repository');
/**
* Java到NestJS迁移协调器
* 按技术层级组织模块严格遵循NestJS官方规范
*
* ✅ V2: 使用中央数据仓库(CDR)统一管理元数据
*/
class JavaToNestJSMigrationCoordinator {
constructor() {
@@ -18,9 +21,11 @@ class JavaToNestJSMigrationCoordinator {
this.mapper = new LayerMapper();
this.moduleGenerator = new ModuleGenerator();
// ✅ 中央数据仓库Service方法签名索引
// 格式:{ 'ServiceImplName.methodName': { parameters: [...], returnType: '...' } }
this.serviceMethodSignatureIndex = new Map();
// ✅ V2: 中央数据仓库(替代原来的单一索引
this.cdr = new CentralDataRepository();
// ⚠️ 向后兼容保留旧的索引引用指向CDR
this.serviceMethodSignatureIndex = this.cdr.serviceMethodSignatureIndex;
this.stats = {
startTime: null,
@@ -59,6 +64,22 @@ class JavaToNestJSMigrationCoordinator {
console.log('\n✅ 迁移流程完成!');
this.printStats();
// ✅ V2: 打印CDR统计
console.log('');
this.cdr.printStats();
// ✅ V2: 测试查询几个DTO
console.log('\n🔍 测试CDR查询:');
const testTypes = ['MemberInfoDto', 'MemberListVo', 'PageParam', 'MemberSearchParam'];
testTypes.forEach(typeName => {
const location = this.cdr.getTypeLocation(typeName);
if (location) {
console.log(`${typeName}: ${location.relativePath}`);
} else {
console.log(`${typeName}: 未找到`);
}
});
} catch (error) {
console.error('❌ 迁移过程中发生错误:', error.message);
this.stats.errors.push(error.message);
@@ -93,7 +114,7 @@ class JavaToNestJSMigrationCoordinator {
}
/**
* ✅ 构建Service方法签名索引
* ✅ V2: 构建Service方法签名索引使用CDR
* 从Java扫描结果中提取所有Service方法的参数和返回类型
*/
buildServiceMethodSignatureIndex(services) {
@@ -104,15 +125,23 @@ class JavaToNestJSMigrationCoordinator {
// 提取所有方法
const methods = this.scanner.extractMethods(content);
// ✅ V2: 使用CDR存储方法签名
methods.forEach(method => {
const key = `${className}.${method.methodName}`;
this.serviceMethodSignatureIndex.set(key, {
this.cdr.setServiceSignature(className, method.methodName, {
parameters: method.parameters || [],
returnType: method.returnType || 'void',
methodBody: method.methodBody || ''
});
});
// ✅ V2: 记录Service依赖关系
if (service.dependencies && service.dependencies.length > 0) {
this.cdr.setServiceDependencies(className, service.dependencies);
}
});
// ✅ V2: 打印CDR统计
console.log(`📋 CDR: 已索引 ${this.cdr.serviceMethodSignatureIndex.size} 个Service方法签名`);
}
/**
@@ -283,12 +312,16 @@ class JavaToNestJSMigrationCoordinator {
}
/**
* 生成NestJS模块
* ✅ V2: 生成NestJS模块传递CDR
*/
async generateModules(nestJSModules) {
// ✅ 传递中央索引给ModuleGenerator
// ✅ V2: 传递CDR给ModuleGenerator
this.moduleGenerator.setOutputDir(this.nestJSPath);
this.moduleGenerator.setCDR(this.cdr); // ✅ 传递整个CDR
// ⚠️ 向后兼容也传递旧的索引指向CDR内部
this.moduleGenerator.setServiceMethodSignatureIndex(this.serviceMethodSignatureIndex);
await this.moduleGenerator.generateAllModules(nestJSModules);
this.stats.modulesGenerated = Object.keys(nestJSModules).length;