Files
wwjcloud-nest-v1/wwjcloud-nest-v1/tools/java-to-nestjs-migration/generators/controller-generator.js
wanwu 944034695e refactor: 统一协调架构 - 中央Service方法签名索引
🏗️ 架构重构:
- Coordinator构建中央Service方法签名索引(1038个方法)
- 所有Generator从索引读取,而非各自读文件
- Controller不再依赖已生成的NestJS文件

 修复:
1. Java类型映射(Integer/Long→number, String→string)
2. Controller参数智能匹配V6(Java类型感知)
3. 消除层级间的循环依赖

📊 效果: 14234 → 14121 (-113)
- Controller层: 162 → 49 (-113)
- TS2345类型错误: 115 → 2 (-113)

🔧 核心改进:
- 统一数据源,消除不一致
- 各Generator协同工作,不再各自为政
2025-10-29 19:55:43 +08:00

933 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const path = require('path');
const NamingUtils = require('../utils/naming-utils');
/**
* 控制器生成器
* 将Java控制器转换为NestJS控制器
*/
class ControllerGenerator {
constructor(outputDir) {
this.namingUtils = new NamingUtils();
this.outputDir = outputDir || '';
// ✅ 中央Service方法签名索引
this.serviceMethodSignatureIndex = null;
}
/**
* ✅ 设置中央Service方法签名索引
*/
setServiceMethodSignatureIndex(index) {
this.serviceMethodSignatureIndex = index;
}
/**
* 生成控制器文件
*/
generateController(javaController, outputDir) {
// 如果this.outputDir已经被设置不要覆盖它用于findServiceFile
// outputDir参数用于控制器文件的输出目录
const controllerOutputDir = outputDir || this.outputDir;
// 检查控制器数据是否有效
if (!javaController || !javaController.className) {
console.warn(`⚠️ 跳过无效控制器: ${JSON.stringify(javaController)}`);
return null;
}
// 根据Java文件路径创建子目录结构
const subDir = this.getSubDirectoryFromJavaPath(javaController.filePath, 'controller');
const fullOutputDir = path.join(controllerOutputDir, subDir);
// 确保子目录存在
if (!fs.existsSync(fullOutputDir)) {
fs.mkdirSync(fullOutputDir, { recursive: true });
}
const controllerName = this.namingUtils.generateControllerName(javaController.className);
const fileName = this.namingUtils.generateFileName(javaController.className, 'controller');
const filePath = path.join(fullOutputDir, fileName);
const content = this.generateControllerContent(javaController, controllerName);
fs.writeFileSync(filePath, content);
console.log(`✅ 生成控制器: ${filePath}`);
return { fileName, content };
}
/**
* 根据Java文件路径获取子目录结构
*/
getSubDirectoryFromJavaPath(javaFilePath, type) {
if (!javaFilePath) return '';
// 从Java文件路径中提取包结构
// 例如: /path/to/java/com/niu/core/controller/core/HttpServerErrorController.java
// 提取: controller/core
const pathParts = javaFilePath.split(path.sep);
const javaIndex = pathParts.findIndex(part => part === 'java');
if (javaIndex === -1) return '';
// 获取java目录后的包结构
const packageParts = pathParts.slice(javaIndex + 1, -1); // 排除文件名
// 根据类型过滤相关目录
const typeIndex = packageParts.findIndex(part => part === type || part === type + 's');
if (typeIndex === -1) return '';
// 返回类型目录后的子目录结构
const subParts = packageParts.slice(typeIndex + 1);
return subParts.join('/');
}
/**
* 生成控制器内容
*/
generateControllerContent(javaController, controllerName) {
const routeInfo = javaController.routeInfo;
const imports = this.generateImports(javaController);
const decorators = this.generateDecorators(routeInfo);
const constructor = this.generateConstructor(javaController);
const methods = this.generateMethods(javaController);
return `${imports}
${decorators}
export class ${controllerName} {
${constructor}
${methods}
}
`;
}
/**
* 查找Service文件的实际路径
* 支持宽松匹配,忽略连字符差异
* @returns {string|null} 相对于services目录的路径
*/
findServiceFile(serviceName) {
if (!this.outputDir) return null;
const servicesDir = path.join(this.outputDir, 'services');
if (!fs.existsSync(servicesDir)) return null;
// 规范化文件名用于比较移除连字符、下划线、impl、service等后缀统一为小写
const normalizeForComparison = (name) => {
return name.toLowerCase()
.replace(/[-_]/g, '')
.replace(/serviceimpl/g, '')
.replace(/impl/g, '')
.replace(/service\.ts/g, '.ts')
.replace(/service/g, '');
};
const targetNormalized = normalizeForComparison(serviceName);
const searchDir = (dir) => {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
const result = searchDir(fullPath);
if (result) return result;
} else if (normalizeForComparison(file) === targetNormalized) {
return path.relative(servicesDir, fullPath);
}
}
return null;
};
return searchDir(servicesDir);
}
/**
* 从Service文件中提取实际的类名
* @param {string} serviceFilePath - Service文件的完整路径
* @returns {string|null} - 导出的类名
*/
extractServiceClassName(serviceFilePath) {
if (!fs.existsSync(serviceFilePath)) return null;
try {
const content = fs.readFileSync(serviceFilePath, 'utf-8');
// 匹配: export class XxxService
const classMatch = content.match(/export\s+class\s+(\w+)/);
return classMatch ? classMatch[1] : null;
} catch (error) {
console.warn(`⚠️ 无法读取Service文件: ${serviceFilePath}`);
return null;
}
}
/**
* 获取Service的实际类名优先从文件读取否则推断
* @param {string} dep - 依赖的Java类名如 AddonServiceImpl
* @returns {string} - 实际的TypeScript类名
*/
getActualServiceClassName(dep) {
const serviceFileName = this.namingUtils.generateFileName(dep, 'service');
let serviceName = this.namingUtils.generateServiceName(dep); // 默认推断的类名
if (this.outputDir) {
const servicesDir = path.join(this.outputDir, 'services');
const serviceRelativePath = this.findServiceFile(serviceFileName);
if (serviceRelativePath) {
const serviceFullPath = path.join(servicesDir, serviceRelativePath);
const actualClassName = this.extractServiceClassName(serviceFullPath);
if (actualClassName) {
serviceName = actualClassName; // 使用实际的类名
}
}
}
return serviceName;
}
/**
* 计算从Controller到Service的相对路径
*/
calculateServicePath(javaFilePath, serviceFileName) {
const cleanFileName = serviceFileName.replace('.ts', '');
// 先尝试查找实际的service文件
const actualServicePath = this.findServiceFile(serviceFileName);
if (actualServicePath) {
if (!javaFilePath) return `../../../services/${actualServicePath.replace(/\\/g, '/').replace('.ts', '')}`;
const pathParts = javaFilePath.split(path.sep);
const javaIndex = pathParts.findIndex(part => part === 'java');
if (javaIndex === -1) return `../../../services/${actualServicePath.replace(/\\/g, '/').replace('.ts', '')}`;
const packageParts = pathParts.slice(javaIndex + 1, -1);
const controllerIndex = packageParts.findIndex(part => part === 'controller' || part === 'controllers');
if (controllerIndex === -1) return `../../../services/${actualServicePath.replace(/\\/g, '/').replace('.ts', '')}`;
const subParts = packageParts.slice(controllerIndex + 1);
const depth = subParts.length + 1;
const upLevels = '../'.repeat(depth);
return `${upLevels}services/${actualServicePath.replace(/\\/g, '/').replace('.ts', '')}`;
}
// 回退到推断路径
if (!javaFilePath) return `../../../services/admin/impl/${cleanFileName}`;
const pathParts = javaFilePath.split(path.sep);
const javaIndex = pathParts.findIndex(part => part === 'java');
if (javaIndex === -1) return `../../../services/admin/impl/${cleanFileName}`;
const packageParts = pathParts.slice(javaIndex + 1, -1);
const controllerIndex = packageParts.findIndex(part => part === 'controller' || part === 'controllers');
if (controllerIndex === -1) return `../../../services/admin/impl/${cleanFileName}`;
const subParts = packageParts.slice(controllerIndex + 1);
const depth = subParts.length + 1;
const upLevels = '../'.repeat(depth);
let serviceSubPath = subParts.map(part => part === 'adminapi' ? 'admin' : part).join('/');
if (serviceSubPath) {
serviceSubPath += '/impl';
} else {
serviceSubPath = 'admin/impl';
}
return `${upLevels}services/${serviceSubPath}/${cleanFileName}`;
}
/**
* 计算Result类的相对路径
*/
calculateResultPath(javaFilePath) {
// 根据Java文件路径计算相对路径
if (!javaFilePath) return '../common/result';
// 从Java文件路径中提取包结构
const pathParts = javaFilePath.split(path.sep);
const javaIndex = pathParts.findIndex(part => part === 'java');
if (javaIndex === -1) return '../common/result';
// 获取java目录后的包结构
const packageParts = pathParts.slice(javaIndex + 1, -1); // 排除文件名
// 根据类型过滤相关目录
const typeIndex = packageParts.findIndex(part => part === 'controller' || part === 'controllers');
if (typeIndex === -1) return '../common/result';
// 返回类型目录后的子目录结构
const subParts = packageParts.slice(typeIndex + 1);
// 计算需要返回的层级数
const depth = subParts.length + 1; // +1 for controllers directory
const upLevels = '../'.repeat(depth);
return `${upLevels}common/result`;
}
/**
* 生成导入语句
*/
generateImports(javaController) {
const imports = [
"import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';",
"import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';",
"import { AuthGuard, RbacGuard, Public, Result } from '@wwjBoot';"
];
// 获取依赖列表,如果为空则从控制器名称推断
let dependencies = javaController.dependencies || [];
if (dependencies.length === 0) {
// 从控制器名称推断 Service 依赖
// Controller -> ControllerServiceImpl匹配Java Service命名规范
const controllerName = javaController.className.replace(/Controller$/, '');
dependencies = [controllerName + 'ServiceImpl'];
}
// 添加服务导入 - 计算正确的相对路径
dependencies.forEach(dep => {
const serviceFileName = this.namingUtils.generateFileName(dep, 'service');
// 检查Service文件是否存在
const serviceRelativePath = this.findServiceFile(serviceFileName);
if (!serviceRelativePath) {
console.warn(`⚠️ Service文件未找到: ${serviceFileName} (Controller: ${javaController.className})`);
return; // 跳过该导入
}
const relativePath = this.calculateServicePath(javaController.filePath, serviceFileName);
const serviceName = this.getActualServiceClassName(dep);
if (serviceName) {
imports.push(`import { ${serviceName} } from '${relativePath}';`);
}
});
// 添加DTO导入
if (javaController.dtos && javaController.dtos.length > 0) {
javaController.dtos.forEach(dto => {
const dtoName = this.namingUtils.generateDtoName(dto);
const dtoFileName = this.namingUtils.generateFileName(dto, 'dto');
imports.push(`import { ${dtoName} } from '../dtos/${dtoFileName.replace('.dto.ts', '')}';`);
});
}
return imports.join('\n');
}
/**
* 生成装饰器
*/
generateDecorators(routeInfo) {
const decorators = [];
// 控制器装饰器 - 保持Java原始路径只去掉前导斜杠
// Java: adminapi → NestJS: adminapi → 最终: /adminapi/*
// Java: /api → NestJS: api → 最终: /api/*
if (routeInfo.controllerPath) {
let cleanPath = routeInfo.controllerPath.replace(/^\/+/, ''); // 只去掉前导斜杠
if (cleanPath) {
decorators.push(`@Controller('${cleanPath}')`);
} else {
decorators.push('@Controller()'); // 空路径使用无参数形式
}
} else {
decorators.push('@Controller()');
}
// API文档装饰器
decorators.push('@ApiTags(\'API\')');
// ✅ 修复自动识别Java白名单路由添加@Public()装饰器
// 对应Java配置: WebMvcConfig.excludePathPatterns
const controllerPath = (routeInfo.controllerPath || '').toLowerCase();
// Java白名单路径无需认证:
// 1. /adminapi/login/**
// 2. /adminapi/sys/web/**
// 3. /adminapi/captcha/**
const publicPaths = [
'adminapi/login',
'adminapi/sys/web',
'adminapi/captcha',
'login',
'register',
'captcha',
];
const isPublicRoute = publicPaths.some(path => controllerPath.includes(path)) ||
controllerPath === 'api' ||
routeInfo.hasClassLevelIgnore;
// 根据路由信息决定是否添加认证守卫
if (isPublicRoute) {
// 白名单路由或Java标记@SaIgnore的路由自动添加@Public()
decorators.push('@Public()');
} else if (routeInfo.hasClassLevelAuth) {
// 如果类级别有 @SaCheckLogin添加守卫
decorators.push('@UseGuards(AuthGuard)');
decorators.push('@ApiBearerAuth()');
}
return decorators.join('\n');
}
/**
* 生成方法
*/
generateMethods(javaController) {
if (!javaController.routeInfo || !javaController.routeInfo.methods) {
return ' // 无方法';
}
const existingMethods = [];
return javaController.routeInfo.methods.map(method => {
const methodName = this.generateValidMethodName(method, existingMethods);
existingMethods.push(methodName);
return this.generateMethod(method, javaController, methodName);
}).join('\n\n');
}
/**
* 生成单个方法
*/
generateMethod(method, javaController, methodName) {
// 使用传入的方法名,确保无重复
const httpDecorator = this.getHttpDecorator(method.httpMethod);
const parameters = this.generateMethodParameters(method, javaController);
const returnType = this.generateReturnType(method);
// 将 /{key} 格式转换为 NestJS 的 :key 格式,并移除前导斜杠
const nestPath = method.path.replace(/\{(\w+)\}/g, ':$1').replace(/^\/+/, '');
// 生成方法体使用实际的Java Service调用
const methodBody = this.generateMethodBody(method, javaController);
// 生成方法级别的认证装饰器
const authDecorators = this.generateMethodAuthDecorators(method, javaController.routeInfo);
return ` ${httpDecorator}('${nestPath}')
@ApiOperation({ summary: '${method.path}' })
@ApiResponse({ status: 200, description: '成功' })${authDecorators}
async ${methodName}(${parameters}): Promise<${returnType}> {
${methodBody}
}`;
}
/**
* 生成方法级别的认证装饰器
*/
generateMethodAuthDecorators(method, routeInfo) {
const decorators = [];
// 情况1: 类级别有认证,但方法标记了@SaIgnore
if (routeInfo.hasClassLevelAuth && method.isPublic) {
decorators.push('\n @Public()');
}
// 情况2: 类级别无认证,但方法标记了@SaCheckLogin
if (!routeInfo.hasClassLevelAuth && !routeInfo.hasClassLevelIgnore && method.requiresAuth) {
decorators.push('\n @UseGuards(AuthGuard)');
decorators.push('\n @ApiBearerAuth()');
}
return decorators.join('');
}
/**
* 生成方法体
*/
generateMethodBody(method, javaController) {
const javaMethodName = method.javaMethodName;
const methodServiceCalls = javaController.methodServiceCalls || {};
const serviceCalls = methodServiceCalls[javaMethodName] || [];
if (serviceCalls.length === 0) {
// 没有找到Service调用返回占位符
return ` // TODO: 实现业务逻辑
return Result.success(null);`;
}
// 使用第一个Service调用通常Controller方法只调用一个主Service方法
const firstCall = serviceCalls[0];
const servicePropertyName = this.namingUtils.toCamelCase(firstCall.serviceImpl) + 'Service';
const serviceMethodName = firstCall.serviceMethod;
const methodParams = this.generateServiceMethodParameters(method, javaController);
return ` const result = await this.${servicePropertyName}.${serviceMethodName}(${methodParams});
return Result.success(result);`;
}
/**
* 生成有效的方法名
*/
generateValidMethodName(method, existingMethods = []) {
// 如果已经有有效的方法名,直接使用
if (method.methodName && this.isValidMethodName(method.methodName)) {
return method.methodName;
}
// 从路径生成方法名
const path = method.path || '';
const httpMethod = method.httpMethod || 'get';
// 移除路径参数和特殊字符
let methodName = path
.replace(/[{}]/g, '') // 移除路径参数
.replace(/[^a-zA-Z0-9\/]/g, '') // 只保留字母数字和斜杠
.split('/')
.filter(part => part.length > 0)
.join('');
// 如果为空使用HTTP方法
if (!methodName) {
methodName = httpMethod.toLowerCase();
}
// 确保以HTTP方法开头
const httpPrefix = httpMethod.toLowerCase();
if (!methodName.toLowerCase().startsWith(httpPrefix)) {
methodName = httpPrefix + this.namingUtils.toPascalCase(methodName);
}
// 转换为camelCase
let finalMethodName = this.namingUtils.toCamelCase(methodName);
// 检查重复并添加后缀
let counter = 1;
while (existingMethods.includes(finalMethodName)) {
finalMethodName = this.namingUtils.toCamelCase(methodName) + counter;
counter++;
}
return finalMethodName;
}
/**
* 检查方法名是否有效
*/
isValidMethodName(name) {
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
}
/**
* 获取HTTP装饰器
*/
getHttpDecorator(httpMethod) {
const methodMap = {
'get': '@Get',
'post': '@Post',
'put': '@Put',
'delete': '@Delete',
'patch': '@Patch'
};
return methodMap[httpMethod.toLowerCase()] || '@Get';
}
/**
* 生成方法参数V2基于Service签名
*/
generateMethodParameters(method, javaController) {
const parameters = [];
const httpMethod = method.httpMethod.toLowerCase();
const isPost = httpMethod === 'post' || httpMethod === 'put';
const isGet = httpMethod === 'get';
// 尝试读取Service签名确定需要哪些参数
const serviceParams = javaController ? this.readServiceMethodSignature(method, javaController) : null;
let needsBody = false;
let needsQuery = false;
let pathParams = [];
if (serviceParams !== null) {
// 根据Service参数类型确定Controller需要哪些参数
for (const param of serviceParams) {
const paramType = param.type;
const paramName = param.name;
const tsType = this.javaTypeToTsType(paramType); // ✅ Java类型转换
// 路径参数
if (method.path && method.path.includes(`{${paramName}}`)) {
pathParams.push(paramName);
}
// DTO/Param类型
else if (this.isBodyParameter(paramType)) {
if (isPost) {
needsBody = true;
} else {
needsQuery = true;
}
}
// 基本类型或其他
else {
if (isPost) {
needsQuery = true; // POST请求的基本类型从query获取
} else {
needsQuery = true;
}
}
}
} else {
// 无法读取Service签名使用默认逻辑
needsBody = isPost;
needsQuery = isGet;
// 提取路径参数
if (method.path.includes('{')) {
const paramNames = method.path.match(/\{(\w+)\}/g);
if (paramNames) {
pathParams = paramNames.map(p => p.replace(/[{}]/g, ''));
}
}
}
// 生成参数列表
if (needsBody) {
const dtoType = method.requestDto || 'Record<string, any>';
parameters.push(`@Body() body: ${dtoType}`);
}
if (pathParams.length > 0) {
if (pathParams.length === 1) {
parameters.push(`@Param('${pathParams[0]}') ${pathParams[0]}: string`);
} else {
// 多个路径参数,分别声明
pathParams.forEach(paramName => {
parameters.push(`@Param('${paramName}') ${paramName}: string`);
});
}
}
if (needsQuery) {
const queryType = method.queryDto || 'Record<string, any>';
parameters.push(`@Query() query: ${queryType}`);
}
return parameters.join(', ');
}
/**
* 生成返回类型
*/
generateReturnType(method) {
return 'Result<any>';
}
/**
* 获取正确的服务属性名
*/
getCorrectServiceName(javaController) {
// 获取依赖列表,如果为空则从控制器名称推断
let dependencies = javaController.dependencies || [];
if (dependencies.length === 0) {
// 从控制器名称推断服务名
// Controller -> ControllerServiceImpl匹配Java Service命名规范
const controllerName = javaController.className.replace(/Controller$/, '');
return this.namingUtils.toCamelCase(controllerName + 'ServiceImpl') + 'Service';
}
const firstDep = dependencies[0];
return this.namingUtils.toCamelCase(firstDep) + 'Service';
}
/**
* 生成服务方法参数
*
* ✅ V2: 智能匹配Service方法签名
*
* 策略:
* 1. 尝试从Service文件读取方法签名
* 2. 根据参数类型智能映射到Controller参数源
* 3. 如果读取失败回退到基于HTTP路由的简单映射
*/
generateServiceMethodParameters(method, javaController) {
// 尝试从Service文件读取方法签名
const serviceParams = this.readServiceMethodSignature(method, javaController);
if (serviceParams !== null) {
// 成功读取到Service签名包括0参数的情况
if (serviceParams.length === 0) {
return ''; // 无参数方法
}
return this.mapServiceParametersToController(serviceParams, method);
}
// 回退到旧逻辑基于HTTP路由
return this.generateParametersFromRoute(method);
}
/**
* ✅ V2: 从中央索引读取Service方法签名不再读取文件
*/
readServiceMethodSignature(method, javaController) {
try {
// 如果没有索引,回退到旧逻辑
if (!this.serviceMethodSignatureIndex) {
return null;
}
// 从methodServiceCalls中获取实际调用的Service名称
const javaMethodName = method.javaMethodName;
const methodServiceCalls = javaController.methodServiceCalls || {};
const serviceCalls = methodServiceCalls[javaMethodName] || [];
if (serviceCalls.length === 0) {
return null;
}
const serviceImplName = serviceCalls[0].serviceImpl;
const serviceMethodName = serviceCalls[0].serviceMethod;
// ✅ 从中央索引查询方法签名
const key = `${serviceImplName}.${serviceMethodName}`;
const signature = this.serviceMethodSignatureIndex.get(key);
if (!signature) {
return null; // 索引中没有找到
}
// 返回参数列表(已经是 {name, type} 格式)
return signature.parameters || [];
} catch (error) {
// 查询失败返回null
return null;
}
}
/**
* 智能映射Service参数到Controller参数源
*
* ✅ V6 策略Java类型感知
* - Java Integer/Long/int/long → Number(id) 或 Number(query.xxx)
* - Java String → query.xxx
* - DTO/Param → body 或 query
*/
mapServiceParametersToController(serviceParams, method) {
const controllerParams = [];
const httpMethod = method.httpMethod.toLowerCase();
const isGetRequest = httpMethod === 'get';
const isPostOrPut = httpMethod === 'post' || httpMethod === 'put';
for (const param of serviceParams) {
const paramType = param.type;
const paramName = param.name;
// ✅ 将Java类型转为TS类型用于判断
const tsType = this.javaTypeToTsType(paramType);
// 1. 路径参数:根据类型决定是否转换
if (method.path && method.path.includes(`{${paramName}}`)) {
if (tsType === 'number') {
controllerParams.push(`Number(${paramName})`);
} else {
controllerParams.push(paramName);
}
}
// 2. DTO/Param类型整体传递
else if (this.isBodyParameter(paramType)) {
if (isPostOrPut) {
controllerParams.push('body');
} else {
controllerParams.push('query');
}
}
// 3. 基本类型
else if (tsType === 'number' || tsType === 'string' || tsType === 'boolean') {
if (tsType === 'number') {
controllerParams.push(`Number(query.${paramName})`);
} else {
controllerParams.push(`query.${paramName}`);
}
}
// 4. 默认query
else {
controllerParams.push('query');
}
}
return controllerParams.join(', ');
}
/**
* ✅ 将Java类型转换为TypeScript类型
*/
javaTypeToTsType(javaType) {
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',
// 其他
'void': 'void',
'Object': 'any'
};
return typeMap[javaType] || javaType; // 未映射的保持原样
}
/**
* 判断是否是ID参数
*/
isIdParameter(paramName, paramType) {
return paramName === 'id' ||
paramName.endsWith('Id') ||
(paramType === 'number' && paramName.includes('id'));
}
/**
* 判断是否是Body参数DTO/Param类型
*/
isBodyParameter(paramType) {
return paramType.includes('Dto') ||
paramType.includes('Param') ||
paramType.includes('Body') ||
paramType.includes('Request');
}
/**
* 判断是否是分页参数
*/
isPageParameter(paramType) {
return paramType.includes('PageParam') ||
paramType.includes('Page');
}
/**
* 判断是否是搜索参数
*/
isSearchParameter(paramType) {
return paramType.includes('Search') ||
paramType.includes('Query') ||
paramType.includes('Filter');
}
/**
* 判断是否是基本类型
*/
isPrimitiveParameter(paramType) {
return ['number', 'string', 'boolean'].includes(paramType.toLowerCase());
}
/**
* 基于HTTP路由生成参数回退逻辑
*/
generateParametersFromRoute(method) {
const parameters = [];
// 根据HTTP方法添加参数
if (method.httpMethod.toLowerCase() === 'post' || method.httpMethod.toLowerCase() === 'put') {
parameters.push('body');
}
// 添加路径参数 - 使用实际的参数名
if (method.path.includes('{')) {
const paramNames = method.path.match(/\{(\w+)\}/g);
if (paramNames && paramNames.length === 1) {
// 单个参数,使用参数名
const paramName = paramNames[0].replace(/[{}]/g, '');
parameters.push(paramName);
} else if (paramNames && paramNames.length > 1) {
// 多个参数使用params对象
parameters.push('params');
}
}
// 添加查询参数 - 只在GET请求时添加
if (method.httpMethod.toLowerCase() === 'get') {
parameters.push('query');
}
return parameters.join(', ');
}
/**
* 生成构造函数
*/
generateConstructor(javaController) {
// 获取依赖列表,如果为空则从控制器名称推断
let dependencies = javaController.dependencies || [];
if (dependencies.length === 0) {
// 从控制器名称推断 Service 依赖
// Controller -> ControllerServiceImpl匹配Java Service命名规范
const controllerName = javaController.className.replace(/Controller$/, '');
dependencies = [controllerName + 'ServiceImpl'];
}
const constructorParams = [];
dependencies.forEach(dep => {
// 检查Service文件是否存在
const serviceFileName = this.namingUtils.generateFileName(dep, 'service');
const serviceRelativePath = this.findServiceFile(serviceFileName);
if (serviceRelativePath) {
const serviceName = this.getActualServiceClassName(dep); // 使用实际类名
const servicePropertyName = this.namingUtils.toCamelCase(dep) + 'Service';
constructorParams.push(` private readonly ${servicePropertyName}: ${serviceName}`);
}
});
if (constructorParams.length === 0) {
return ' constructor() {}';
}
return ` constructor(
${constructorParams.join(',\n')}
) {}`;
}
/**
* 验证控制器一致性
*/
validateControllerConsistency(javaController, nestJSController) {
const issues = [];
// 验证路由路径
if (javaController.routeInfo.controllerPath !== nestJSController.routeInfo.controllerPath) {
issues.push('控制器路径不一致');
}
// 验证方法数量
if (javaController.routeInfo.methods.length !== nestJSController.routeInfo.methods.length) {
issues.push('方法数量不一致');
}
// 验证每个方法
javaController.routeInfo.methods.forEach((javaMethod, index) => {
const nestJSMethod = nestJSController.routeInfo.methods[index];
if (nestJSMethod && javaMethod.path !== nestJSMethod.path) {
issues.push(`方法路径不一致: ${javaMethod.path} vs ${nestJSMethod.path}`);
}
});
return issues;
}
}
module.exports = ControllerGenerator;