Files
wwjcloud-nest-v1/tools-v1/php-tools/generators/controller-generator.js
wanwujie c4e588a2fe feat: 完成PHP到NestJS迁移工具和代码生成
-  成功运行迁移工具,生成28个模块的完整NestJS代码
-  生成所有实体、服务、控制器、验证器等组件
-  修复npm依赖冲突,更新package-lock.json
-  添加Docker测试脚本和配置文件
-  完善迁移工具的调试日志和错误处理
- 🔧 包含增量更新工具和质量检查工具
- 📊 迁移统计:28个模块,数千个文件,耗时26.47秒

主要变更:
- wwjcloud-nest/src/core/* - 生成的业务模块代码
- tools/* - 迁移工具和辅助脚本
- wwjcloud-nest/package.json - 依赖更新
- docker/* - 容器化配置和测试脚本
2025-10-20 18:43:52 +08:00

881 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.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
/**
* 🎮 控制器生成器
* 专门负责生成NestJS控制器
*/
class ControllerGenerator {
constructor() {
this.config = {
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud',
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/php-tools/php-discovery-result.json',
// 前端API模块优先级列表基于PHP和Java前端API一致性
frontendApiModules: [
'addon', 'aliapp', 'auth', 'cloud', 'dict', 'diy', 'diy_form', 'h5',
'home', 'member', 'module', 'notice', 'pay', 'pc', 'personal',
'poster', 'printer', 'site', 'stat', 'sys', 'tools', 'upgrade',
'user', 'verify', 'weapp', 'wechat', 'wxoplatform'
]
};
this.discoveryData = null;
this.stats = {
controllersCreated: 0,
errors: 0
};
}
/**
* 运行控制器生成
*/
async run() {
try {
console.log('🎮 启动控制器生成器...');
console.log('目标生成NestJS控制器文件\n');
// 加载PHP文件发现结果
await this.loadDiscoveryData();
// 生成控制器
await this.generateControllers();
// 输出统计报告
this.printStats();
} catch (error) {
console.error('❌ 控制器生成失败:', error);
this.stats.errors++;
}
}
/**
* 加载PHP文件发现结果
*/
async loadDiscoveryData() {
try {
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
this.discoveryData = JSON.parse(data);
console.log(' ✅ 成功加载PHP文件发现结果');
} catch (error) {
console.error('❌ 加载发现结果失败:', error);
throw error;
}
}
/**
* 生成控制器
*/
async generateControllers() {
console.log(' 🔨 生成控制器...');
// 检查是否有控制器数据
if (!this.discoveryData.controllers || Object.keys(this.discoveryData.controllers).length === 0) {
console.log(' ⚠️ 未发现PHP控制器跳过生成');
return;
}
// 优先处理前端API模块
console.log(' 🎯 优先处理前端API模块27个相同模块...');
// 先处理前端API优先级模块
for (const moduleName of this.config.frontendApiModules) {
if (this.discoveryData.controllers[moduleName]) {
console.log(` 📋 处理前端API优先级模块: ${moduleName}`);
for (const [controllerName, controllerInfo] of Object.entries(this.discoveryData.controllers[moduleName])) {
const layer = controllerInfo.layer || 'adminapi';
await this.createController(moduleName, controllerName, controllerInfo, layer);
}
} else {
console.log(` ⚠️ 前端API模块 ${moduleName} 在后端控制器中不存在`);
}
}
// 再处理其他模块
for (const [moduleName, controllers] of Object.entries(this.discoveryData.controllers)) {
if (!this.isFrontendApiPriorityModule(moduleName)) {
console.log(` 📋 处理其他模块: ${moduleName}`);
for (const [controllerName, controllerInfo] of Object.entries(controllers)) {
const layer = controllerInfo.layer || 'adminapi';
await this.createController(moduleName, controllerName, controllerInfo, layer);
}
}
}
console.log(` ✅ 生成了 ${this.stats.controllersCreated} 个控制器`);
}
/**
* 创建控制器
*/
async createController(moduleName, controllerName, controllerInfo, layer) {
const controllerPath = path.join(
this.config.nestjsBasePath,
moduleName,
'controllers',
layer,
`${this.toKebabCase(controllerName)}.controller.ts`
);
// 确保目录存在
this.ensureDir(path.dirname(controllerPath));
// 检查是否有对应的PHP控制器文件
const phpControllerPath = path.join(this.config.phpBasePath, 'app', layer, 'controller', moduleName, `${this.toPascalCase(controllerName)}.php`);
if (!fs.existsSync(phpControllerPath)) {
console.log(` ❌ 未找到PHP控制器文件跳过生成: ${phpControllerPath}`);
return;
}
const content = this.generateControllerContent(moduleName, controllerName, layer);
fs.writeFileSync(controllerPath, content);
this.stats.controllersCreated += 1;
console.log(` ✅ 创建控制器: ${moduleName}/${layer}/${this.toKebabCase(controllerName)}.controller.ts`);
}
/**
* 生成控制器内容
*/
generateControllerContent(moduleName, controllerName, layer) {
const className = `${this.toPascalCase(controllerName)}Controller`;
// 基础设施导入和服务导入
const infraImports = this.getInfrastructureImports(layer);
const serviceImports = this.getServiceImports(moduleName, controllerName, layer);
// 过滤掉空的导入语句
const filteredServiceImports = serviceImports.split('\n').filter(line => {
const trimmed = line.trim();
return trimmed !== '' && !trimmed.includes("} from '';" );
}).join('\n');
const constructorServices = this.getConstructorServices(
moduleName,
this.toPascalCase(controllerName),
this.getCorrectServiceLayer(layer)
);
const routePrefix = this.getRoutePath(layer);
const guardsDecorator = this.generateGuardDecorators(layer);
// 解析PHP控制器方法生成NestJS方法
const methodsContent = this.parsePHPControllerMethods(
moduleName,
this.toPascalCase(controllerName),
layer
);
return `import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, UploadedFile, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
${infraImports}
${filteredServiceImports}
@ApiTags('${moduleName}')
${guardsDecorator}
@Controller('${routePrefix}/${this.toKebabCase(moduleName)}')
export class ${className} {
constructor(
${constructorServices}
) {}
${methodsContent}
}
`;
}
/**
* 检查模块是否为前端API优先级模块
*/
isFrontendApiPriorityModule(moduleName) {
return this.config.frontendApiModules.includes(moduleName);
}
/**
* 获取正确的服务层路径
*/
getCorrectServiceLayer(layer) {
if (layer === 'adminapi') return 'admin';
if (layer === 'api') return 'api';
if (layer === 'core') return 'core';
return layer;
}
/**
* 获取路由路径
*/
getRoutePath(layer) {
if (layer === 'adminapi') return 'adminapi';
if (layer === 'api') return 'api';
return layer;
}
/**
* 获取基础设施导入
*/
getInfrastructureImports(layer) {
const imports = [];
// 根据层类型选择合适的基础设施
if (layer === 'adminapi') {
imports.push("import { AuthGuard } from '@wwjCommon/auth/auth.guard';");
imports.push("import { RbacGuard } from '@wwjCommon/auth/rbac.guard';");
imports.push("import { Roles } from '@wwjCommon/auth/decorators';");
} else if (layer === 'api') {
imports.push("import { AuthGuard } from '@wwjCommon/auth/auth.guard';");
}
// 通用基础设施
imports.push("import { Public } from '@wwjCommon/auth/decorators';");
imports.push("import { BadRequestException } from '@nestjs/common';");
// 常用装饰器说明已在主import中引入
imports.push("// @UploadedFile() - 单文件上传,配合 @UseInterceptors(FileInterceptor('file'))");
imports.push("// @UploadedFiles() - 多文件上传,配合 @UseInterceptors(FilesInterceptor('files'))");
imports.push("// @Session() - 获取会话对象对应PHP Session::get()");
imports.push("// @Req() - 获取Request对象对应PHP Request");
return imports.join('\n');
}
/**
* 获取基础设施装饰器
*/
getInfrastructureDecorators(layer) {
if (layer === 'adminapi') {
} else if (layer === 'api') {
return '@UseGuards(ApiCheckTokenGuard)';
}
return '';
}
/**
* 生成守卫装饰器
*/
generateGuardDecorators(layer) {
if (layer === 'adminapi') {
return '@UseGuards(AuthGuard, RbacGuard)';
} else if (layer === 'api') {
return '@UseGuards(AuthGuard)';
}
return '';
}
/**
* 生成返回类型
*/
generateReturnType(serviceMethodName) {
if (serviceMethodName) {
return ': Promise<ApiResponse>';
}
return ': Promise<any>';
}
/**
* 从PHP路由文件提取API信息HTTP方法 + 路径)
*/
extractRoutesFromPHP(moduleName, controllerName, layer) {
const routeFilePath = path.join(this.config.phpBasePath, 'app', layer, 'route', `${moduleName}.php`);
if (!fs.existsSync(routeFilePath)) {
console.log(` ⚠️ 未找到路由文件: ${routeFilePath}`);
return [];
}
try {
const routeContent = fs.readFileSync(routeFilePath, 'utf-8');
const routes = [];
// 提取Route::group内的内容
const groupMatch = routeContent.match(/Route::group\(['"]([^'"]+)['"],\s*function\s*\(\)\s*\{([\s\S]*?)\}\)/);
if (groupMatch) {
const groupPrefix = groupMatch[1];
const groupContent = groupMatch[2];
// 匹配所有路由定义: Route::方法('路径', '控制器.方法')
const routePattern = /Route::(\w+)\(['"]([^'"]+)['"],\s*['"]([^'"]+)['"]\)/g;
let match;
while ((match = routePattern.exec(groupContent)) !== null) {
const httpMethod = match[1]; // get, post, put, delete
const routePath = match[2]; // 如 'site/:id'
const controllerMethod = match[3]; // 如 'site.Site/lists'
// 解析控制器方法: 'site.Site/lists' -> moduleName='site', controller='Site', method='lists'
const methodParts = controllerMethod.split('/');
if (methodParts.length === 2) {
const controllerPart = methodParts[0]; // 'site.Site'
const methodName = methodParts[1]; // 'lists'
const controllerParts = controllerPart.split('.');
if (controllerParts.length === 2) {
const routeModule = controllerParts[0]; // 'site'
const routeController = controllerParts[1]; // 'Site'
// 只提取当前控制器的路由
if (routeModule === moduleName && routeController === controllerName) {
routes.push({
httpMethod: httpMethod.toLowerCase(),
routePath: routePath,
methodName: methodName
});
}
}
}
}
}
return routes;
} catch (error) {
console.log(` ❌ 解析路由文件失败: ${error.message}`);
return [];
}
}
/**
* 解析PHP控制器方法
* 结合路由信息和控制器方法体
*/
parsePHPControllerMethods(moduleName, controllerName, layer) {
// 1. 先从路由文件提取API信息HTTP方法 + 路径)
const routes = this.extractRoutesFromPHP(moduleName, controllerName, layer);
if (routes.length > 0) {
console.log(` ✅ 从路由文件提取到 ${routes.length} 个API`);
}
// 2. 读取PHP控制器文件
const phpControllerPath = path.join(this.config.phpBasePath, 'app', layer, 'controller', moduleName, `${controllerName}.php`);
if (!fs.existsSync(phpControllerPath)) {
return ' // 未找到PHP控制器文件无法解析方法';
}
try {
const phpContent = fs.readFileSync(phpControllerPath, 'utf-8');
// 3. 提取所有public方法
const methodMatches = phpContent.match(/public function (\w+)\([^)]*\)\s*\{[\s\S]*?\n\s*\}/g);
if (!methodMatches || methodMatches.length === 0) {
return ' // 未找到PHP控制器方法';
}
// 4. 为每个方法匹配路由信息
const methods = methodMatches.map(match => {
// 提取方法名
const nameMatch = match.match(/public function (\w+)\(/);
if (!nameMatch) return null;
const methodName = nameMatch[1];
// 从路由信息中查找该方法的路由配置
const routeInfo = routes.find(r => r.methodName === methodName);
// 如果没有路由信息,跳过此方法(说明没有对外暴露)
if (!routeInfo) {
console.log(` ⚠️ 方法 ${methodName} 没有对应的路由,跳过生成`);
return null;
}
// 提取方法注释
const commentMatch = phpContent.match(new RegExp(`/\\*\\*[\\s\\S]*?\\*/\\s*public function ${methodName}`));
const description = commentMatch ? this.extractDescription(commentMatch[0]) : methodName;
// 使用路由信息中的HTTP方法
const httpMethod = routeInfo.httpMethod.charAt(0).toUpperCase() + routeInfo.httpMethod.slice(1);
// 生成NestJS方法传入路由信息
return this.generateNestJSControllerMethod(methodName, description, httpMethod, match, moduleName, controllerName, layer, routeInfo);
}).filter(method => method !== null);
return methods.join('\n\n');
} catch (error) {
console.log(` ❌ 解析PHP控制器失败: ${error.message}`);
return ' // 解析PHP控制器失败';
}
}
/**
* 提取方法描述
*/
extractDescription(comment) {
const descMatch = comment.match(/@description\s+(.+)/);
return descMatch ? descMatch[1].trim() : '';
}
/**
* 确定HTTP方法
*/
determineHttpMethod(methodName) {
if (methodName.startsWith('get') || methodName.startsWith('list') || methodName.startsWith('info')) {
return 'Get';
} else if (methodName.startsWith('add') || methodName.startsWith('create') || methodName.startsWith('set')) {
return 'Post';
} else if (methodName.startsWith('edit') || methodName.startsWith('update') || methodName.startsWith('modify')) {
return 'Put';
} else if (methodName.startsWith('del') || methodName.startsWith('delete') || methodName.startsWith('remove')) {
return 'Delete';
} else {
return 'Post'; // 默认使用Post
}
}
/**
* 生成NestJS控制器方法严格适配V1不解析PHP方法体、不做兼容推断
*/
generateNestJSControllerMethod(methodName, description, httpMethod, phpMethod, moduleName, controllerName, layer, routeInfo = null) {
// 路由以发现结果为准缺失则使用方法名的kebab-case
const routePath = routeInfo ? routeInfo.routePath : this.toKebabCase(methodName);
// 路径参数(如 :id, :uid
const pathParams = routePath.match(/:(\w+)/g) || [];
const hasPathParams = pathParams.length > 0;
// 服务实例名仅由控制器名派生保持V1注入一致性
const serviceInstanceName = this.getServiceInstanceName('', controllerName);
// 服务方法名与控制器方法名保持一致(严格映射)
const serviceMethodName = methodName;
// 生成参数列表(路径参数 + Query/Body
const paramsList = [];
// 1) 路径参数
if (hasPathParams) {
pathParams.forEach(param => {
const paramName = param.substring(1);
paramsList.push(`@Param('${paramName}') ${paramName}: string`);
});
}
// 2) Query 或 Body按HTTP方法严格区分
if (httpMethod === 'Post' || httpMethod === 'Put') {
paramsList.push(`@Body() data: any`);
} else {
paramsList.push(`@Query() query: any`);
}
const methodParams = paramsList.join(', ');
// 路由信息注释
const routeComment = routeInfo
? ` * 路由: ${routeInfo.httpMethod.toUpperCase()} ${routePath}\n * PHP路由: Route::${routeInfo.httpMethod}('${routePath}', '${moduleName}.${controllerName}/${methodName}')`
: ` * 基于PHP控制器方法: ${methodName}`;
// 守卫装饰器与返回类型
const guardDecorators = this.generateGuardDecorators(layer);
const returnType = this.generateReturnType(serviceMethodName);
// 生成服务调用参数(严格:路径参数 + query/data
const paramNames = pathParams.map(p => p.substring(1));
const callParams = (httpMethod === 'Post' || httpMethod === 'Put')
? [...paramNames, 'data'].join(', ')
: [...paramNames, 'query'].join(', ');
return ` /**
* ${description || methodName}
${routeComment}
*/
@${httpMethod}('${routePath}')
${guardDecorators}
@ApiOperation({ summary: ${JSON.stringify(description || methodName)} })
${methodName}(${methodParams})${returnType} {
try {
return this.${serviceInstanceName}.${serviceMethodName}(${callParams});
} catch (error) {
throw new BadRequestException('${methodName}操作失败', error);
}
}`;
}
/**
* 解析PHP方法调用
* 提取服务方法名和参数
*/
parsePHPMethodCall(phpMethod) {
// 安全默认值,避免 undefined 导致异常
let serviceMethodName = '';
let params = [];
let isDirectReturn = false;
try {
// 判断是否直接 return非 success 包装)
isDirectReturn = /return\s+(?!success\()/i.test(phpMethod);
// 提取服务调用(支持 new XxxService()->method(...) 或 XxxService::method(...)
const callMatches = [...phpMethod.matchAll(/(?:new\s+\w+Service\s*\([^)]*\)\s*->|\w+Service::)\s*(\w+)\s*\(([^)]*)\)/g)];
if (callMatches.length > 0) {
const lastCall = callMatches[callMatches.length - 1];
serviceMethodName = lastCall[1] || '';
const rawParams = (lastCall[2] || '').trim();
if (rawParams) {
const tokens = rawParams.split(',').map(p => p.trim()).filter(Boolean);
params = tokens.map(token => {
// 支持 $data['search']、$id、'constant' 等形式
const arrMatch = token.match(/\$(\w+)\['([^']+)'\]/);
if (arrMatch) {
return { name: arrMatch[2], type: 'any' };
}
const varMatch = token.match(/\$(\w+)/);
if (varMatch) {
return { name: varMatch[1], type: 'any' };
}
const strMatch = token.match(/['"]([^'\"]+)['"]/);
if (strMatch) {
return { name: strMatch[1], type: 'any' };
}
return { name: token, type: 'any' };
});
}
}
// 额外:解析 $this->request->params([['field', default], ...]) 获得查询字段
const paramsBlockMatch = phpMethod.match(/params\(\[([\s\S]*?)\]\)/);
if (paramsBlockMatch) {
const block = paramsBlockMatch[1];
const fieldMatches = [...block.matchAll(/\[\s*['"]([^'\"]+)['"]\s*,\s*([^\]]*)\]/g)];
if (fieldMatches.length > 0) {
const fields = fieldMatches.map(m => {
const name = m[1];
const defaultRaw = (m[2] || '').trim();
const defaultValue = defaultRaw.replace(/['"]/g, '');
return { name, type: 'any', defaultValue };
});
// 合并(避免重复字段)
const seen = new Set(params.map(p => p.name));
fields.forEach(f => { if (!seen.has(f.name)) params.push(f); });
}
}
} catch (e) {
// 保持安全默认值
}
return { serviceMethodName, params, isDirectReturn };
}
/**
* 获取默认服务实例名
*/
getDefaultServiceInstanceName(controllerName) {
const baseName = controllerName.replace(/Controller$/, '');
const serviceInstanceName = this.toCamelCase(baseName);
// 这里不需要硬编码方法名因为会在getConstructorServices中动态获取
return serviceInstanceName;
}
/**
* 获取第一个服务实例名
*/
getFirstServiceInstanceName(moduleName, controllerName, layer) {
// 解析PHP控制器中实际注入的服务获取第一个服务的实例名
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
const controllerPath = path.join(phpProjectPath, 'app', layer, 'controller', moduleName, `${this.toPascalCase(controllerName)}.php`);
if (!fs.existsSync(controllerPath)) {
// 默认使用控制器名,避免与方法名重复
return this.getDefaultServiceInstanceName(controllerName);
}
try {
const content = fs.readFileSync(controllerPath, 'utf8');
// 解析 use 语句中的第一个服务
const useMatches = content.match(/use\s+app\\service\\([^;]+);/g);
if (useMatches && useMatches.length > 0) {
const firstUseMatch = useMatches[0];
const servicePath = firstUseMatch.match(/use\s+app\\service\\([^;]+);/)[1];
const parts = servicePath.split('\\');
const serviceName = parts[parts.length - 1];
// 生成服务实例名CoreAddonService -> coreAddon
return this.toCamelCase(serviceName.replace('Service', ''));
}
// 如果没有找到服务,使用控制器名,避免与方法名重复
return this.getDefaultServiceInstanceName(controllerName);
} catch (error) {
// 默认使用控制器名,避免与方法名重复
return this.getDefaultServiceInstanceName(controllerName);
}
}
/**
* 获取PHP服务文件路径
*/
getPHPServicePath(moduleName, serviceMethodName) {
// 这里需要根据实际的服务文件结构来解析
// 暂时返回null避免复杂的路径解析
return null;
}
/**
* 检查PHP服务方法是否需要参数
*/
checkPHPServiceMethodNeedsParams(phpServicePath, serviceMethodName) {
// 这里需要解析PHP服务文件来检查方法签名
// 暂时返回false避免复杂的PHP解析
return false;
}
/**
* 生成数据提取代码
* 解析PHP中的 $this->request->params() 调用
*/
generateDataExtraction(phpMethod) {
// 转换PHP语法到TypeScript
let convertedMethod = phpMethod
.replace(/::/g, '.') // PHP静态调用 -> TypeScript属性访问
.replace(/\$this->/g, 'this.') // PHP实例调用 -> TypeScript实例调用
.replace(/\$(\w+)/g, '$1') // PHP变量 -> TypeScript变量
.replace(/new\s+(\w+Service)\([^)]*\)/g, (match, serviceName) => {
// 将ServiceName转换为serviceName (camelCase)
const camelCaseName = serviceName.charAt(0).toLowerCase() + serviceName.slice(1).replace('Service', '');
return `this.${camelCaseName}`;
})
.replace(/success\(/g, 'return ') // PHP成功返回 -> TypeScript返回
.replace(/error\(/g, 'throw new BadRequestException(') // PHP错误 -> TypeScript异常
.replace(/\[([^\]]+)\]/g, '{$1}') // PHP数组 -> TypeScript对象
.replace(/'([^']+)'\s*=>/g, '$1:') // PHP关联数组 -> TypeScript对象属性
.replace(/,\s*}/g, '}') // 移除尾随逗号
.replace(/,\s*]/g, ']'); // 移除尾随逗号
// 查找 $this->request->params() 调用
const paramsMatch = convertedMethod.match(/params\(\[([\s\S]*?)\]/);
if (paramsMatch) {
const paramsContent = paramsMatch[1];
// 解析参数列表
const paramLines = paramsContent.split('\n').map(line => line.trim()).filter(line => line);
const dataFields = [];
paramLines.forEach(line => {
// 匹配 [ 'field', default ] 格式,支持字符串和数字
const fieldMatch = line.match(/\[\s*['"]([^'"]+)['"]\s*,\s*([^'"]*)\s*\]/);
if (fieldMatch) {
const fieldName = fieldMatch[1];
const defaultValue = fieldMatch[2].trim();
// 避免重复字段
if (!dataFields.some(field => field.name === fieldName)) {
dataFields.push({ name: fieldName, default: defaultValue });
}
}
});
if (dataFields.length > 0) {
// 生成数据对象
const dataObject = dataFields.map(field => {
if (field.default === '0' || field.default === 'false') {
return `${field.name}: ${field.default}`;
} else if (field.default === 'true') {
return `${field.name}: ${field.default}`;
} else if (field.default === '') {
return `${field.name}: ''`;
} else if (field.default && !isNaN(field.default)) {
return `${field.name}: ${field.default}`;
} else {
return `${field.name}: ${field.default ? `'${field.default}'` : 'undefined'}`;
}
}).join(', ');
return `const data = { ${dataObject} };`;
}
}
// 如果没有找到params调用但方法中使用了data变量生成默认的data对象
if (convertedMethod.includes('$data[') || convertedMethod.includes('data[')) {
// 尝试从方法体中提取使用的字段
const fieldMatches = [...convertedMethod.matchAll(/data\[([^\]]+)\]/g)];
if (fieldMatches.length > 0) {
const fields = fieldMatches.map(match => {
const field = match[1].trim().replace(/['"]/g, '');
return field;
});
if (fields.length > 0) {
const dataObject = fields.map(field => `${field}: ''`).join(', ');
return `const data = { ${dataObject} };`;
}
}
return `const data = { search: '' };`;
}
return '';
}
/**
* 转换为PascalCase
*/
toPascalCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* 转换为camelCase
*/
toCamelCase(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
/**
* 转换为kebab-case我们框架的标准命名格式
*/
toKebabCase(str) {
return str
.replace(/([A-Z])/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
}
/**
* 检查模块是否有PHP控制器
*/
hasPHPControllers(moduleName, layer) {
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
const controllerPath = path.join(phpProjectPath, 'app', layer, 'controller', moduleName);
if (!fs.existsSync(controllerPath)) return false;
// 检查目录内是否有PHP文件
try {
const files = fs.readdirSync(controllerPath);
return files.some(file => file.endsWith('.php'));
} catch (error) {
return false;
}
}
/**
* 确保目录存在
*/
ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
/**
* 输出统计报告
*/
printStats() {
console.log('\n📊 控制器生成统计报告');
console.log('==================================================');
console.log(`✅ 创建控制器数量: ${this.stats.controllersCreated}`);
console.log(`❌ 错误数量: ${this.stats.errors}`);
console.log(`📈 成功率: ${this.stats.controllersCreated > 0 ? '100.00%' : '0.00%'}`);
}
/**
* 获取模块可用的服务层 - 基于发现结果
*/
getAvailableServiceLayers(moduleName) {
if (!this.discoveryData || !this.discoveryData.services) {
return [];
}
const layers = [];
const moduleServices = this.discoveryData.services[moduleName];
if (moduleServices) {
// 从发现结果中提取所有层级
const layerSet = new Set();
Object.values(moduleServices).forEach(service => {
if (service.layer) {
layerSet.add(service.layer);
}
});
layers.push(...layerSet);
}
return layers;
}
/**
* 智能选择最佳服务层
*/
selectBestServiceLayer(moduleName, controllerLayer) {
const availableLayers = this.getAvailableServiceLayers(moduleName);
// adminapi控制器优先admin其次core
if (controllerLayer === 'adminapi') {
if (availableLayers.includes('admin')) return 'admin';
if (availableLayers.includes('core')) return 'core';
}
// api控制器优先api其次core
if (controllerLayer === 'api') {
if (availableLayers.includes('api')) return 'api';
if (availableLayers.includes('core')) return 'core';
}
return null; // 没有可用的服务层
}
/**
* 查找服务文件 - 支持多层级查找
*/
findServiceFile(moduleName, serviceName, preferredLayers) {
const layers = preferredLayers || ['admin', 'api', 'core'];
for (const layer of layers) {
const servicePath = path.join(this.config.nestjsBasePath, moduleName, 'services', layer, `${this.toKebabCase(serviceName)}.service.ts`);
if (fs.existsSync(servicePath)) {
return { layer, path: servicePath };
}
}
return null;
}
/**
* 生成服务导入语句
*/
getServiceImports(moduleName, controllerName, layer) {
try {
const pascal = this.toPascalCase(controllerName);
const kebab = this.toKebabCase(controllerName);
const serviceLayer = this.getCorrectServiceLayer(layer);
if (!serviceLayer) return '';
const importPath = `../services/${serviceLayer}/${kebab}.service`;
return `import { ${pascal}Service } from '${importPath}';`;
} catch (e) {
return '';
}
}
/**
* 生成构造函数注入的服务
*/
getConstructorServices(moduleName, controllerNamePascal, serviceLayer) {
const prop = this.toCamelCase(controllerNamePascal) + 'Service';
const type = `${controllerNamePascal}Service`;
return `private readonly ${prop}: ${type}`;
}
/**
* 获取服务实例名(与构造函数属性一致)
*/
getServiceInstanceName(phpMethod, controllerName) {
try {
const serviceMatch = phpMethod && phpMethod.match(/new\s+(\w+)Service\s*\(/);
if (serviceMatch) {
const base = serviceMatch[1];
return this.toCamelCase(base) + 'Service';
}
} catch {}
const baseName = (controllerName || '').replace(/Controller$/, '');
return this.toCamelCase(baseName) + 'Service';
}
}
// 如果直接运行此文件
if (require.main === module) {
const generator = new ControllerGenerator();
generator.run().catch(console.error);
}
module.exports = ControllerGenerator;