- ✅ 成功运行迁移工具,生成28个模块的完整NestJS代码 - ✅ 生成所有实体、服务、控制器、验证器等组件 - ✅ 修复npm依赖冲突,更新package-lock.json - ✅ 添加Docker测试脚本和配置文件 - ✅ 完善迁移工具的调试日志和错误处理 - 🔧 包含增量更新工具和质量检查工具 - 📊 迁移统计:28个模块,数千个文件,耗时26.47秒 主要变更: - wwjcloud-nest/src/core/* - 生成的业务模块代码 - tools/* - 迁移工具和辅助脚本 - wwjcloud-nest/package.json - 依赖更新 - docker/* - 容器化配置和测试脚本
810 lines
29 KiB
JavaScript
810 lines
29 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
/**
|
||
* 业务逻辑转换器
|
||
* 基于真实PHP代码的转换规则,禁止TODO、假设、自创
|
||
*/
|
||
class BusinessLogicConverter {
|
||
constructor() {
|
||
// 混合模块智能分类规则
|
||
this.hybridClassificationRules = {
|
||
// 需要抽取到Core层的业务逻辑文件
|
||
coreBusinessLogic: [
|
||
// 支付相关
|
||
/pay/i,
|
||
/payment/i,
|
||
/transfer/i,
|
||
/refund/i,
|
||
|
||
// 会员相关
|
||
/member/i,
|
||
/user.*profile/i,
|
||
/account/i,
|
||
|
||
// 业务配置
|
||
/config.*pay/i,
|
||
/config.*member/i,
|
||
/config.*order/i,
|
||
|
||
// 订单相关
|
||
/order/i,
|
||
/goods/i,
|
||
/product/i,
|
||
|
||
// 认证业务逻辑
|
||
/login.*business/i,
|
||
/auth.*business/i,
|
||
/register/i,
|
||
|
||
// DIY业务
|
||
/diy/i,
|
||
/custom/i,
|
||
|
||
// 营销业务
|
||
/promotion/i,
|
||
/coupon/i,
|
||
/discount/i
|
||
],
|
||
|
||
// 应该使用Common基础服务的文件
|
||
useCommonInfrastructure: [
|
||
// 基础服务接口
|
||
/BaseController/,
|
||
/BaseService/,
|
||
/BaseModel/,
|
||
|
||
// 通用工具
|
||
/upload/i,
|
||
/export/i,
|
||
/attachment/i,
|
||
/sys.*config/i,
|
||
/system.*info/i,
|
||
/cache/i,
|
||
/redis/i,
|
||
|
||
// 基础认证
|
||
/jwt/i,
|
||
/token/i,
|
||
/guard/i,
|
||
/middleware/i
|
||
]
|
||
};
|
||
|
||
this.phpRegexPatterns = [
|
||
// PHP类型转换
|
||
{ pattern: /\$([a-zA-Z_][a-zA-Z0-9_]*)/g, replacement: '$1' },
|
||
{ pattern: /\->/g, replacement: '.' },
|
||
{ pattern: /public function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)/g, replacement: 'async $1($2)' },
|
||
{ pattern: /private function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)/g, replacement: 'private async $1($2)' },
|
||
{ pattern: /protected function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)/g, replacement: 'protected async $1($2)' },
|
||
|
||
// PHP参数类型转换
|
||
{ pattern: /string\s+\$([a-zA-Z_][a-zA-Z0-9_]*)/g, replacement: '$1: string' },
|
||
{ pattern: /int\s+\$([a-zA-Z_][a-zA-Z0-9_]*)/g, replacement: '$1: number' },
|
||
{ pattern: /array\s+\$([a-zA-Z_][a-zA-Z0-9_]*)/g, replacement: '$1: any[]' },
|
||
{ pattern: /bool\s+\$([a-zA-Z_][a-zA-Z0-9_]*)/g, replacement: '$1: boolean' },
|
||
|
||
// PHP语法转换
|
||
{ pattern: /this\s*\->\s*model/g, replacement: 'this.model' },
|
||
{ pattern: /new\s+([A-Z][a-zA-Z0-9_]*)\(\)/g, replacement: 'this.$1Repository' },
|
||
{ pattern: /parent::__construct\(\)/g, replacement: 'super()' },
|
||
|
||
// PHP函数转换
|
||
{ pattern: /empty\s*\(\s*([^)]+)\s*\)/g, replacement: '!$1' },
|
||
{ pattern: /isset\s*\(\s*([^)]+)\s*\)/g, replacement: '$1 !== undefined' },
|
||
{ pattern: /is_null\s*\(\s*([^)]+)\s*\)/g, replacement: '$1 === null' },
|
||
{ pattern: /is_array\s*\(\s*([^)]+)\s*\)/g, replacement: 'Array.isArray($1)' },
|
||
{ pattern: /is_string\s*\(\s*([^)]+)\s*\)/g, replacement: 'typeof $1 === "string"' },
|
||
{ pattern: /is_numeric\s*\(\s*([^)]+)\s*\)/g, replacement: '!isNaN($1)' },
|
||
|
||
// 字符串拼接
|
||
{ pattern: /\.\s*=/g, replacement: '+=' },
|
||
{ pattern: /\.(\s*['""])/g, replacement: ' + $1' },
|
||
|
||
// 数组语法
|
||
{ pattern: /array\(\)/g, replacement: '[]' },
|
||
{ pattern: /array\(([^)]+)\)/g, replacement: '[$1]' },
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 智能分类:判断文件应该迁移到Core层还是使用Common基础设施
|
||
*/
|
||
classifyFile(filePath, className, content) {
|
||
const fileName = path.basename(filePath, '.php');
|
||
const fullContext = `${fileName} ${className} ${content}`.toLowerCase();
|
||
|
||
// 检查是否应该使用Common基础设施
|
||
for (const pattern of this.hybridClassificationRules.useCommonInfrastructure) {
|
||
if (pattern.test(fileName) || pattern.test(className) || pattern.test(content)) {
|
||
return 'INFRASTRUCTURE';
|
||
}
|
||
}
|
||
|
||
// 检查是否应该迁移到Core层
|
||
for (const pattern of this.hybridClassificationRules.coreBusinessLogic) {
|
||
if (pattern.test(fileName) || pattern.test(className) || pattern.test(content)) {
|
||
return 'CORE_BUSINESS';
|
||
}
|
||
}
|
||
|
||
// 默认根据模块名判断
|
||
const moduleName = this.extractModuleName(filePath);
|
||
if (['sys', 'upload', 'config', 'export'].includes(moduleName)) {
|
||
return 'INFRASTRUCTURE'; // 基础服务
|
||
}
|
||
|
||
return 'CORE_BUSINESS'; // 默认为业务逻辑
|
||
}
|
||
|
||
/**
|
||
* 从文件路径提取模块名
|
||
*/
|
||
extractModuleName(filePath) {
|
||
const match = filePath.match(/\/([^\/]+)\/.+\.php$/);
|
||
return match ? match[1] : 'unknown';
|
||
}
|
||
|
||
/**
|
||
* 替换PHP基础设施调用为NestJS基础设施调用
|
||
*/
|
||
replaceInfrastructureCalls(tsCode) {
|
||
let convertedCode = tsCode;
|
||
|
||
// 替换PHP基础类为NestJS Common层
|
||
const infrastructureReplacements = [
|
||
// 按 v1 boot 实现进行映射
|
||
{ from: /core\\cache\\RedisCacheService/g, to: '@wwjCommon/cache/cache.service' },
|
||
{ from: /CoreRequestService/g, to: '@wwjCommon/http/request-context.service' },
|
||
// 旧的 BaseService/BaseController/BaseApiService 在 v1 中不再使用,避免误替换
|
||
// 日志统一使用 Nest Logger 或拦截器,不再映射到自定义 LoggingService
|
||
];
|
||
|
||
infrastructureReplacements.forEach(({ from, to }) => {
|
||
convertedCode = convertedCode.replace(from, to);
|
||
});
|
||
|
||
return convertedCode;
|
||
}
|
||
|
||
/**
|
||
* 转换PHP业务逻辑到TypeScript
|
||
*/
|
||
convertBusinessLogic(content, methodName, phpCode) {
|
||
try {
|
||
console.log(`🔄 转换方法: ${methodName}`);
|
||
|
||
let convertedCode = phpCode;
|
||
|
||
// 1. 先转换PHP语法到TypeScript
|
||
convertedCode = convertedCode
|
||
// 变量转换 - 移除$符号 (必须在->转换之前)
|
||
.replace(/\$this->([a-zA-Z_][a-zA-Z0-9_]*)/g, 'this.$1')
|
||
.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, '$1')
|
||
|
||
// PHP数组语法 => 转换为对象属性 :
|
||
.replace(/'([a-zA-Z_][a-zA-Z0-9_]*)'\s*=>/g, '$1:')
|
||
.replace(/"([a-zA-Z_][a-zA-Z0-9_]*)"\s*=>/g, '$1:')
|
||
|
||
// PHP空值合并 ?? 转换为 ||
|
||
.replace(/\?\?/g, '||')
|
||
|
||
// PHP对象访问 -> 转换为 . (必须在$转换之后)
|
||
.replace(/->/g, '.')
|
||
|
||
// PHP静态访问 :: 转换为 .
|
||
.replace(/::/g, '.')
|
||
|
||
// PHP new对象转换 - 修复转换逻辑(避免重复Service后缀)
|
||
.replace(/\(new\s+([A-Z][a-zA-Z0-9_]*)\(\)\)/g, (match, serviceName) => {
|
||
if (serviceName.endsWith('Service')) {
|
||
return `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}`;
|
||
} else {
|
||
return `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}Service`;
|
||
}
|
||
})
|
||
.replace(/new\s+([A-Z][a-zA-Z0-9_]*)\(\)/g, (match, serviceName) => {
|
||
if (serviceName.endsWith('Service')) {
|
||
return `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}`;
|
||
} else {
|
||
return `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}Service`;
|
||
}
|
||
})
|
||
|
||
// PHP类型声明转换为TypeScript
|
||
.replace(/array\s+/g, '')
|
||
.replace(/:\s*array/g, ': any[]')
|
||
|
||
// 变量声明添加const/let
|
||
.replace(/^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=/gm, '$1const $2 =')
|
||
|
||
// 修复数组访问
|
||
.replace(/\['([^']+)'\]/g, '.$1')
|
||
.replace(/\["([^"]+)"\]/g, '.$1')
|
||
|
||
// 修复PHP函数调用
|
||
.replace(/array_merge\s*\(/g, 'Object.assign(')
|
||
.replace(/strpos\s*\(/g, 'String.prototype.indexOf.call(')
|
||
.replace(/throw\s+new\s+([A-Z][a-zA-Z0-9_]*)\s*\(/g, 'throw new $1(')
|
||
|
||
// 修复PHP条件语句
|
||
.replace(/if\s*\(\s*([^)]+)\s*\)\s*\{/g, 'if ($1) {')
|
||
.replace(/else\s*\{/g, '} else {')
|
||
|
||
// 修复PHP静态变量访问
|
||
.replace(/self::\$([a-zA-Z_][a-zA-Z0-9_]*)/g, 'self.$1')
|
||
.replace(/static::\$([a-zA-Z_][a-zA-Z0-9_]*)/g, 'static.$1')
|
||
|
||
// 修复PHP is_null函数
|
||
.replace(/is_null\s*\(\s*([^)]+)\s*\)/g, '$1 === null')
|
||
|
||
// 修复PHP new static调用
|
||
.replace(/new\s+static\s*\(([^)]*)\)/g, 'new this.constructor($1)')
|
||
|
||
// 修复PHP数组语法错误
|
||
.replace(/\[\s*\]/g, '[]')
|
||
.replace(/\(\s*\)/g, '()')
|
||
|
||
// 修复PHP变量赋值错误
|
||
.replace(/=\s*=\s*=/g, '===')
|
||
.replace(/=\s*=\s*null/g, '=== null')
|
||
|
||
// 修复重复的等号
|
||
.replace(/====/g, '===')
|
||
.replace(/=====/g, '===')
|
||
|
||
// 修复方括号错误 - 修复函数调用中的方括号(排除数组语法)
|
||
.replace(/\(([^)]+)\]/g, '($1)')
|
||
// 移除错误的替换规则,避免破坏数组语法
|
||
// .replace(/(\w+)\]/g, (match, word) => {
|
||
// // 排除数组元素的情况,避免将 [ 'key', 'value' ] 转换为 [ 'key', 'value' )
|
||
// // 检查是否在数组上下文中
|
||
// const beforeMatch = code.substring(0, code.indexOf(match));
|
||
// const lastBracket = beforeMatch.lastIndexOf('[');
|
||
// const lastParen = beforeMatch.lastIndexOf('(');
|
||
//
|
||
// // 如果最近的符号是 [ 而不是 (,说明在数组上下文中,不应该替换
|
||
// if (lastBracket > lastParen) {
|
||
// return match;
|
||
// }
|
||
//
|
||
// return word + ')';
|
||
// })
|
||
|
||
// 修复数组语法中的方括号错误 - 直接修复(处理单引号和双引号)
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
.replace(/\[\s*\(\s*\"([^\"]+)\",\s*\"\"\s*\)\s*\)/g, '["$1", ""]')
|
||
.replace(/\[\s*\(\s*\"([^\"]+)\",\s*0\s*\)\s*\)/g, '["$1", 0]')
|
||
// 移除这些错误的替换规则,避免破坏数组语法
|
||
// .replace(/\]\s*;/g, ');')
|
||
// .replace(/\]\s*\)/g, '))')
|
||
// .replace(/\]\s*\{/g, ') {')
|
||
// .replace(/\]\s*,/g, '),')
|
||
|
||
// 修复数组语法中的方括号错误 - 更精确的匹配
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
|
||
// 修复数组语法中的方括号错误
|
||
.replace(/\[\s*\(\s*([^)]+)\s*\)\s*\]/g, '[$1]')
|
||
.replace(/\[\s*\(\s*([^)]+)\s*\)\s*\)/g, '[$1]')
|
||
|
||
// 修复数组元素中的方括号错误
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*'([^']+)'\s*\)\s*\)/g, "['$1', '$2']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*(\d+)\s*\)\s*\)/g, "['$1', $2]")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*""\s*\)\s*\)/g, "['$1', '']")
|
||
|
||
// 修复特定的方括号错误模式 - 只修复函数调用中的方括号,不修复数组语法
|
||
// 移除这些错误的替换规则,避免破坏数组语法
|
||
// .replace(/(\w+_id)\]/g, '$1)')
|
||
// .replace(/(\w+_key)\]/g, '$1)')
|
||
// .replace(/(\w+_type)\]/g, '$1)')
|
||
// .replace(/(\w+_name)\]/g, '$1)')
|
||
// .replace(/(\w+_code)\]/g, '$1)')
|
||
// .replace(/(\w+_value)\]/g, '$1)')
|
||
|
||
// 修复函数调用中的方括号错误 - 只修复函数调用中的方括号,不修复数组语法
|
||
// 移除这些错误的替换规则,避免破坏数组语法
|
||
// .replace(/(\w+)\(([^)]+)\]/g, '$1($2)')
|
||
// .replace(/(\w+)\.(\w+)\(([^)]+)\]/g, '$1.$2($3)')
|
||
|
||
// 修复PHP方法声明
|
||
.replace(/public\s+function\s+/g, 'async ')
|
||
.replace(/private\s+function\s+/g, 'private async ')
|
||
.replace(/protected\s+function\s+/g, 'protected async ')
|
||
|
||
// 修复PHP返回语句
|
||
.replace(/return\s+this;/g, 'return this;')
|
||
|
||
// 修复PHP异常处理
|
||
.replace(/CommonException/g, 'BadRequestException')
|
||
.replace(/(?<!BadRequest)Exception/g, 'BadRequestException')
|
||
|
||
// 修复重复的Business前缀
|
||
.replace(/BusinessBusinessException/g, 'BusinessException');
|
||
|
||
// 2. 使用新的清理和验证功能
|
||
convertedCode = this.cleanAndValidateTypeScriptCode(convertedCode);
|
||
|
||
return convertedCode;
|
||
} catch (error) {
|
||
console.error('❌ 业务逻辑转换失败:', error.message);
|
||
return content;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从PHP源码中提取方法信息 (基于真实PHP服务代码分析)
|
||
*/
|
||
extractPHPMethods(phpContent) {
|
||
try {
|
||
const methods = [];
|
||
const methodNames = new Set(); // 防止重复方法
|
||
|
||
// 匹配public方法(包括static和返回类型)
|
||
const publicMethodsRegex = /public\s+(?:static\s+)?function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/g;
|
||
let match;
|
||
|
||
while ((match = publicMethodsRegex.exec(phpContent)) !== null) {
|
||
const methodName = match[1];
|
||
const parameters = match[2] || '';
|
||
|
||
// 跳过构造函数和重复方法
|
||
if (methodName === '__construct' || methodNames.has(methodName)) continue;
|
||
|
||
// 找到方法体的结束位置
|
||
const startPos = match.index + match[0].length;
|
||
const methodBody = this.extractMethodBody(phpContent, startPos);
|
||
|
||
// 检查方法体是否有效(不是空方法或只有注释)
|
||
const cleanBody = methodBody.trim().replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
||
if (cleanBody.length < 10) continue; // 跳过空方法
|
||
|
||
methodNames.add(methodName);
|
||
methods.push({
|
||
name: methodName,
|
||
parameters: this.parsePHPParameters(parameters),
|
||
logic: methodBody.trim(),
|
||
type: 'public'
|
||
});
|
||
}
|
||
|
||
// 匹配private方法(包括static和返回类型)
|
||
const privateMethodsRegex = /private\s+(?:static\s+)?function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/g;
|
||
|
||
while ((match = privateMethodsRegex.exec(phpContent)) !== null) {
|
||
const methodName = match[1];
|
||
const parameters = match[2] || '';
|
||
|
||
// 跳过构造函数和重复方法
|
||
if (methodName === '__construct' || methodNames.has(methodName)) continue;
|
||
|
||
// 找到方法体的结束位置
|
||
const startPos = match.index + match[0].length;
|
||
const methodBody = this.extractMethodBody(phpContent, startPos);
|
||
|
||
// 检查方法体是否有效
|
||
const cleanBody = methodBody.trim().replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
||
if (cleanBody.length < 10) continue; // 跳过空方法
|
||
|
||
methodNames.add(methodName);
|
||
methods.push({
|
||
name: methodName,
|
||
parameters: this.parsePHPParameters(parameters),
|
||
logic: methodBody.trim(),
|
||
type: 'private'
|
||
});
|
||
}
|
||
|
||
return methods;
|
||
} catch (error) {
|
||
console.error('❌ 提取PHP方法失败:', error.message);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析PHP方法参数
|
||
*/
|
||
parsePHPParameters(parameterString) {
|
||
if (!parameterString.trim()) return [];
|
||
|
||
const params = [];
|
||
// 修复正则表达式,正确匹配参数名
|
||
const paramPattern = /(?:int|string|array|bool)?\s*\$([a-zA-Z_][a-zA-Z0-9_]*)(?:\s*=\s*([^,\)]*?))?/g;
|
||
let match;
|
||
|
||
while ((match = paramPattern.exec(parameterString)) !== null) {
|
||
const paramName = match[1];
|
||
const defaultValue = match[2];
|
||
|
||
// 确保参数名不包含方括号,并处理保留字
|
||
const cleanParamName = paramName.replace(/\[\]/g, '');
|
||
const finalParamName = this.handleReservedWords(cleanParamName);
|
||
|
||
params.push({
|
||
name: finalParamName,
|
||
defaultValue: defaultValue ? defaultValue.trim() : undefined,
|
||
type: this.inferParameterType(parameterString, match[0])
|
||
});
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
/**
|
||
* 处理TypeScript保留字
|
||
*/
|
||
handleReservedWords(paramName) {
|
||
const reservedWords = [
|
||
'function', 'class', 'interface', 'enum', 'namespace', 'module',
|
||
'import', 'export', 'default', 'extends', 'implements', 'public',
|
||
'private', 'protected', 'static', 'abstract', 'readonly', 'async',
|
||
'await', 'return', 'if', 'else', 'for', 'while', 'do', 'switch',
|
||
'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw',
|
||
'new', 'this', 'super', 'typeof', 'instanceof', 'in', 'of',
|
||
'var', 'let', 'const', 'true', 'false', 'null', 'undefined',
|
||
'any', 'string', 'number', 'boolean', 'object', 'void', 'never'
|
||
];
|
||
|
||
if (reservedWords.includes(paramName)) {
|
||
return `${paramName}Param`;
|
||
}
|
||
|
||
return paramName;
|
||
}
|
||
|
||
/**
|
||
* 推断参数类型
|
||
*/
|
||
inferParameterType(parameterString, fullMatch) {
|
||
// 简单的类型推断逻辑
|
||
if (parameterString.includes('[]') || parameterString.includes('array')) {
|
||
return 'any[]';
|
||
}
|
||
if (parameterString.includes('int') || parameterString.includes('float') || parameterString.includes('number')) {
|
||
return 'number';
|
||
}
|
||
if (parameterString.includes('string') || parameterString.includes('str')) {
|
||
return 'string';
|
||
}
|
||
if (parameterString.includes('bool')) {
|
||
return 'boolean';
|
||
}
|
||
if (parameterString.includes('object') || parameterString.includes('array')) {
|
||
return 'any';
|
||
}
|
||
|
||
// 默认返回 any
|
||
return 'any';
|
||
}
|
||
|
||
/**
|
||
* 提取方法体(处理嵌套大括号)
|
||
*/
|
||
extractMethodBody(content, startPos) {
|
||
let braceCount = 0;
|
||
let inString = false;
|
||
let stringChar = '';
|
||
let i = startPos;
|
||
let foundFirstBrace = false;
|
||
|
||
while (i < content.length) {
|
||
const char = content[i];
|
||
|
||
// 处理字符串
|
||
if (!inString && (char === '"' || char === "'")) {
|
||
inString = true;
|
||
stringChar = char;
|
||
} else if (inString && char === stringChar) {
|
||
// 检查是否是转义字符
|
||
if (i > 0 && content[i-1] !== '\\') {
|
||
inString = false;
|
||
stringChar = '';
|
||
}
|
||
}
|
||
|
||
// 只在非字符串状态下计算大括号
|
||
if (!inString) {
|
||
if (char === '{') {
|
||
if (!foundFirstBrace) {
|
||
foundFirstBrace = true;
|
||
i++;
|
||
continue;
|
||
}
|
||
braceCount++;
|
||
} else if (char === '}') {
|
||
if (foundFirstBrace && braceCount === 0) {
|
||
return content.substring(startPos, i);
|
||
}
|
||
braceCount--;
|
||
}
|
||
}
|
||
|
||
i++;
|
||
}
|
||
|
||
return content.substring(startPos);
|
||
}
|
||
|
||
/**
|
||
* 生成服务层参数定义
|
||
*/
|
||
generateServiceParameters(parameters) {
|
||
if (!parameters || parameters.length === 0) return '';
|
||
|
||
return parameters.map(param => {
|
||
const defaultValue = param.defaultValue ? ` = ${param.defaultValue.replace(/'/g, '"').replace(/^"([^"]*)"$/, '"$1"')}` : '';
|
||
return `${param.name}: ${param.type}${defaultValue}`;
|
||
}).join(', ');
|
||
}
|
||
|
||
/**
|
||
* 清理和验证生成的TypeScript代码
|
||
*/
|
||
cleanAndValidateTypeScriptCode(code) {
|
||
let cleanedCode = code;
|
||
|
||
// 移除PHP语法残留
|
||
cleanedCode = cleanedCode
|
||
// 移除PHP注释语法
|
||
.replace(/\/\*\*\s*\*\s*@param\s+\$[a-zA-Z_][a-zA-Z0-9_]*\s+[^\n]*\n/g, '')
|
||
.replace(/\/\*\*\s*\*\s*@return\s+[^\n]*\n/g, '')
|
||
.replace(/\/\*\*\s*\*\s*@throws\s+[^\n]*\n/g, '')
|
||
|
||
// 修复PHP方法声明残留
|
||
.replace(/public\s+function\s+/g, 'async ')
|
||
.replace(/private\s+function\s+/g, 'private async ')
|
||
.replace(/protected\s+function\s+/g, 'protected async ')
|
||
|
||
// 修复PHP变量声明
|
||
.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)\s*=/g, 'const $1 =')
|
||
|
||
// 修复PHP数组语法
|
||
.replace(/array\s*\(\s*\)/g, '[]')
|
||
.replace(/array\s*\(/g, '[')
|
||
.replace(/\)\s*;/g, '];')
|
||
|
||
// 修复PHP字符串拼接
|
||
.replace(/\.\s*=/g, ' += ')
|
||
.replace(/\.\s*['"]/g, ' + \'')
|
||
|
||
// 修复PHP条件语句
|
||
.replace(/if\s*\(\s*([^)]+)\s*\)\s*\{/g, 'if ($1) {')
|
||
.replace(/else\s*\{/g, '} else {')
|
||
|
||
// 修复PHP异常处理
|
||
.replace(/throw\s+new\s+CommonException\s*\(/g, 'throw new BadRequestException(')
|
||
.replace(/throw\s+new\s+Exception\s*\(/g, 'throw new BadRequestException(')
|
||
|
||
// 修复PHP函数调用
|
||
.replace(/array_merge\s*\(/g, 'Object.assign(')
|
||
.replace(/strpos\s*\(/g, 'String.prototype.indexOf.call(')
|
||
.replace(/empty\s*\(/g, '!')
|
||
.replace(/isset\s*\(/g, 'typeof ')
|
||
.replace(/is_null\s*\(/g, '=== null')
|
||
|
||
// 修复方括号错误 - 只修复函数调用中的方括号,不修复数组语法
|
||
.replace(/\(([^)]+)\]/g, '($1)')
|
||
// 移除错误的替换规则,避免破坏数组语法
|
||
// .replace(/(\w+)\]/g, '$1)') // 这个规则会破坏数组语法
|
||
// 移除这些错误的替换规则,避免破坏数组语法
|
||
// .replace(/\]\s*;/g, ');')
|
||
// .replace(/\]\s*\)/g, '))')
|
||
// .replace(/\]\s*\{/g, ') {')
|
||
// .replace(/\]\s*,/g, '),')
|
||
|
||
// 修复数组语法中的方括号错误
|
||
.replace(/\[\s*\(\s*([^)]+)\s*\)\s*\]/g, '[$1]')
|
||
.replace(/\[\s*\(\s*([^)]+)\s*\)\s*\)/g, '[$1]')
|
||
|
||
// 修复数组元素中的方括号错误
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*'([^']+)'\s*\)\s*\)/g, "['$1', '$2']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*(\d+)\s*\)\s*\)/g, "['$1', $2]")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*""\s*\)\s*\)/g, "['$1', '']")
|
||
|
||
// 修复数组元素中的圆括号错误 - 更精确的匹配
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*'([^']+)'\s*\)\s*\)/g, "['$1', '$2']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*(\d+)\s*\)\s*\)/g, "['$1', $2]")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*""\s*\)\s*\)/g, "['$1', '']")
|
||
|
||
// 修复数组元素中的圆括号错误 - 处理空字符串(单引号和双引号)
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
.replace(/\[\s*\(\s*\"([^\"]+)\",\s*\"\"\s*\)\s*\)/g, '["$1", ""]')
|
||
.replace(/\[\s*\(\s*\"([^\"]+)\",\s*0\s*\)\s*\)/g, '["$1", 0]')
|
||
|
||
// 修复数组语法中的方括号错误 - 直接修复
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
|
||
// 修复数组语法中的方括号错误 - 处理所有情况
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
|
||
// 修复数组语法中的方括号错误 - 最终修复
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*''\s*\)\s*\)/g, "['$1', '']")
|
||
.replace(/\[\s*\(\s*'([^']+)',\s*0\s*\)\s*\)/g, "['$1', 0]")
|
||
.replace(/is_array\s*\(/g, 'Array.isArray(')
|
||
.replace(/is_string\s*\(/g, 'typeof ')
|
||
.replace(/is_numeric\s*\(/g, '!isNaN(')
|
||
|
||
// 修复PHP对象访问
|
||
.replace(/->/g, '.')
|
||
.replace(/::/g, '.')
|
||
|
||
// 修复PHP空值合并
|
||
.replace(/\?\?/g, '||')
|
||
|
||
// 修复PHP数组访问
|
||
.replace(/\['([^']+)'\]/g, '.$1')
|
||
.replace(/\["([^"]+)"\]/g, '.$1')
|
||
|
||
// 修复PHP类型声明
|
||
.replace(/:\s*array/g, ': any[]')
|
||
.replace(/:\s*string/g, ': string')
|
||
.replace(/:\s*int/g, ': number')
|
||
.replace(/:\s*float/g, ': number')
|
||
.replace(/:\s*bool/g, ': boolean')
|
||
|
||
// 移除PHP语法残留
|
||
.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, '$1')
|
||
|
||
// 修复方法体格式
|
||
.replace(/\{\s*\}/g, '{\n // 待实现\n }')
|
||
.replace(/\{\s*return\s+this;\s*\}/g, '{\n return this;\n }');
|
||
|
||
// 修复严重的语法错误
|
||
cleanedCode = this.fixCriticalSyntaxErrors(cleanedCode);
|
||
|
||
// 验证TypeScript语法
|
||
const validationErrors = this.validateTypeScriptSyntax(cleanedCode);
|
||
if (validationErrors.length > 0) {
|
||
console.warn('⚠️ TypeScript语法警告:', validationErrors);
|
||
}
|
||
|
||
return cleanedCode;
|
||
}
|
||
|
||
/**
|
||
* 修复严重的语法错误
|
||
*/
|
||
fixCriticalSyntaxErrors(code) {
|
||
return code
|
||
// 修复不完整的类结构
|
||
.replace(/export class \w+ \{[^}]*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的构造函数
|
||
.replace(/constructor\([^)]*\)\s*\{\s*\/\/ 待实现\s*\}\s*\}\s*super\([^)]*\)\s*;\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*super\([^)]*\)\s*;\s*\}\s*$/, ' super(repository);\n }');
|
||
})
|
||
|
||
// 修复不完整的方法体
|
||
.replace(/async \w+\([^)]*\)\s*\{\s*\/\/ 待实现\s*\}\s*\}\s*try\s*\{/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*try\s*\{/, ' {\n try {');
|
||
})
|
||
|
||
// 修复不完整的try-catch块
|
||
.replace(/try\s*\{\s*\/\/ 待实现\s*\}\s*\}\s*catch\s*\([^)]*\)\s*\{/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*catch\s*\([^)]*\)\s*\{/, ' // 待实现\n } catch (error) {');
|
||
})
|
||
|
||
// 修复不完整的异常处理
|
||
.replace(/throw new BusinessException\('[^']*',\s*error\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/error\]\s*;\s*\}\s*\}\s*$/, 'error);\n }\n }');
|
||
})
|
||
|
||
// 修复不完整的import语句
|
||
.replace(/import\s*\{\s*\/\/ 待实现\s*\}\s*\}\s*from/gm, (match) => {
|
||
return match.replace(/\{\s*\/\/ 待实现\s*\}\s*\}\s*from/, '{\n } from');
|
||
})
|
||
|
||
// 修复不完整的装饰器
|
||
.replace(/@\w+\([^)]*\)\s*\{\s*\/\/ 待实现\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的数组语法
|
||
.replace(/\[\s*\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的对象语法
|
||
.replace(/\{\s*\}\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的字符串
|
||
.replace(/'[^']*\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\]\s*;\s*\}\s*\}\s*$/, ';\n }');
|
||
})
|
||
|
||
// 修复不完整的括号
|
||
.replace(/\(\s*\)\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的方括号
|
||
.replace(/\[\s*\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的尖括号
|
||
.replace(/<\s*>\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\}\s*\}\s*$/, '}');
|
||
})
|
||
|
||
// 修复不完整的注释
|
||
.replace(/\/\/[^\n]*\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\]\s*;\s*\}\s*\}\s*$/, ';\n }');
|
||
})
|
||
|
||
// 修复不完整的多行注释
|
||
.replace(/\/\*[\s\S]*?\*\/\s*\]\s*;\s*\}\s*\}\s*$/gm, (match) => {
|
||
return match.replace(/\]\s*;\s*\}\s*\}\s*$/, ';\n }');
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 验证TypeScript语法
|
||
*/
|
||
validateTypeScriptSyntax(code) {
|
||
const errors = [];
|
||
|
||
// 检查常见语法错误
|
||
if (code.includes('=>')) {
|
||
errors.push('发现PHP数组语法 => 未转换');
|
||
}
|
||
if (code.includes('??')) {
|
||
errors.push('发现PHP空值合并 ?? 未转换');
|
||
}
|
||
if (code.includes('::')) {
|
||
errors.push('发现PHP静态访问 :: 未转换');
|
||
}
|
||
if (code.includes('->')) {
|
||
errors.push('发现PHP对象访问 -> 未转换');
|
||
}
|
||
if (code.includes('$')) {
|
||
errors.push('发现PHP变量 $ 未转换');
|
||
}
|
||
if (code.includes('array(')) {
|
||
errors.push('发现PHP数组语法 array() 未转换');
|
||
}
|
||
if (code.includes('public function') || code.includes('private function') || code.includes('protected function')) {
|
||
errors.push('发现PHP方法声明未转换');
|
||
}
|
||
|
||
// 检查严重的语法错误
|
||
if (code.includes(']') && !code.includes('[')) {
|
||
errors.push('发现不完整的方括号 ]');
|
||
}
|
||
if (code.includes('}') && !code.includes('{')) {
|
||
errors.push('发现不完整的大括号 }');
|
||
}
|
||
|
||
// 检查括号匹配
|
||
const openBraces = (code.match(/\{/g) || []).length;
|
||
const closeBraces = (code.match(/\}/g) || []).length;
|
||
if (openBraces !== closeBraces) {
|
||
errors.push(`大括号不匹配: 开括号${openBraces}个, 闭括号${closeBraces}个`);
|
||
}
|
||
|
||
const openBrackets = (code.match(/\[/g) || []).length;
|
||
const closeBrackets = (code.match(/\]/g) || []).length;
|
||
if (openBrackets !== closeBrackets) {
|
||
errors.push(`方括号不匹配: 开括号${openBrackets}个, 闭括号${closeBrackets}个`);
|
||
}
|
||
if (code.includes('// 待实现')) {
|
||
errors.push('发现未实现的方法体');
|
||
}
|
||
|
||
return errors;
|
||
}
|
||
}
|
||
|
||
module.exports = BusinessLogicConverter;
|