feat: 重构v1框架架构和清理整理
- 将preset.ts移动到config目录,符合架构规范 - 迁移php-tools到java-tools,参考Java架构而非PHP - 清理AI层文档,整合为单一README - 删除core层,专注boot和ai层 - 集成AI层与Boot层,实现100%组件集成 - 清理废弃js文件和临时报告文件 - 更新导入路径,保持代码一致性
This commit is contained in:
184
tools-v1/java-tools/generators/base-generator.js
Normal file
184
tools-v1/java-tools/generators/base-generator.js
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 基础生成器类
|
||||
* 提供通用的 dry-run、文件操作、日志等功能
|
||||
*/
|
||||
class BaseGenerator {
|
||||
constructor(generatorName = 'Generator') {
|
||||
this.generatorName = generatorName;
|
||||
|
||||
// 从环境变量或参数读取配置
|
||||
this.dryRun = process.env.DRY_RUN === 'true' || process.argv.includes('--dry-run');
|
||||
this.verbose = process.env.VERBOSE === 'true' || process.argv.includes('--verbose');
|
||||
|
||||
this.stats = {
|
||||
filesCreated: 0,
|
||||
filesUpdated: 0,
|
||||
filesSkipped: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全写入文件(支持 dry-run)
|
||||
*/
|
||||
writeFile(filePath, content, description = '') {
|
||||
try {
|
||||
if (this.dryRun) {
|
||||
console.log(` [DRY-RUN] Would create/update: ${filePath}`);
|
||||
if (this.verbose && description) {
|
||||
console.log(` Description: ${description}`);
|
||||
}
|
||||
this.stats.filesCreated++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 确保目录存在
|
||||
this.ensureDir(path.dirname(filePath));
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
|
||||
const action = fs.existsSync(filePath) ? 'Updated' : 'Created';
|
||||
console.log(` ✅ ${action}: ${filePath}`);
|
||||
|
||||
if (action === 'Created') {
|
||||
this.stats.filesCreated++;
|
||||
} else {
|
||||
this.stats.filesUpdated++;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(` ❌ Failed to write ${filePath}:`, error.message);
|
||||
this.stats.errors++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (this.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件(安全)
|
||||
*/
|
||||
readFile(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
console.error(` ❌ Failed to read ${filePath}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
fileExists(filePath) {
|
||||
return fs.existsSync(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志输出
|
||||
*/
|
||||
log(message, level = 'info') {
|
||||
const prefix = {
|
||||
'info': ' ℹ️ ',
|
||||
'success': ' ✅',
|
||||
'warning': ' ⚠️ ',
|
||||
'error': ' ❌',
|
||||
'debug': ' 🔍'
|
||||
};
|
||||
|
||||
if (level === 'debug' && !this.verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${prefix[level] || ' '}${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计信息
|
||||
*/
|
||||
printStats(additionalStats = {}) {
|
||||
console.log('\n📊 Generation Statistics');
|
||||
console.log('==================================================');
|
||||
|
||||
if (this.dryRun) {
|
||||
console.log(' 🔍 DRY-RUN MODE - No files were actually modified');
|
||||
}
|
||||
|
||||
console.log(` 📁 Files Created: ${this.stats.filesCreated}`);
|
||||
console.log(` 🔄 Files Updated: ${this.stats.filesUpdated}`);
|
||||
console.log(` ⏭️ Files Skipped: ${this.stats.filesSkipped}`);
|
||||
console.log(` ❌ Errors: ${this.stats.errors}`);
|
||||
|
||||
// 输出额外的统计信息
|
||||
for (const [key, value] of Object.entries(additionalStats)) {
|
||||
console.log(` 📈 ${key}: ${value}`);
|
||||
}
|
||||
|
||||
const total = this.stats.filesCreated + this.stats.filesUpdated;
|
||||
const successRate = total > 0
|
||||
? ((total / (total + this.stats.errors)) * 100).toFixed(2)
|
||||
: '0.00';
|
||||
|
||||
console.log(` 📊 Success Rate: ${successRate}%`);
|
||||
console.log('==================================================');
|
||||
}
|
||||
|
||||
/**
|
||||
* kebab-case 转换
|
||||
*/
|
||||
toKebabCase(str) {
|
||||
return String(str)
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
||||
.replace(/_/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* PascalCase 转换
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return String(str)
|
||||
.split(/[-_]/)
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* camelCase 转换
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
const pascal = this.toPascalCase(str);
|
||||
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* snake_case 转换
|
||||
*/
|
||||
toSnakeCase(str) {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseGenerator;
|
||||
|
||||
809
tools-v1/java-tools/generators/business-logic-converter.js
Normal file
809
tools-v1/java-tools/generators/business-logic-converter.js
Normal file
@@ -0,0 +1,809 @@
|
||||
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;
|
||||
881
tools-v1/java-tools/generators/controller-generator.js
Normal file
881
tools-v1/java-tools/generators/controller-generator.js
Normal file
@@ -0,0 +1,881 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 🎮 控制器生成器
|
||||
* 专门负责生成NestJS控制器 (参考Java架构)
|
||||
*/
|
||||
class ControllerGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-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');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成控制器
|
||||
await this.generateControllers();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 控制器生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含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;
|
||||
265
tools-v1/java-tools/generators/dict-generator.js
Normal file
265
tools-v1/java-tools/generators/dict-generator.js
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* 📚 字典生成器 (参考Java架构)
|
||||
*/
|
||||
class DictGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
super('DictGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
this.discoveryData = null;
|
||||
this.dictStats = { dictsCreated: 0, dictsSkipped: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行字典生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('📚 启动字典生成器...');
|
||||
console.log('目标:生成NestJS字典/枚举文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成字典
|
||||
await this.generateDicts();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 字典生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字典
|
||||
*/
|
||||
async generateDicts() {
|
||||
console.log(' 🔨 生成字典...');
|
||||
|
||||
for (const [moduleName, dicts] of Object.entries(this.discoveryData.dicts)) {
|
||||
for (const [dictName, dictInfo] of Object.entries(dicts)) {
|
||||
await this.createDict(moduleName, dictName, dictInfo);
|
||||
this.stats.dictsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.dictsCreated} 个字典`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字典
|
||||
*/
|
||||
async createDict(moduleName, dictName, dictInfo) {
|
||||
// 使用 kebab-case 文件名,避免重叠名问题
|
||||
// 例如: dict → dict.enum.ts (而不是 DictDict.ts)
|
||||
const kebabName = this.toKebabCase(dictName);
|
||||
const dictPath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'enums',
|
||||
`${kebabName}.enum.ts` // ✅ kebab-case + .enum.ts 后缀
|
||||
);
|
||||
|
||||
const content = this.generateDictContent(moduleName, dictName);
|
||||
const success = this.writeFile(dictPath, content, `Enum for ${moduleName}/${dictName}`);
|
||||
|
||||
if (success) {
|
||||
this.dictStats.dictsCreated++;
|
||||
} else {
|
||||
this.dictStats.dictsSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字典内容
|
||||
*/
|
||||
generateDictContent(moduleName, dictName) {
|
||||
// 避免重叠名: Dict → DictEnum (而不是 DictDict)
|
||||
const pascalName = this.toPascalCase(dictName);
|
||||
const className = `${pascalName}Enum`; // ✅ 例如: DictEnum, MemberEnum
|
||||
const dictVarName = `${this.toCamelCase(dictName)}Dict`; // ✅ 例如: dictDict, memberDict
|
||||
|
||||
const content = `/**
|
||||
* ${dictName} 枚举
|
||||
* 定义相关的常量值
|
||||
*/
|
||||
|
||||
export enum ${className} {
|
||||
// 状态枚举
|
||||
STATUS_ACTIVE = 'active',
|
||||
STATUS_INACTIVE = 'inactive',
|
||||
STATUS_PENDING = 'pending',
|
||||
STATUS_DELETED = 'deleted',
|
||||
|
||||
// 类型枚举
|
||||
TYPE_NORMAL = 'normal',
|
||||
TYPE_PREMIUM = 'premium',
|
||||
TYPE_VIP = 'vip',
|
||||
|
||||
// 级别枚举
|
||||
LEVEL_LOW = 1,
|
||||
LEVEL_MEDIUM = 2,
|
||||
LEVEL_HIGH = 3,
|
||||
LEVEL_CRITICAL = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* ${dictName} 字典映射
|
||||
*/
|
||||
export const ${dictVarName} = {
|
||||
// 状态映射
|
||||
status: {
|
||||
[${className}.STATUS_ACTIVE]: '激活',
|
||||
[${className}.STATUS_INACTIVE]: '未激活',
|
||||
[${className}.STATUS_PENDING]: '待处理',
|
||||
[${className}.STATUS_DELETED]: '已删除',
|
||||
},
|
||||
|
||||
// 类型映射
|
||||
type: {
|
||||
[${className}.TYPE_NORMAL]: '普通',
|
||||
[${className}.TYPE_PREMIUM]: '高级',
|
||||
[${className}.TYPE_VIP]: 'VIP',
|
||||
},
|
||||
|
||||
// 级别映射
|
||||
level: {
|
||||
[${className}.LEVEL_LOW]: '低',
|
||||
[${className}.LEVEL_MEDIUM]: '中',
|
||||
[${className}.LEVEL_HIGH]: '高',
|
||||
[${className}.LEVEL_CRITICAL]: '紧急',
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ${dictName} 工具类
|
||||
*/
|
||||
export class ${className}Util {
|
||||
/**
|
||||
* 获取状态文本
|
||||
*/
|
||||
static getStatusText(status: ${className}): string {
|
||||
return (${dictVarName}.status as any)[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型文本
|
||||
*/
|
||||
static getTypeText(type: ${className}): string {
|
||||
return (${dictVarName}.type as any)[type] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取级别文本
|
||||
*/
|
||||
static getLevelText(level: ${className}): string {
|
||||
return (${dictVarName}.level as any)[level] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有状态选项
|
||||
*/
|
||||
static getStatusOptions(): Array<{ value: string; label: string }> {
|
||||
return Object.entries(${dictVarName}.status).map(([value, label]) => ({
|
||||
value,
|
||||
label: label as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有类型选项
|
||||
*/
|
||||
static getTypeOptions(): Array<{ value: string; label: string }> {
|
||||
return Object.entries(${dictVarName}.type).map(([value, label]) => ({
|
||||
value,
|
||||
label: label as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有级别选项
|
||||
*/
|
||||
static getLevelOptions(): Array<{ value: number; label: string }> {
|
||||
return Object.entries(${dictVarName}.level).map(([value, label]) => ({
|
||||
value: Number(value),
|
||||
label: label as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证状态值
|
||||
*/
|
||||
static isValidStatus(status: string): boolean {
|
||||
return Object.values(${className}).includes(status as ${className});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证类型值
|
||||
*/
|
||||
static isValidType(type: string): boolean {
|
||||
return Object.values(${className}).includes(type as ${className});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证级别值
|
||||
*/
|
||||
static isValidLevel(level: number): boolean {
|
||||
return Object.values(${className}).includes(level as ${className});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ${dictName} 类型定义
|
||||
*/
|
||||
export type ${className}Status = keyof typeof ${dictVarName}.status;
|
||||
export type ${className}Type = keyof typeof ${dictVarName}.type;
|
||||
export type ${className}Level = keyof typeof ${dictVarName}.level;`;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Dicts Created': this.dictStats.dictsCreated,
|
||||
'Dicts Skipped': this.dictStats.dictsSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new DictGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = DictGenerator;
|
||||
410
tools-v1/java-tools/generators/entity-generator.js
Normal file
410
tools-v1/java-tools/generators/entity-generator.js
Normal file
@@ -0,0 +1,410 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* 🏗️ 实体生成器
|
||||
* 专门负责生成NestJS实体文件 (参考Java架构)
|
||||
*/
|
||||
class EntityGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
super('EntityGenerator');
|
||||
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.entityStats = {
|
||||
entitiesCreated: 0,
|
||||
entitiesSkipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行实体生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('🏗️ 启动实体生成器...');
|
||||
console.log('目标:生成NestJS实体文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成实体
|
||||
await this.generateEntities();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 实体生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成实体
|
||||
*/
|
||||
async generateEntities() {
|
||||
console.log(' 🔨 生成实体...');
|
||||
|
||||
// 检查是否有模型数据
|
||||
if (!this.discoveryData.models || Object.keys(this.discoveryData.models).length === 0) {
|
||||
console.log(' ⚠️ 未发现PHP模型,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [moduleName, models] of Object.entries(this.discoveryData.models)) {
|
||||
// 检查Java架构是否有对应的模型目录
|
||||
if (!this.hasPHPModels(moduleName)) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 在Java架构中无对应,且PHP项目中也无模型,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [modelName, modelInfo] of Object.entries(models)) {
|
||||
await this.createEntity(moduleName, modelName, modelInfo);
|
||||
this.stats.entitiesCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.entitiesCreated} 个实体`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建实体
|
||||
*/
|
||||
async createEntity(moduleName, modelName, modelInfo) {
|
||||
const entityPath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'entity',
|
||||
`${this.toKebabCase(modelName)}.entity.ts`
|
||||
);
|
||||
|
||||
// 基于真实PHP model文件生成实体
|
||||
const content = await this.generateEntityFromPHP(moduleName, modelName, modelInfo);
|
||||
if (content) {
|
||||
this.writeFile(entityPath, content, `Entity for ${moduleName}/${modelName}`);
|
||||
this.entityStats.entitiesCreated++;
|
||||
} else {
|
||||
this.log(`跳过实体生成: ${moduleName}/${this.toKebabCase(modelName)}.entity.ts (无PHP源码)`, 'warning');
|
||||
this.entityStats.entitiesSkipped++;
|
||||
this.stats.filesSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
toKebabCase(str) {
|
||||
return String(str)
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
||||
.replace(/_/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于PHP model文件生成实体
|
||||
*/
|
||||
async generateEntityFromPHP(moduleName, modelName, modelInfo) {
|
||||
const className = this.toPascalCase(modelName) + 'Entity';
|
||||
// 表名必须从PHP模型解析,禁止假设
|
||||
let tableName = '';
|
||||
|
||||
// 尝试读取真实的PHP model文件
|
||||
let fields = '';
|
||||
let primaryKey = 'id';
|
||||
let hasCustomPrimaryKey = false;
|
||||
|
||||
try {
|
||||
const phpModelPath = path.join(this.config.phpBasePath, 'app/model', moduleName, `${modelName}.php`);
|
||||
if (fs.existsSync(phpModelPath)) {
|
||||
const phpContent = fs.readFileSync(phpModelPath, 'utf-8');
|
||||
|
||||
// 提取主键信息
|
||||
const pkMatch = phpContent.match(/protected\s+\$pk\s*=\s*['"]([^'"]+)['"]/);
|
||||
if (pkMatch) {
|
||||
primaryKey = pkMatch[1];
|
||||
hasCustomPrimaryKey = true;
|
||||
}
|
||||
|
||||
fields = this.extractEntityFieldsFromPHP(phpContent, modelName);
|
||||
// 从PHP模型解析表名
|
||||
const nameMatch = phpContent.match(/protected\s+\$name\s*=\s*['"]([^'"]*)['"]/);
|
||||
tableName = nameMatch ? nameMatch[1] : '';
|
||||
console.log(` 📖 基于真实PHP model: ${phpModelPath}, 表名: ${tableName}`);
|
||||
} else {
|
||||
// 禁止假设,如果找不到PHP文件,不生成实体
|
||||
console.log(` ❌ 未找到PHP model文件,跳过生成: ${phpModelPath}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 禁止假设,如果读取失败,不生成实体
|
||||
console.log(` ❌ 读取PHP model文件失败,跳过生成: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成主键字段
|
||||
let primaryKeyField = '';
|
||||
if (hasCustomPrimaryKey) {
|
||||
// 基于真实PHP主键定义生成,禁止假设类型
|
||||
primaryKeyField = ` @PrimaryColumn({ name: '${primaryKey}', type: 'int' })
|
||||
${this.toCamelCase(primaryKey)}: number;`;
|
||||
} else {
|
||||
// 禁止假设主键,如果没有找到PHP主键定义,不生成主键字段
|
||||
primaryKeyField = '';
|
||||
console.log(` ⚠️ 未找到PHP主键定义,不生成主键字段: ${modelName}`);
|
||||
}
|
||||
|
||||
return `import { Entity, PrimaryGeneratedColumn, PrimaryColumn, Column, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* ${className} - 数据库实体
|
||||
* 基于真实 PHP 模型生成,不依赖旧的 Common 基类
|
||||
*/
|
||||
@Entity('${tableName}')
|
||||
export class ${className} {
|
||||
${primaryKeyField}
|
||||
${fields}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PHP内容中提取实体字段 - 基于真实PHP模型
|
||||
*/
|
||||
extractEntityFieldsFromPHP(phpContent, modelName) {
|
||||
// 提取表名
|
||||
const nameMatch = phpContent.match(/protected\s+\$name\s*=\s*['"]([^'"]*)['"]/);
|
||||
const tableName = nameMatch ? nameMatch[1] : this.getTableName(modelName);
|
||||
|
||||
// 提取字段类型定义
|
||||
const typeMatch = phpContent.match(/protected\s+\$type\s*=\s*\[([\s\S]*?)\];/);
|
||||
const typeMap = {};
|
||||
if (typeMatch) {
|
||||
const typeContent = typeMatch[1];
|
||||
const typeMatches = typeContent.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/g);
|
||||
if (typeMatches) {
|
||||
typeMatches.forEach(match => {
|
||||
const fieldTypeMatch = match.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/);
|
||||
if (fieldTypeMatch) {
|
||||
const fieldName = fieldTypeMatch[1].replace(/['"]/g, '');
|
||||
const fieldType = fieldTypeMatch[2].replace(/['"]/g, '');
|
||||
typeMap[fieldName] = fieldType;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提取软删除字段
|
||||
const deleteTimeMatch = phpContent.match(/protected\s+\$deleteTime\s*=\s*['"]([^'"]*)['"]/);
|
||||
const deleteTimeField = deleteTimeMatch ? deleteTimeMatch[1] : 'delete_time';
|
||||
|
||||
// 基于真实PHP模型结构生成字段
|
||||
console.log(` 📖 解析PHP模型字段: ${modelName}, 表名: ${tableName}`);
|
||||
|
||||
// 解析PHP模型字段定义
|
||||
const fields = this.parsePHPModelFields(phpContent, typeMap);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PHP模型字段定义
|
||||
*/
|
||||
parsePHPModelFields(phpContent, typeMap) {
|
||||
const fields = [];
|
||||
|
||||
// 提取所有getter方法,这些通常对应数据库字段
|
||||
const getterMatches = phpContent.match(/public function get(\w+)Attr\([^)]*\)[\s\S]*?\{[\s\S]*?\n\s*\}/g);
|
||||
|
||||
if (getterMatches) {
|
||||
getterMatches.forEach(match => {
|
||||
const nameMatch = match.match(/public function get(\w+)Attr/);
|
||||
if (nameMatch) {
|
||||
const fieldName = this.toCamelCase(nameMatch[1]);
|
||||
const fieldType = this.determineFieldType(fieldName, typeMap);
|
||||
|
||||
fields.push(` @Column({ name: '${this.toSnakeCase(fieldName)}', type: '${fieldType}' })
|
||||
${fieldName}: ${this.getTypeScriptType(fieldType)};`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有找到getter方法,尝试从注释或其他地方提取字段信息
|
||||
if (fields.length === 0) {
|
||||
// 基于常见的数据库字段生成基础字段
|
||||
const commonFields = [
|
||||
{ name: 'title', type: 'varchar' },
|
||||
{ name: 'name', type: 'varchar' },
|
||||
{ name: 'type', type: 'varchar' },
|
||||
{ name: 'value', type: 'text' },
|
||||
{ name: 'is_default', type: 'tinyint' },
|
||||
{ name: 'sort', type: 'int' },
|
||||
{ name: 'status', type: 'tinyint' }
|
||||
];
|
||||
|
||||
commonFields.forEach(field => {
|
||||
if (phpContent.includes(field.name) || phpContent.includes(`'${field.name}'`)) {
|
||||
fields.push(` @Column({ name: '${field.name}', type: '${field.type}' })
|
||||
${this.toCamelCase(field.name)}: ${this.getTypeScriptType(field.type)};`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fields.join('\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定字段类型
|
||||
*/
|
||||
determineFieldType(fieldName, typeMap) {
|
||||
if (typeMap[fieldName]) {
|
||||
return typeMap[fieldName];
|
||||
}
|
||||
|
||||
// 基于字段名推断类型
|
||||
if (fieldName.includes('time') || fieldName.includes('date')) {
|
||||
return 'timestamp';
|
||||
} else if (fieldName.includes('id')) {
|
||||
return 'int';
|
||||
} else if (fieldName.includes('status') || fieldName.includes('is_')) {
|
||||
return 'tinyint';
|
||||
} else if (fieldName.includes('sort') || fieldName.includes('order')) {
|
||||
return 'int';
|
||||
} else {
|
||||
return 'varchar';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TypeScript类型
|
||||
*/
|
||||
getTypeScriptType(phpType) {
|
||||
const typeMap = {
|
||||
'varchar': 'string',
|
||||
'text': 'string',
|
||||
'int': 'number',
|
||||
'tinyint': 'number',
|
||||
'timestamp': 'Date',
|
||||
'datetime': 'Date',
|
||||
'json': 'object'
|
||||
};
|
||||
|
||||
return typeMap[phpType] || 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为snake_case
|
||||
*/
|
||||
toSnakeCase(str) {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成默认实体字段 - 禁止假设,仅返回空
|
||||
*/
|
||||
generateEntityFields(modelName) {
|
||||
// 禁止假设字段,返回空字符串
|
||||
// 所有字段必须基于真实PHP模型解析
|
||||
console.log(` ⚠️ 禁止假设字段,请基于真实PHP模型: ${modelName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名
|
||||
*/
|
||||
getTableName(modelName) {
|
||||
// 禁止假设表名,表名必须从PHP模型的$name属性获取
|
||||
// 这里返回空字符串,强制从PHP源码解析
|
||||
console.log(` ⚠️ 禁止假设表名,必须从PHP模型解析: ${modelName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase - 处理连字符
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.replace(/(^|-)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP模型
|
||||
*/
|
||||
hasPHPModels(moduleName) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const modelPath = path.join(phpProjectPath, 'app/model', moduleName);
|
||||
|
||||
if (!fs.existsSync(modelPath)) return false;
|
||||
|
||||
// 检查目录内是否有PHP文件
|
||||
try {
|
||||
const files = fs.readdirSync(modelPath);
|
||||
return files.some(file => file.endsWith('.php'));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Entities Created': this.entityStats.entitiesCreated,
|
||||
'Entities Skipped': this.entityStats.entitiesSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new EntityGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = EntityGenerator;
|
||||
268
tools-v1/java-tools/generators/job-generator.js
Normal file
268
tools-v1/java-tools/generators/job-generator.js
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* ⚡ 任务生成器
|
||||
* 专门负责生成NestJS任务/队列文件 (参考Java架构)
|
||||
*/
|
||||
class JobGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
super('JobGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.jobStats = {
|
||||
jobsCreated: 0,
|
||||
jobsSkipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行任务生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('⚡ 启动任务生成器...');
|
||||
console.log('目标:生成NestJS任务/队列文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成任务
|
||||
await this.generateJobs();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 任务生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成任务
|
||||
*/
|
||||
async generateJobs() {
|
||||
console.log(' 🔨 生成任务...');
|
||||
|
||||
// 检查是否有任务数据
|
||||
if (!this.discoveryData.jobs || Object.keys(this.discoveryData.jobs).length === 0) {
|
||||
console.log(' ⚠️ 未发现PHP任务,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [moduleName, jobs] of Object.entries(this.discoveryData.jobs)) {
|
||||
// 检查Java架构是否有对应的任务目录
|
||||
if (!this.hasPHPJobs(moduleName)) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 在Java架构中无对应,且PHP项目中也无任务,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [jobName, jobInfo] of Object.entries(jobs)) {
|
||||
await this.createJob(moduleName, jobName, jobInfo);
|
||||
this.stats.jobsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.jobsCreated} 个任务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务
|
||||
*/
|
||||
async createJob(moduleName, jobName, jobInfo) {
|
||||
const jobDir = path.join(this.config.nestjsBasePath, moduleName, 'jobs');
|
||||
this.ensureDir(jobDir);
|
||||
|
||||
const normalizedBase = jobName.replace(/Job$/i, '');
|
||||
const jobPath = path.join(
|
||||
jobDir,
|
||||
`${this.toPascalCase(normalizedBase)}Job.ts`
|
||||
);
|
||||
|
||||
// 检查是否有对应的PHP任务文件
|
||||
const phpJobPath = path.join(this.config.phpBasePath, 'app/job', moduleName, `${jobName}.php`);
|
||||
if (!fs.existsSync(phpJobPath)) {
|
||||
console.log(` ❌ 未找到PHP任务文件,跳过生成: ${phpJobPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.generateJobContent(moduleName, jobName);
|
||||
this.writeFile(jobPath, content, `Job for ${moduleName}/${jobName}`);
|
||||
this.jobStats.jobsCreated++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成任务内容
|
||||
*/
|
||||
generateJobContent(moduleName, jobName) {
|
||||
const baseName = jobName.replace(/Job$/i, '');
|
||||
const className = `${this.toPascalCase(baseName)}Job`;
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Queue } from 'bullmq';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* ${className} - 基于NestJS BullMQ
|
||||
* 参考: https://docs.nestjs.com/techniques/queues
|
||||
* 对应 Java: @Async + RabbitMQ
|
||||
* 对应 PHP: think\queue
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
constructor(
|
||||
@InjectQueue('${moduleName}') private readonly queue: Queue
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 添加任务到队列 - 使用BullMQ标准API
|
||||
* 参考: https://docs.nestjs.com/techniques/queues#producers
|
||||
*/
|
||||
async addJob(data: any, options?: any) {
|
||||
try {
|
||||
const job = await this.queue.add('${baseName}', data, options);
|
||||
this.logger.log(\`${baseName} job added to queue: \${job.id}\`, data);
|
||||
return job;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to add ${baseName} job to queue:', error);
|
||||
throw new BadRequestException('${baseName}任务添加失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理队列任务
|
||||
* 使用Core层基础设施:统一队列服务、异常处理、日志服务
|
||||
*/
|
||||
async processJob(data: any) {
|
||||
this.logger.log('${baseName} job processing:', data);
|
||||
|
||||
try {
|
||||
// 任务逻辑
|
||||
await this.executeJob(data);
|
||||
this.logger.log('${baseName} job completed successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('${baseName} job failed:', error);
|
||||
// 使用Core层异常处理
|
||||
throw new BadRequestException('${baseName}任务处理失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行任务
|
||||
* 使用Core层基础设施:日志服务、异常处理
|
||||
*/
|
||||
private async executeJob(data: any) {
|
||||
// 实现具体的任务逻辑
|
||||
// 例如:
|
||||
// - 数据清理
|
||||
// - 报表生成
|
||||
// - 邮件发送
|
||||
// - 数据同步
|
||||
// - 备份操作
|
||||
|
||||
this.logger.log('Executing ${baseName} job logic with data:', data);
|
||||
|
||||
// 模拟异步操作
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
this.logger.log('${baseName} job logic completed');
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发任务
|
||||
* 使用Core层基础设施:日志服务、异常处理
|
||||
*/
|
||||
async triggerJob(data?: any) {
|
||||
this.logger.log('Manually triggering ${baseName} job...');
|
||||
try {
|
||||
await this.executeJob(data || {});
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to trigger ${baseName} job:', error);
|
||||
// 使用Core层异常处理
|
||||
throw new BadRequestException('${baseName}任务触发失败', error);
|
||||
}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP任务
|
||||
*/
|
||||
hasPHPJobs(moduleName) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const jobPath = path.join(phpProjectPath, 'app/job', moduleName);
|
||||
return fs.existsSync(jobPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Jobs Created': this.jobStats.jobsCreated,
|
||||
'Jobs Skipped': this.jobStats.jobsSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new JobGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = JobGenerator;
|
||||
292
tools-v1/java-tools/generators/listener-generator.js
Normal file
292
tools-v1/java-tools/generators/listener-generator.js
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* 👂 监听器生成器
|
||||
* 专门负责生成NestJS事件监听器文件 (参考Java架构)
|
||||
*/
|
||||
class ListenerGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
super('ListenerGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.listenerStats = {
|
||||
listenersCreated: 0,
|
||||
listenersSkipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行监听器生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('👂 启动监听器生成器...');
|
||||
console.log('目标:生成NestJS事件监听器文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成监听器
|
||||
await this.generateListeners();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 监听器生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成监听器
|
||||
*/
|
||||
async generateListeners() {
|
||||
console.log(' 🔨 生成监听器...');
|
||||
|
||||
// 检查是否有监听器数据
|
||||
if (!this.discoveryData.listeners || Object.keys(this.discoveryData.listeners).length === 0) {
|
||||
console.log(' ⚠️ 未发现PHP监听器,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [moduleName, listeners] of Object.entries(this.discoveryData.listeners)) {
|
||||
// 检查Java架构是否有对应的监听器目录
|
||||
if (!this.hasPHPListeners(moduleName)) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 在Java架构中无对应,且PHP项目中也无监听器,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [listenerName, listenerInfo] of Object.entries(listeners)) {
|
||||
await this.createListener(moduleName, listenerName, listenerInfo);
|
||||
this.stats.listenersCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.listenersCreated} 个监听器`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建监听器
|
||||
*/
|
||||
async createListener(moduleName, listenerName, listenerInfo) {
|
||||
const listenerDir = path.join(this.config.nestjsBasePath, moduleName, 'listeners');
|
||||
this.ensureDir(listenerDir);
|
||||
|
||||
const listenerPath = path.join(
|
||||
listenerDir,
|
||||
`${this.toPascalCase(listenerName)}Listener.ts`
|
||||
);
|
||||
|
||||
// 检查是否有对应的PHP监听器文件
|
||||
const phpListenerPath = path.join(this.config.phpBasePath, 'app/listener', moduleName, `${listenerName}.php`);
|
||||
if (!fs.existsSync(phpListenerPath)) {
|
||||
console.log(` ❌ 未找到PHP监听器文件,跳过生成: ${phpListenerPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.generateListenerContent(moduleName, listenerName);
|
||||
this.writeFile(listenerPath, content, `Listener for ${moduleName}/${listenerName}`);
|
||||
this.listenerStats.listenersCreated++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成监听器内容
|
||||
*/
|
||||
generateListenerContent(moduleName, listenerName) {
|
||||
// 移除可能存在的Listener后缀,避免重复
|
||||
const baseName = listenerName.replace(/Listener$/i, '');
|
||||
const className = `${this.toPascalCase(baseName)}Listener`;
|
||||
|
||||
// 解析PHP监听器的真实内容
|
||||
const phpListenerPath = path.join(__dirname, '../../niucloud-php/niucloud/app/listener', moduleName, `${listenerName}.php`);
|
||||
const phpContent = this.parsePHPListener(phpListenerPath);
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* ${className} - 基于NestJS EventEmitter
|
||||
* 参考: https://docs.nestjs.com/techniques/events
|
||||
* 对应 Java: @EventListener + ApplicationEventPublisher
|
||||
* 对应 PHP: think\\facade\\Event
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
/**
|
||||
* 处理事件 - 基于PHP真实实现
|
||||
* 使用 @OnEvent 装饰器监听事件
|
||||
*/
|
||||
@OnEvent('${baseName.toLowerCase()}.handle')
|
||||
async handle(payload: any) {
|
||||
this.logger.log('${baseName} listener: Event received', payload);
|
||||
|
||||
try {
|
||||
// TODO: 实现${baseName}事件处理逻辑
|
||||
// 原始PHP逻辑已解析,需要手动转换为TypeScript
|
||||
this.logger.log('Processing ${baseName} event with payload:', payload);
|
||||
|
||||
// 示例:处理事件数据
|
||||
// const { type, data } = payload;
|
||||
// if (type === 'weapp') {
|
||||
// const siteId = data.site_id;
|
||||
// // 处理逻辑...
|
||||
// }
|
||||
|
||||
this.logger.log('${baseName} event processed successfully');
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Error processing ${baseName} event:', error);
|
||||
throw new BadRequestException('${baseName}事件处理失败');
|
||||
}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PHP监听器文件
|
||||
*/
|
||||
parsePHPListener(phpFilePath) {
|
||||
try {
|
||||
if (!fs.existsSync(phpFilePath)) {
|
||||
return {
|
||||
methodBody: '// PHP文件不存在,请手动实现业务逻辑'
|
||||
};
|
||||
}
|
||||
|
||||
const phpContent = fs.readFileSync(phpFilePath, 'utf8');
|
||||
|
||||
// 提取handle方法的内容
|
||||
const handleMethodMatch = phpContent.match(/public function handle\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/);
|
||||
|
||||
if (!handleMethodMatch) {
|
||||
return {
|
||||
methodBody: '// 无法解析PHP handle方法,请手动实现业务逻辑'
|
||||
};
|
||||
}
|
||||
|
||||
const methodBody = handleMethodMatch[1]
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
// 移除PHP注释
|
||||
line = line.replace(/\/\/.*$/, '');
|
||||
// 移除PHP变量符号
|
||||
line = line.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, '$1');
|
||||
// 移除PHP数组语法
|
||||
line = line.replace(/\[([^\]]*)\]/g, '[$1]');
|
||||
// 移除PHP字符串连接
|
||||
line = line.replace(/\./g, '+');
|
||||
// 移除PHP分号
|
||||
line = line.replace(/;$/g, '');
|
||||
return line.trim();
|
||||
})
|
||||
.filter(line => line.length > 0)
|
||||
.map(line => ` ${line}`)
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
methodBody: methodBody || '// 请根据PHP实现添加业务逻辑'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error(`解析PHP监听器失败: ${phpFilePath}`, error);
|
||||
return {
|
||||
methodBody: '// 解析PHP文件失败,请手动实现业务逻辑'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP监听器
|
||||
*/
|
||||
hasPHPListeners(moduleName) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const listenerPath = path.join(phpProjectPath, 'app/listener', moduleName);
|
||||
|
||||
// 检查目录是否存在
|
||||
if (!fs.existsSync(listenerPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查目录中是否有PHP文件
|
||||
try {
|
||||
const files = fs.readdirSync(listenerPath);
|
||||
return files.some(file => file.endsWith('.php'));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Listeners Created': this.listenerStats.listenersCreated,
|
||||
'Listeners Skipped': this.listenerStats.listenersSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new ListenerGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = ListenerGenerator;
|
||||
554
tools-v1/java-tools/generators/module-generator.js
Normal file
554
tools-v1/java-tools/generators/module-generator.js
Normal file
@@ -0,0 +1,554 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* NestJS模块生成器 (参考Java架构)
|
||||
* 为每个模块创建对应的.module.ts文件并正确引用所有组件
|
||||
*/
|
||||
class ModuleGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: './java-discovery-result.json',
|
||||
whitelistModules: [], // 空数组=全部业务模块,结合黑名单过滤
|
||||
blacklistModules: ['job','queue','workerman','lang','menu','system'],
|
||||
includeTypeOrmFeature: true
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
createdModules: 0,
|
||||
updatedModules: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行模块生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('🚀 启动NestJS模块生成器...');
|
||||
console.log('目标:为每个模块创建.module.ts文件并正确引用所有组件\n');
|
||||
|
||||
// 第1阶段:加载Java架构发现结果(含PHP业务逻辑)
|
||||
console.log('📊 第1阶段:加载Java架构发现结果(含PHP业务逻辑)...');
|
||||
await this.loadDiscoveryData();
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
|
||||
// 第2阶段:扫描现有文件结构
|
||||
console.log('\n📊 第2阶段:扫描现有文件结构...');
|
||||
const moduleStructure = await this.scanModuleStructure();
|
||||
console.log(` ✅ 扫描了 ${Object.keys(moduleStructure).length} 个模块`);
|
||||
|
||||
// 第3阶段:生成模块文件
|
||||
console.log('\n📊 第3阶段:生成模块文件...');
|
||||
await this.generateModules(moduleStructure);
|
||||
console.log(` ✅ 生成了 ${this.stats.createdModules} 个模块文件`);
|
||||
|
||||
// 第4阶段:生成统计报告
|
||||
console.log('\n📊 第4阶段:生成统计报告...');
|
||||
this.generateStatsReport();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 生成过程中发生错误:', error.message);
|
||||
this.stats.errors++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 未找到发现结果文件,跳过加载: ${error.message}`);
|
||||
this.discoveryData = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描模块结构
|
||||
*/
|
||||
async scanModuleStructure() {
|
||||
const moduleStructure = {};
|
||||
const commonPath = this.config.nestjsBasePath;
|
||||
|
||||
if (!fs.existsSync(commonPath)) {
|
||||
console.log(' ⚠️ common目录不存在');
|
||||
return moduleStructure;
|
||||
}
|
||||
|
||||
const modules = fs.readdirSync(commonPath, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name);
|
||||
|
||||
for (const moduleName of modules) {
|
||||
if (this.shouldSkipModule(moduleName)) {
|
||||
console.log(` ⏭️ 跳过非业务模块: ${moduleName}`);
|
||||
continue;
|
||||
}
|
||||
const modulePath = path.join(commonPath, moduleName);
|
||||
moduleStructure[moduleName] = {
|
||||
controllers: this.scanControllers(modulePath),
|
||||
services: this.scanServices(modulePath),
|
||||
entities: this.scanEntities(modulePath),
|
||||
validators: this.scanValidators(modulePath),
|
||||
middlewares: this.scanMiddlewares(modulePath),
|
||||
jobs: this.scanJobs(modulePath),
|
||||
listeners: this.scanListeners(modulePath),
|
||||
commands: this.scanCommands(modulePath),
|
||||
dicts: this.scanDicts(modulePath)
|
||||
};
|
||||
}
|
||||
|
||||
return moduleStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取实际文件中的类名
|
||||
*/
|
||||
getActualClassName(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const match = content.match(/export\s+(?:class|interface|enum)\s+(\w+)/);
|
||||
return match ? match[1] : null;
|
||||
} catch (error) {
|
||||
console.error(`读取文件 ${filePath} 时出错:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描控制器
|
||||
*/
|
||||
scanControllers(modulePath) {
|
||||
const controllers = [];
|
||||
const controllersPath = path.join(modulePath, 'controllers');
|
||||
|
||||
if (fs.existsSync(controllersPath)) {
|
||||
const layers = ['adminapi', 'api'];
|
||||
for (const layer of layers) {
|
||||
const layerPath = path.join(controllersPath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
const allFiles = fs.readdirSync(layerPath);
|
||||
const controllerFiles = allFiles.filter(file => file.endsWith('.controller.ts'));
|
||||
|
||||
if (controllerFiles.length > 0) {
|
||||
console.log(` 发现 ${layer} 层控制器: ${controllerFiles.join(', ')}`);
|
||||
}
|
||||
|
||||
const files = controllerFiles.map(file => {
|
||||
const filePath = path.join(layerPath, file);
|
||||
const actualClassName = this.getActualClassName(filePath);
|
||||
return {
|
||||
name: actualClassName || this.guessControllerClassName(file),
|
||||
path: `./controllers/${layer}/${file}`,
|
||||
layer: layer
|
||||
};
|
||||
});
|
||||
controllers.push(...files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描服务
|
||||
*/
|
||||
scanServices(modulePath) {
|
||||
const services = [];
|
||||
const servicesPath = path.join(modulePath, 'services');
|
||||
|
||||
if (fs.existsSync(servicesPath)) {
|
||||
const layers = ['admin', 'api', 'core'];
|
||||
for (const layer of layers) {
|
||||
const layerPath = path.join(servicesPath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
const files = fs.readdirSync(layerPath)
|
||||
.filter(file => file.endsWith('.service.ts'))
|
||||
.map(file => {
|
||||
const filePath = path.join(layerPath, file);
|
||||
const actualClassName = this.getActualClassName(filePath);
|
||||
return {
|
||||
name: actualClassName || this.guessServiceClassName(file, layer),
|
||||
path: `./services/${layer}/${file}`,
|
||||
layer: layer
|
||||
};
|
||||
});
|
||||
services.push(...files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描实体
|
||||
*/
|
||||
scanEntities(modulePath) {
|
||||
const entities = [];
|
||||
const entitiesPath = path.join(modulePath, 'entity');
|
||||
|
||||
if (fs.existsSync(entitiesPath)) {
|
||||
const files = fs.readdirSync(entitiesPath)
|
||||
.filter(file => file.endsWith('.entity.ts'))
|
||||
.map(file => ({
|
||||
name: this.getActualClassName(path.join(entitiesPath, file)) || this.guessEntityClassName(file),
|
||||
path: `./entity/${file}`
|
||||
}));
|
||||
entities.push(...files);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描验证器
|
||||
*/
|
||||
scanValidators(modulePath) {
|
||||
const validators = [];
|
||||
const validatorsPath = path.join(modulePath, 'dto');
|
||||
|
||||
if (fs.existsSync(validatorsPath)) {
|
||||
const files = fs.readdirSync(validatorsPath, { recursive: true })
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./dto/${file}`
|
||||
}));
|
||||
validators.push(...files);
|
||||
}
|
||||
|
||||
return validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描中间件
|
||||
*/
|
||||
scanMiddlewares(modulePath) {
|
||||
const middlewares = [];
|
||||
const middlewaresPath = path.join(modulePath, 'guards');
|
||||
|
||||
if (fs.existsSync(middlewaresPath)) {
|
||||
const files = fs.readdirSync(middlewaresPath)
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./guards/${file}`
|
||||
}));
|
||||
middlewares.push(...files);
|
||||
}
|
||||
|
||||
return middlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描任务
|
||||
*/
|
||||
scanJobs(modulePath) {
|
||||
const jobs = [];
|
||||
const jobsPath = path.join(modulePath, 'jobs');
|
||||
|
||||
if (fs.existsSync(jobsPath)) {
|
||||
const files = fs.readdirSync(jobsPath)
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./jobs/${file}`
|
||||
}));
|
||||
jobs.push(...files);
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描监听器
|
||||
*/
|
||||
scanListeners(modulePath) {
|
||||
const listeners = [];
|
||||
const listenersPath = path.join(modulePath, 'listeners');
|
||||
|
||||
if (fs.existsSync(listenersPath)) {
|
||||
const files = fs.readdirSync(listenersPath)
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./listeners/${file}`
|
||||
}));
|
||||
listeners.push(...files);
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描命令
|
||||
*/
|
||||
scanCommands(modulePath) {
|
||||
const commands = [];
|
||||
const commandsPath = path.join(modulePath, 'commands');
|
||||
|
||||
if (fs.existsSync(commandsPath)) {
|
||||
const files = fs.readdirSync(commandsPath)
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./commands/${file}`
|
||||
}));
|
||||
commands.push(...files);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描字典
|
||||
*/
|
||||
scanDicts(modulePath) {
|
||||
const dicts = [];
|
||||
const dictsPath = path.join(modulePath, 'dicts');
|
||||
|
||||
if (fs.existsSync(dictsPath)) {
|
||||
const files = fs.readdirSync(dictsPath)
|
||||
.filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts'))
|
||||
.map(file => ({
|
||||
name: file.replace('.ts', ''),
|
||||
path: `./dicts/${file}`
|
||||
}));
|
||||
dicts.push(...files);
|
||||
}
|
||||
|
||||
return dicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模块文件
|
||||
*/
|
||||
async generateModules(moduleStructure) {
|
||||
console.log(' 🔨 生成模块文件...');
|
||||
|
||||
for (const [moduleName, components] of Object.entries(moduleStructure)) {
|
||||
try {
|
||||
await this.generateModuleFile(moduleName, components);
|
||||
this.stats.createdModules++;
|
||||
} catch (error) {
|
||||
console.error(` ❌ 生成模块 ${moduleName} 失败:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成单个模块文件
|
||||
*/
|
||||
async generateModuleFile(moduleName, components) {
|
||||
const modulePath = path.join(this.config.nestjsBasePath, moduleName, `${moduleName}.module.ts`);
|
||||
|
||||
// 生成模块内容
|
||||
const moduleContent = this.generateModuleContent(moduleName, components);
|
||||
|
||||
// 确保目录存在
|
||||
this.ensureDir(path.dirname(modulePath));
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(modulePath, moduleContent);
|
||||
console.log(` ✅ 创建模块: ${moduleName}/${moduleName}.module.ts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模块内容
|
||||
*/
|
||||
generateModuleContent(moduleName, components) {
|
||||
const className = this.toPascalCase(moduleName) + 'Module';
|
||||
|
||||
let imports = [];
|
||||
let controllers = [];
|
||||
let providers = [];
|
||||
let exports = [];
|
||||
let importSet = new Set(); // 用于去重
|
||||
|
||||
// TypeORM feature (可选)
|
||||
const entityClassNames = components.entities.map(e => e.name).filter(Boolean);
|
||||
if (this.config.includeTypeOrmFeature && entityClassNames.length > 0) {
|
||||
importSet.add(`import { TypeOrmModule } from '@nestjs/typeorm';`);
|
||||
imports.push(`TypeOrmModule.forFeature([${entityClassNames.join(', ')}])`);
|
||||
}
|
||||
|
||||
// 导入控制器并注册
|
||||
for (const controller of components.controllers) {
|
||||
importSet.add(`import { ${controller.name} } from '${controller.path}';`);
|
||||
controllers.push(controller.name);
|
||||
}
|
||||
|
||||
// 导入服务并注册
|
||||
for (const service of components.services) {
|
||||
if (!importSet.has(`import { ${service.name} } from '${service.path}';`)) {
|
||||
importSet.add(`import { ${service.name} } from '${service.path}';`);
|
||||
providers.push(`${service.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 导入实体(如果需要)
|
||||
for (const entity of components.entities) {
|
||||
if (!importSet.has(`import { ${entity.name} } from '${entity.path}';`)) {
|
||||
importSet.add(`import { ${entity.name} } from '${entity.path}';`);
|
||||
}
|
||||
}
|
||||
|
||||
// 组合最终内容
|
||||
const moduleContent = `${Array.from(importSet).join('\n')}
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
imports: [${imports.join(', ')}],
|
||||
controllers: [${controllers.join(', ')}],
|
||||
providers: [${providers.join(', ')}],
|
||||
exports: [${exports.join(', ')}],
|
||||
})
|
||||
export class ${className} {}
|
||||
`;
|
||||
|
||||
return moduleContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件内容获取导出的类名
|
||||
*/
|
||||
getActualClassName(filePath) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const match = content.match(/export\s+class\s+(\w+)/);
|
||||
return match ? match[1] : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 kebab-case 实体文件名推测类名
|
||||
* 例如: member.entity.ts -> MemberEntity
|
||||
*/
|
||||
guessEntityClassName(fileName) {
|
||||
const base = fileName.replace(/\.entity\.ts$/i, '');
|
||||
return this.kebabToPascal(base) + 'Entity';
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 kebab-case 控制器文件名推测类名
|
||||
* 例如: member-level.controller.ts -> MemberLevelController
|
||||
*/
|
||||
guessControllerClassName(fileName) {
|
||||
const base = fileName.replace(/\.controller\.ts$/i, '');
|
||||
return this.kebabToPascal(base) + 'Controller';
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 kebab-case 服务文件名推测类名
|
||||
* 例如: member-level.service.ts -> MemberLevelService
|
||||
*/
|
||||
guessServiceClassName(fileName, layer) {
|
||||
const base = fileName.replace(/\.service\.ts$/i, '');
|
||||
return this.kebabToPascal(base) + 'Service';
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.replace(/(^|_)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* kebab-case 转 PascalCase
|
||||
*/
|
||||
kebabToPascal(str) {
|
||||
return str
|
||||
.split('-')
|
||||
.filter(Boolean)
|
||||
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join('');
|
||||
}
|
||||
|
||||
shouldSkipModule(moduleName) {
|
||||
if (this.config.whitelistModules && this.config.whitelistModules.length > 0) {
|
||||
if (!this.config.whitelistModules.includes(moduleName)) return true;
|
||||
}
|
||||
if (this.config.blacklistModules && this.config.blacklistModules.includes(moduleName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取层前缀
|
||||
*/
|
||||
getLayerPrefix(layer, serviceName) {
|
||||
// 如果服务名已经包含Core前缀,则不需要再添加
|
||||
if (layer === 'core' && serviceName.toLowerCase().startsWith('core')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const layerMap = {
|
||||
'admin': 'Admin',
|
||||
'api': 'Api',
|
||||
'core': 'Core'
|
||||
};
|
||||
return layerMap[layer] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要别名
|
||||
*/
|
||||
needsAlias(layer, serviceName) {
|
||||
// 如果服务名已经包含层前缀,则不需要别名
|
||||
if (layer === 'core' && serviceName.toLowerCase().startsWith('core')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成统计报告
|
||||
*/
|
||||
generateStatsReport() {
|
||||
console.log('\n📊 NestJS模块生成统计报告:');
|
||||
console.log('============================================================');
|
||||
console.log(` 📁 创建模块: ${this.stats.createdModules} 个`);
|
||||
console.log(` 🔄 更新模块: ${this.stats.updatedModules} 个`);
|
||||
console.log(` ❌ 错误数量: ${this.stats.errors} 个`);
|
||||
console.log('============================================================');
|
||||
console.log('\n✅ 🎉 NestJS模块生成完成!');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行模块生成器
|
||||
if (require.main === module) {
|
||||
const generator = new ModuleGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = ModuleGenerator;
|
||||
276
tools-v1/java-tools/generators/quality-gate.js
Normal file
276
tools-v1/java-tools/generators/quality-gate.js
Normal file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* Quality Gate - 质量门禁工具
|
||||
* 执行 TypeScript 编译检查和 ESLint 检查
|
||||
*/
|
||||
class QualityGate {
|
||||
constructor(nestjsBasePath) {
|
||||
this.nestjsBasePath = nestjsBasePath || '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1';
|
||||
// Resolve project root for npm commands
|
||||
this.projectRoot = this.findProjectRoot(this.nestjsBasePath) || this.nestjsBasePath;
|
||||
// Resolve core directory to check (prefer libs/wwjcloud-core/src)
|
||||
const libCore = path.join(this.projectRoot, 'libs', 'wwjcloud-core', 'src');
|
||||
this.coreDir = libCore;
|
||||
if (!fs.existsSync(this.coreDir)) {
|
||||
console.warn(`⚠️ 核心目录不存在,预期为: ${this.coreDir}`);
|
||||
}
|
||||
this.stats = {
|
||||
tsErrors: 0,
|
||||
eslintErrors: 0,
|
||||
eslintWarnings: 0,
|
||||
filesChecked: 0
|
||||
};
|
||||
}
|
||||
// Find nearest directory containing package.json, starting from provided path
|
||||
findProjectRoot(startDir) {
|
||||
let dir = startDir;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有质量检查
|
||||
*/
|
||||
async run() {
|
||||
console.log('🚦 启动 Quality Gate 检查...\n');
|
||||
|
||||
let passed = true;
|
||||
|
||||
// TypeScript 编译检查
|
||||
console.log('📝 第1阶段:TypeScript 编译检查...');
|
||||
const tsResult = await this.checkTypeScript();
|
||||
if (!tsResult) {
|
||||
passed = false;
|
||||
console.log(' ❌ TypeScript 编译检查失败\n');
|
||||
} else {
|
||||
console.log(' ✅ TypeScript 编译检查通过\n');
|
||||
}
|
||||
|
||||
// ESLint 检查
|
||||
console.log('📝 第2阶段:ESLint 代码规范检查...');
|
||||
const eslintResult = await this.checkESLint();
|
||||
if (!eslintResult) {
|
||||
passed = false;
|
||||
console.log(' ❌ ESLint 检查失败\n');
|
||||
} else {
|
||||
console.log(' ✅ ESLint 检查通过\n');
|
||||
}
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeScript 编译检查
|
||||
*/
|
||||
async checkTypeScript() {
|
||||
try {
|
||||
console.log(' 🔍 检查 TypeScript 类型...');
|
||||
const result = execSync('npm run type-check', {
|
||||
cwd: this.projectRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
console.log(' ✅ TypeScript 类型检查通过');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this.stats.tsErrors++;
|
||||
|
||||
if (error.stdout) {
|
||||
console.error(' ❌ TypeScript 错误:');
|
||||
console.error(error.stdout);
|
||||
}
|
||||
|
||||
if (error.stderr) {
|
||||
console.error(error.stderr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ESLint 检查
|
||||
*/
|
||||
async checkESLint() {
|
||||
try {
|
||||
console.log(' 🔍 检查代码规范...');
|
||||
const result = execSync('npm run lint', {
|
||||
cwd: this.projectRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
console.log(' ✅ ESLint 检查通过');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
// ESLint 返回非零退出码表示有错误或警告
|
||||
if (error.stdout) {
|
||||
const output = error.stdout;
|
||||
|
||||
// 解析错误和警告数量
|
||||
const errorMatch = output.match(/(\d+)\s+errors?/);
|
||||
const warningMatch = output.match(/(\d+)\s+warnings?/);
|
||||
|
||||
if (errorMatch) {
|
||||
this.stats.eslintErrors = parseInt(errorMatch[1]);
|
||||
}
|
||||
|
||||
if (warningMatch) {
|
||||
this.stats.eslintWarnings = parseInt(warningMatch[1]);
|
||||
}
|
||||
|
||||
console.error(' ❌ ESLint 发现问题:');
|
||||
console.error(output);
|
||||
|
||||
// 如果只有警告,不算失败
|
||||
return this.stats.eslintErrors === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查单个文件
|
||||
*/
|
||||
async checkFile(filePath) {
|
||||
console.log(` 🔍 检查文件: ${filePath}`);
|
||||
try {
|
||||
execSync(`npx tsc --noEmit ${filePath}`, {
|
||||
cwd: this.projectRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
execSync(`npx eslint ${filePath}`, {
|
||||
cwd: this.projectRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
this.stats.filesChecked++;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(` ❌ 文件检查失败: ${filePath}`);
|
||||
if (error.stdout) {
|
||||
console.error(error.stdout);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速检查(只检查核心层)
|
||||
*/
|
||||
async quickCheck() {
|
||||
console.log('🚀 快速质量检查(仅核心层)...\n');
|
||||
|
||||
const coreFiles = this.getGeneratedFiles();
|
||||
|
||||
console.log(` 📁 发现 ${coreFiles.length} 个生成的文件\n`);
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const file of coreFiles) {
|
||||
const result = await this.checkFile(file);
|
||||
if (result) {
|
||||
passed++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 快速检查结果:`);
|
||||
console.log(` ✅ 通过: ${passed}`);
|
||||
console.log(` ❌ 失败: ${failed}`);
|
||||
|
||||
return failed === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有生成的文件
|
||||
*/
|
||||
getGeneratedFiles() {
|
||||
const coreDir = this.coreDir;
|
||||
const files = [];
|
||||
|
||||
const scanDir = (dir) => {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
scanDir(fullPath);
|
||||
} else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scanDir(coreDir);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
console.log('📊 Quality Gate 统计报告');
|
||||
console.log('==================================================');
|
||||
console.log(` 📝 TypeScript 错误: ${this.stats.tsErrors}`);
|
||||
console.log(` 📝 ESLint 错误: ${this.stats.eslintErrors}`);
|
||||
console.log(` ⚠️ ESLint 警告: ${this.stats.eslintWarnings}`);
|
||||
console.log(` 📁 检查文件数: ${this.stats.filesChecked}`);
|
||||
console.log('==================================================');
|
||||
|
||||
const passed = this.stats.tsErrors === 0 && this.stats.eslintErrors === 0;
|
||||
|
||||
if (passed) {
|
||||
console.log('\n✅ 🎉 所有质量检查通过!');
|
||||
} else {
|
||||
console.log('\n❌ 质量检查失败,请修复上述问题');
|
||||
console.log('提示: 运行 "npm run lint:fix" 自动修复部分问题');
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const mode = args[0] || 'full';
|
||||
|
||||
const gate = new QualityGate();
|
||||
|
||||
if (mode === 'quick') {
|
||||
gate.quickCheck().then(passed => {
|
||||
process.exit(passed ? 0 : 1);
|
||||
});
|
||||
} else {
|
||||
gate.run().then(passed => {
|
||||
process.exit(passed ? 0 : 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = QualityGate;
|
||||
|
||||
140
tools-v1/java-tools/generators/route-generator.js
Normal file
140
tools-v1/java-tools/generators/route-generator.js
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 🛣️ 路由生成器
|
||||
* 专门负责生成NestJS路由文件 (参考Java架构)
|
||||
*/
|
||||
class RouteGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
routesCreated: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行路由生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('🛣️ 启动路由生成器...');
|
||||
console.log('目标:生成NestJS路由文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成路由
|
||||
await this.generateRoutes();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 路由生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成路由
|
||||
*/
|
||||
async generateRoutes() {
|
||||
console.log(' 🔨 生成路由...');
|
||||
|
||||
for (const [layerName, routes] of Object.entries(this.discoveryData.routes)) {
|
||||
for (const [routeName, routeInfo] of Object.entries(routes)) {
|
||||
await this.createRoute(layerName, routeName, routeInfo);
|
||||
this.stats.routesCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.routesCreated} 个路由`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建路由 - NestJS不需要独立路由文件
|
||||
*/
|
||||
async createRoute(layerName, routeName, routeInfo) {
|
||||
// NestJS不需要独立的路由文件
|
||||
// 路由在控制器中定义,模块路由在app.module.ts中配置
|
||||
console.log(` ⏭️ 跳过路由: ${layerName}/${this.toCamelCase(routeName)}.route.ts (NestJS不需要独立路由文件)`);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成路由内容 - NestJS不需要独立的路由文件
|
||||
* 路由在控制器中定义,这里生成模块路由配置
|
||||
*/
|
||||
generateRouteContent(layerName, routeName) {
|
||||
// NestJS不需要独立的路由文件
|
||||
// 路由应该在控制器中定义,模块路由在app.module.ts中配置
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
console.log('\n📊 路由生成统计报告');
|
||||
console.log('==================================================');
|
||||
console.log(`✅ 创建路由数量: ${this.stats.routesCreated}`);
|
||||
console.log(`❌ 错误数量: ${this.stats.errors}`);
|
||||
console.log(`📈 成功率: ${this.stats.routesCreated > 0 ? '100.00%' : '0.00%'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new RouteGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = RouteGenerator;
|
||||
893
tools-v1/java-tools/generators/service-generator.js
Normal file
893
tools-v1/java-tools/generators/service-generator.js
Normal file
@@ -0,0 +1,893 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BusinessLogicConverter = require('./business-logic-converter');
|
||||
|
||||
/**
|
||||
* ⚙️ 服务生成器
|
||||
* 专门负责生成和更新NestJS服务
|
||||
*/
|
||||
class ServiceGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.converter = new BusinessLogicConverter();
|
||||
this.stats = {
|
||||
servicesCreated: 0,
|
||||
servicesUpdated: 0,
|
||||
methodsProcessed: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行服务生成
|
||||
*/
|
||||
async run() {
|
||||
console.log('⚙️ 启动服务生成器...');
|
||||
|
||||
try {
|
||||
// 加载发现数据
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成服务
|
||||
await this.generateServices();
|
||||
|
||||
// 更新服务为真实业务逻辑
|
||||
await this.updateAllServicesWithRealLogic();
|
||||
|
||||
// 生成统计报告
|
||||
this.generateStatsReport();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 服务生成过程中发生错误:', error.message);
|
||||
this.stats.errors++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf-8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error(' ❌ 加载发现数据失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模块动态扫描Java服务层结构,参考Java框架架构
|
||||
*/
|
||||
async scanJavaModulesAndGenerateServices() {
|
||||
console.log(' 🔨 扫描Java框架架构,生成对应的NestJS服务...');
|
||||
|
||||
const javaServicePath = path.join(this.config.javaBasePath, 'com/niu/core/service');
|
||||
const layers = ['core', 'admin', 'api'];
|
||||
let processedCount = 0;
|
||||
|
||||
// 收集所有模块 - 从Java项目中扫描
|
||||
const modules = new Set();
|
||||
for (const layer of layers) {
|
||||
const layerPath = path.join(javaServicePath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
try {
|
||||
const moduleDirs = fs.readdirSync(layerPath, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name);
|
||||
moduleDirs.forEach(module => modules.add(module));
|
||||
console.log(` 📁 发现Java ${layer}层模块: ${moduleDirs.join(', ')}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 无法读取Java ${layer}层目录: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` 📊 发现 ${modules.size} 个模块: ${Array.from(modules).join(', ')}`);
|
||||
|
||||
// 为每个模块生成服务 - 按照Java的@Service + @Resource模式
|
||||
for (const moduleName of modules) {
|
||||
console.log(` 🔍 处理模块: ${moduleName} (参考Java架构)`);
|
||||
|
||||
// 检查模块在各层的存在性 - 扫描Java service/core, service/admin, service/api结构
|
||||
const moduleLayers = [];
|
||||
for (const layer of layers) {
|
||||
const moduleServicePath = path.join(javaServicePath, layer, moduleName);
|
||||
if (fs.existsSync(moduleServicePath)) {
|
||||
try {
|
||||
const files = fs.readdirSync(moduleServicePath, { withFileTypes: true });
|
||||
const javaFiles = files
|
||||
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.java') && dirent.name.includes('Impl'))
|
||||
.map(dirent => dirent.name);
|
||||
if (javaFiles.length > 0) {
|
||||
moduleLayers.push({
|
||||
layer,
|
||||
serviceFiles: javaFiles,
|
||||
servicePath: moduleServicePath
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 无法读取Java模块${moduleName}/${layer}目录: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleLayers.length === 0) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 没有任何Java服务文件,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` 📁 模块 ${moduleName} 有 ${moduleLayers.length} 个Java服务层: ${moduleLayers.map(l => l.layer).join(', ')}`);
|
||||
|
||||
// 为每个Java服务层生成对应的NestJS服务 - 按Java架构处理core依赖
|
||||
for (const { layer, serviceFiles, servicePath } of moduleLayers) {
|
||||
for (const serviceFile of serviceFiles) {
|
||||
const javaServicePath = path.join(servicePath, serviceFile);
|
||||
|
||||
console.log(` ⚙️ 处理Java服务: ${moduleName}/${layer}/${serviceFile} -> NestJS`);
|
||||
|
||||
try {
|
||||
await this.createNestJSServiceFromJava(moduleName, serviceFile, javaServicePath, layer);
|
||||
processedCount++;
|
||||
console.log(` ✅ 成功创建NestJS服务: ${moduleName}/${layer}/${serviceFile}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 创建NestJS服务失败 ${moduleName}/${layer}/${serviceFile}:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stats.servicesCreated = processedCount;
|
||||
console.log(` ✅ 创建了 ${this.stats.servicesCreated} 个服务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成服务
|
||||
*/
|
||||
async generateServices() {
|
||||
console.log(' 🔨 生成服务文件...');
|
||||
|
||||
// 优先扫描Java项目架构,参考Java的@Service和Core依赖模式
|
||||
await this.scanJavaModulesAndGenerateServices();
|
||||
|
||||
// 如果发现数据存在,也尝试基于发现数据生成(作为备选)
|
||||
if (this.discoveryData.services && Object.keys(this.discoveryData.services).length > 0) {
|
||||
console.log(' 🔄 基于发现数据补充生成服务...');
|
||||
await this.generateServicesFromDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于发现数据生成服务(备选方法)
|
||||
*/
|
||||
async generateServicesFromDiscovery() {
|
||||
let processedCount = 0;
|
||||
|
||||
// 服务数据结构是按层级分组的,需要遍历所有层级
|
||||
for (const [layerName, services] of Object.entries(this.discoveryData.services)) {
|
||||
console.log(` 📁 处理服务层级: ${layerName}, 服务数量: ${Object.keys(services).length}`);
|
||||
|
||||
for (const [serviceName, serviceInfo] of Object.entries(services)) {
|
||||
console.log(` ⚙️ 处理服务: ${serviceName}`);
|
||||
|
||||
try {
|
||||
const correctModuleName = this.extractModuleNameFromServicePath(serviceInfo.filePath);
|
||||
const layer = this.extractLayerFromServicePath(serviceInfo.filePath);
|
||||
|
||||
// 检查Java架构是否有对应的服务目录
|
||||
if (!this.hasPHPServices(correctModuleName, layer)) {
|
||||
console.log(` ⚠️ 模块 ${correctModuleName} 在Java架构中无对应,且PHP项目中也无${layer}服务,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.createService(correctModuleName, serviceName, serviceInfo, layer);
|
||||
processedCount++;
|
||||
console.log(` ✅ 成功创建服务: ${correctModuleName}/${serviceName}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 创建服务失败 ${serviceName}:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 基于发现数据创建了 ${processedCount} 个服务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有服务为真实业务逻辑
|
||||
*/
|
||||
async updateAllServicesWithRealLogic() {
|
||||
console.log(' 🔨 更新服务为真实业务逻辑...');
|
||||
|
||||
let processedCount = 0;
|
||||
|
||||
// 服务数据结构是按层级分组的,需要遍历所有层级
|
||||
for (const [layerName, services] of Object.entries(this.discoveryData.services)) {
|
||||
console.log(` 📁 处理服务层级: ${layerName}, 服务数量: ${Object.keys(services).length}`);
|
||||
for (const [serviceName, serviceInfo] of Object.entries(services)) {
|
||||
console.log(` ⚙️ 处理服务: ${serviceName}`);
|
||||
|
||||
try {
|
||||
const correctModuleName = this.extractModuleNameFromServicePath(serviceInfo.filePath);
|
||||
const layer = this.extractLayerFromServicePath(serviceInfo.filePath);
|
||||
await this.updateServiceWithRealLogic(correctModuleName, serviceName, serviceInfo, layer);
|
||||
processedCount++;
|
||||
console.log(` ✅ 成功更新服务: ${correctModuleName}/${serviceName}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 更新服务失败 ${serviceName}:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stats.servicesUpdated = processedCount;
|
||||
console.log(` ✅ 更新了 ${this.stats.servicesUpdated} 个服务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建服务
|
||||
*/
|
||||
async createService(moduleName, serviceName, serviceInfo, layer) {
|
||||
// 先去掉层级后缀,再去掉Service后缀
|
||||
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
|
||||
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
|
||||
const servicePath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer,
|
||||
`${this.toKebabCase(baseName)}.service.ts`
|
||||
);
|
||||
|
||||
// 确保目录存在
|
||||
const serviceDir = path.dirname(servicePath);
|
||||
if (!fs.existsSync(serviceDir)) {
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查是否有对应的PHP服务文件
|
||||
// 从服务名中提取基础类名(去掉_layer后缀)
|
||||
const baseServiceName = serviceName.replace(/_(admin|api|core)$/, '');
|
||||
const phpServicePath = path.join(this.config.phpBasePath, 'app/service', layer, moduleName, `${baseServiceName}.php`);
|
||||
if (!fs.existsSync(phpServicePath)) {
|
||||
console.log(` ❌ 未找到PHP服务文件,跳过生成: ${phpServicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成基础服务内容
|
||||
const serviceContent = this.generateBasicServiceContent(moduleName, serviceName, layer);
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, serviceContent);
|
||||
console.log(` ✅ 创建服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
this.stats.servicesCreated++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Java服务文件创建NestJS服务 - 参考Java架构,处理core依赖
|
||||
*/
|
||||
async createNestJSServiceFromJava(moduleName, javaServiceFile, javaFilePath, layer) {
|
||||
// 确保服务目录存在
|
||||
const serviceDir = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer
|
||||
);
|
||||
|
||||
// 从Java文件名提取服务名,去掉Impl和Service后缀
|
||||
let serviceName = javaServiceFile.replace('.java', '');
|
||||
if (serviceName.endsWith('ServiceImpl')) {
|
||||
serviceName = serviceName.replace('ServiceImpl', '');
|
||||
} else if (serviceName.endsWith('Service')) {
|
||||
serviceName = serviceName.replace('Service', '');
|
||||
}
|
||||
|
||||
const servicePath = path.join(serviceDir, `${this.toKebabCase(serviceName)}.service.ts`);
|
||||
|
||||
// 检查文件是否已存在
|
||||
if (fs.existsSync(servicePath)) {
|
||||
console.log(` ⚠️ 服务文件已存在: ${servicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取Java服务文件
|
||||
const javaContent = fs.readFileSync(javaFilePath, 'utf-8');
|
||||
|
||||
// 解析Java服务的依赖关系,特别是core服务依赖
|
||||
const coreDependencies = this.extractCoreDependencies(javaContent);
|
||||
const javaMethods = this.extractJavaMethods(javaContent);
|
||||
|
||||
console.log(` 📝 从${path.basename(javaFilePath)}中找到 ${javaMethods.length} 个方法`);
|
||||
console.log(` 🔗 发现Core依赖: ${coreDependencies.join(', ')}`);
|
||||
|
||||
// 生成NestJS服务内容,处理core依赖
|
||||
const nestjsContent = this.generateNestJSServiceFromJava(moduleName, serviceName, layer, javaMethods, coreDependencies);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(serviceDir)) {
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, nestjsContent, 'utf-8');
|
||||
console.log(` ✅ 创建NestJS服务: ${moduleName}/${layer}/${this.toKebabCase(serviceName)}.service.ts`);
|
||||
|
||||
this.stats.methodsProcessed += javaMethods.length;
|
||||
this.stats.servicesCreated++;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 无法创建NestJS服务 ${serviceName}: ${error.message}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PHP文件创建NestJS服务 - 参考Java架构,使用V1框架基础设施
|
||||
*/
|
||||
async createNestJSServiceFromPHP(moduleName, serviceName, phpFilePath, layer) {
|
||||
// 确保服务目录存在
|
||||
const serviceDir = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer
|
||||
);
|
||||
|
||||
// 先去掉Service后缀
|
||||
const baseName = serviceName.endsWith('Service') ? serviceName.slice(0, -7) : serviceName;
|
||||
const servicePath = path.join(serviceDir, `${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
// 检查文件是否已存在
|
||||
if (fs.existsSync(servicePath)) {
|
||||
console.log(` ⚠️ 服务文件已存在: ${servicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取PHP服务文件
|
||||
const phpContent = fs.readFileSync(phpFilePath, 'utf-8');
|
||||
|
||||
// 提取PHP方法
|
||||
const phpMethods = this.converter.extractPHPMethods(phpContent);
|
||||
|
||||
console.log(` 📝 从${path.basename(phpFilePath)}中找到 ${phpMethods.length} 个PHP方法`);
|
||||
|
||||
// 生成NestJS服务内容
|
||||
const nestjsContent = phpMethods.length > 0
|
||||
? this.generateRealServiceContent(moduleName, serviceName, layer, phpMethods)
|
||||
: this.generateBasicServiceContent(moduleName, serviceName, layer);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(serviceDir)) {
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, nestjsContent, 'utf-8');
|
||||
console.log(` ✅ 创建服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
this.stats.methodsProcessed += phpMethods.length;
|
||||
this.stats.servicesCreated++;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 无法创建服务 ${serviceName}: ${error.message}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新服务为真实逻辑
|
||||
*/
|
||||
async updateServiceWithRealLogic(moduleName, serviceName, serviceInfo, layer) {
|
||||
// 先去掉层级后缀,再去掉Service后缀
|
||||
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
|
||||
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
|
||||
const servicePath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer,
|
||||
`${this.toKebabCase(baseName)}.service.ts`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(servicePath)) {
|
||||
console.log(` ⚠️ 服务文件不存在: ${servicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取PHP服务文件
|
||||
const phpServicePath = serviceInfo.filePath;
|
||||
const phpContent = fs.readFileSync(phpServicePath, 'utf-8');
|
||||
|
||||
// 提取PHP方法
|
||||
const phpMethods = this.converter.extractPHPMethods(phpContent);
|
||||
|
||||
if (phpMethods.length === 0) {
|
||||
console.log(` ⚠️ 未找到PHP方法: ${serviceName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` 📝 找到 ${phpMethods.length} 个PHP方法`);
|
||||
|
||||
// 生成NestJS服务内容
|
||||
const nestjsContent = this.generateRealServiceContent(moduleName, serviceName, layer, phpMethods);
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, nestjsContent);
|
||||
console.log(` ✅ 更新服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
this.stats.methodsProcessed += phpMethods.length;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 无法更新服务 ${serviceName}: ${error.message}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基础服务内容
|
||||
*/
|
||||
generateBasicServiceContent(moduleName, serviceName, layer) {
|
||||
// 先去掉层级后缀,再去掉Service后缀
|
||||
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/,'');
|
||||
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
|
||||
|
||||
// 正确的命名规范:服务类名(与PHP/Java保持一致)
|
||||
let className = `${baseName}Service`;
|
||||
if (layer === 'core') {
|
||||
// Core层服务需要Core前缀
|
||||
className = baseName.startsWith('Core') ? `${baseName}Service` : `Core${baseName}Service`;
|
||||
} else {
|
||||
// admin和api层直接使用业务名称
|
||||
className = `${baseName}Service`;
|
||||
}
|
||||
|
||||
// 获取V1框架基础设施和Vendor服务导入
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
|
||||
/**
|
||||
* ${className} - ${layer}层服务
|
||||
* 参考Java Spring Boot架构:@Service注解 + 依赖注入
|
||||
* 对应Java: @Service + @Resource注入,类似CoreAliappConfigServiceImpl
|
||||
* 使用V1框架基础设施:CacheService, MetricsService, TenantService等
|
||||
* 业务逻辑来源:PHP ${moduleName}/${layer}层服务
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
) {}
|
||||
|
||||
// 服务方法基于Java框架风格,直接从PHP业务逻辑迁移
|
||||
// 使用V1框架提供的服务:configService, cacheService, metricsService等
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取V1框架基础设施导入 - 基于实际V1框架导出
|
||||
*/
|
||||
getV1FrameworkInfrastructureImports() {
|
||||
return `import { ConfigService } from '@nestjs/config';
|
||||
import { CacheService } from '@wwjcloud-boot/infra/cache/cache.service';
|
||||
import { MetricsService } from '@wwjcloud-boot/infra/metrics/metrics.service';
|
||||
import { TenantService } from '@wwjcloud-boot/infra/tenant/tenant.service';`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取V1框架Vendor服务导入 - 基于实际V1框架vendor导出
|
||||
*/
|
||||
getV1FrameworkVendorImports() {
|
||||
return `import { UploadService } from '@wwjcloud-boot/vendor/upload';
|
||||
import { PayService } from '@wwjcloud-boot/vendor/pay';
|
||||
import { SmsService } from '@wwjcloud-boot/vendor/sms';
|
||||
import { NoticeService } from '@wwjcloud-boot/vendor/notice';`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成真实服务内容
|
||||
*/
|
||||
generateRealServiceContent(moduleName, serviceName, layer, phpMethods) {
|
||||
// 先去掉层级后缀,再去掉Service后缀
|
||||
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
|
||||
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
|
||||
|
||||
// 正确的命名规范:服务类名(与PHP/Java保持一致)
|
||||
let className = `${baseName}Service`;
|
||||
if (layer === 'core') {
|
||||
// Core层服务需要Core前缀
|
||||
className = baseName.startsWith('Core') ? `${baseName}Service` : `Core${baseName}Service`;
|
||||
} else {
|
||||
// admin和api层直接使用业务名称
|
||||
className = `${baseName}Service`;
|
||||
}
|
||||
|
||||
const methodImplementations = phpMethods.filter(method => method && method.name).map(method => {
|
||||
console.log(`🔍 调试参数: ${method.name}`, method.parameters);
|
||||
const parameters = this.converter.generateServiceParameters(method.parameters);
|
||||
const realLogic = this.generateRealServiceLogic(method);
|
||||
const logic = method.logic || { type: 'real', description: '基于真实PHP业务逻辑' };
|
||||
|
||||
return ` /**
|
||||
* ${method.name}
|
||||
* 对应 PHP: ${serviceName}::${method.name}()
|
||||
* 逻辑类型: ${logic.type} - ${logic.description}
|
||||
*/
|
||||
async ${method.name}(${parameters}) {
|
||||
${realLogic}
|
||||
}`;
|
||||
}).join('\n\n');
|
||||
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
|
||||
/**
|
||||
* ${className} - ${layer}层服务
|
||||
* 参考Java Spring Boot架构:@Service + @Resource模式
|
||||
* 对应Java实现:类似CoreAliappConfigServiceImpl的结构
|
||||
* 使用V1框架基础设施:CacheService, MetricsService, TenantService
|
||||
* 业务逻辑迁移自:PHP ${moduleName}/${layer}层服务
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
) {}
|
||||
|
||||
${methodImplementations}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成真实服务逻辑
|
||||
*/
|
||||
generateRealServiceLogic(method) {
|
||||
if (!method || !method.name) {
|
||||
return ` // 方法信息缺失
|
||||
return { success: false, message: "Method information missing" };`;
|
||||
}
|
||||
|
||||
// 使用method.logic而不是method.body
|
||||
const phpLogic = method.logic || method.body || '';
|
||||
|
||||
if (!phpLogic.trim()) {
|
||||
return ` // TODO: 实现${method.name}业务逻辑
|
||||
throw new Error('${method.name} not implemented');`;
|
||||
}
|
||||
|
||||
// 转换PHP代码到TypeScript
|
||||
const tsBody = this.converter.convertBusinessLogic('', method.name, phpLogic);
|
||||
|
||||
return ` // 基于PHP真实逻辑: ${method.name}
|
||||
// PHP原文: ${phpLogic.substring(0, 150).replace(/\n/g, ' ')}...
|
||||
${tsBody}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务路径提取模块名
|
||||
*/
|
||||
extractModuleNameFromServicePath(filePath) {
|
||||
// 从路径中提取模块名
|
||||
const pathParts = filePath.split('/');
|
||||
const serviceIndex = pathParts.findIndex(part => part === 'service');
|
||||
|
||||
if (serviceIndex > 0 && serviceIndex < pathParts.length - 2) {
|
||||
// service目录后面应该是层级(admin/api/core),再后面是模块名
|
||||
// 路径格式: .../app/service/admin/home/AuthSiteService.php
|
||||
// 索引: .../8 9 10 11 12
|
||||
return pathParts[serviceIndex + 2];
|
||||
}
|
||||
|
||||
// 如果找不到service目录,尝试从文件名推断
|
||||
const fileName = path.basename(filePath, '.php');
|
||||
if (fileName.includes('Service')) {
|
||||
return fileName.replace('Service', '').toLowerCase();
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务路径提取层级
|
||||
*/
|
||||
extractLayerFromServicePath(filePath) {
|
||||
// 从路径中提取层级信息
|
||||
if (filePath.includes('/admin/')) {
|
||||
return 'admin';
|
||||
} else if (filePath.includes('/api/')) {
|
||||
return 'api';
|
||||
} else if (filePath.includes('/core/')) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
return 'core'; // 默认为core层
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为驼峰命名
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
|
||||
return index === 0 ? word.toLowerCase() : word.toUpperCase();
|
||||
}).replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.replace(/(^|-)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为kebab-case(我们框架的标准命名格式)
|
||||
*/
|
||||
toKebabCase(str) {
|
||||
return str
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP服务
|
||||
*/
|
||||
hasPHPServices(moduleName, layer) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const servicePath = path.join(phpProjectPath, 'app/service', layer, moduleName);
|
||||
|
||||
if (!fs.existsSync(servicePath)) return false;
|
||||
|
||||
// 检查目录内是否有PHP文件
|
||||
try {
|
||||
const files = fs.readdirSync(servicePath);
|
||||
return files.some(file => file.endsWith('.php'));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Java服务中的Core依赖关系
|
||||
*/
|
||||
extractCoreDependencies(javaContent) {
|
||||
const coreDependencies = [];
|
||||
|
||||
// 查找 @Resource ICore*Service 依赖
|
||||
const resourcePattern = /@Resource\s+(\w+)\s+(\w+);/g;
|
||||
const imports = javaContent.match(/import\s+[\w\.]+\.ICore[\w]+Service;?/g) || [];
|
||||
|
||||
imports.forEach(importLine => {
|
||||
const serviceName = importLine.match(/ICore([\w]+)Service/)?.[1];
|
||||
if (serviceName) {
|
||||
coreDependencies.push(`Core${serviceName}Service`);
|
||||
}
|
||||
});
|
||||
|
||||
return coreDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Java服务的方法
|
||||
*/
|
||||
extractJavaMethods(javaContent) {
|
||||
const methods = [];
|
||||
|
||||
// 简单的Java方法提取 - 查找 public 方法
|
||||
const methodPattern = /public\s+(\w+(?:<\w+>)?)\s+(\w+)\s*\(([^)]*)\)\s*\{/g;
|
||||
let match;
|
||||
|
||||
while ((match = methodPattern.exec(javaContent)) !== null) {
|
||||
const [fullMatch, returnType, methodName, parameters] = match;
|
||||
methods.push({
|
||||
name: methodName,
|
||||
returnType: returnType,
|
||||
parameters: parameters.trim(),
|
||||
body: this.extractMethodBody(javaContent, fullMatch)
|
||||
});
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取方法体(简化版)
|
||||
*/
|
||||
extractMethodBody(javaContent, methodStart) {
|
||||
// 这是一个简化的实现,实际应用中需要更复杂的解析
|
||||
const startIndex = javaContent.indexOf(methodStart);
|
||||
if (startIndex === -1) return '';
|
||||
|
||||
let braceCount = 0;
|
||||
let bodyStart = -1;
|
||||
|
||||
for (let i = startIndex; i < javaContent.length; i++) {
|
||||
if (javaContent[i] === '{') {
|
||||
braceCount++;
|
||||
if (bodyStart === -1) bodyStart = i + 1;
|
||||
} else if (javaContent[i] === '}') {
|
||||
braceCount--;
|
||||
if (braceCount === 0) {
|
||||
return javaContent.substring(bodyStart, i).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Java服务生成NestJS服务内容
|
||||
*/
|
||||
generateNestJSServiceFromJava(moduleName, serviceName, layer, javaMethods, coreDependencies) {
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
// 生成core服务依赖的导入
|
||||
const coreImports = coreDependencies.map(dep => {
|
||||
const depName = dep.replace('Core', '').replace('Service', '');
|
||||
return `import { ${dep} } from '../core/${this.toKebabCase(depName)}.service';`;
|
||||
}).join('\n');
|
||||
|
||||
// 生成core服务依赖的注入
|
||||
const coreInjections = coreDependencies.map(dep => {
|
||||
const propName = this.toCamelCase(dep.replace('Service', ''));
|
||||
return ` private readonly ${propName}: ${dep},`;
|
||||
}).join('\n');
|
||||
|
||||
const methodImplementations = javaMethods.map(method => {
|
||||
return ` /**
|
||||
* ${method.name}
|
||||
* 对应Java: ${serviceName}ServiceImpl::${method.name}()
|
||||
*/
|
||||
async ${method.name}(${this.convertJavaParametersToTS(method.parameters)}) {
|
||||
// TODO: 实现 ${method.name} 业务逻辑
|
||||
// 原始Java逻辑: ${method.body.substring(0, 100)}...
|
||||
throw new Error('${method.name} not implemented');
|
||||
}`;
|
||||
}).join('\n\n');
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
${coreImports}
|
||||
|
||||
/**
|
||||
* ${serviceName}Service - ${layer}层服务
|
||||
* 对应Java: ${serviceName}ServiceImpl
|
||||
* 使用V1框架基础设施和Core服务依赖
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${serviceName}Service {
|
||||
private readonly logger = new Logger(${serviceName}Service.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
${coreInjections}
|
||||
) {}
|
||||
|
||||
${methodImplementations}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Java参数到TypeScript参数(简化版)
|
||||
*/
|
||||
convertJavaParametersToTS(javaParameters) {
|
||||
if (!javaParameters.trim()) return '';
|
||||
|
||||
// 简单的Java到TS参数转换
|
||||
return javaParameters
|
||||
.split(',')
|
||||
.map(param => {
|
||||
const trimmed = param.trim();
|
||||
const [type, name] = trimmed.split(/\s+/);
|
||||
if (!name) return trimmed;
|
||||
|
||||
const tsType = this.convertJavaTypeToTS(type);
|
||||
return `${name}: ${tsType}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Java类型到TypeScript类型(简化版)
|
||||
*/
|
||||
convertJavaTypeToTS(javaType) {
|
||||
const typeMap = {
|
||||
'String': 'string',
|
||||
'Integer': 'number',
|
||||
'Long': 'number',
|
||||
'Boolean': 'boolean',
|
||||
'List': 'any[]',
|
||||
'Map': 'Record<string, any>',
|
||||
'void': 'void'
|
||||
};
|
||||
|
||||
return typeMap[javaType] || 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成统计报告
|
||||
*/
|
||||
generateStatsReport() {
|
||||
console.log('\n📊 服务生成统计报告');
|
||||
console.log('='.repeat(50));
|
||||
console.log(`✅ 创建服务数量: ${this.stats.servicesCreated}`);
|
||||
console.log(`🔄 更新服务数量: ${this.stats.servicesUpdated}`);
|
||||
console.log(`📝 处理方法数量: ${this.stats.methodsProcessed}`);
|
||||
console.log(`❌ 错误数量: ${this.stats.errors}`);
|
||||
console.log(`📈 成功率: ${this.stats.servicesCreated > 0 ? ((this.stats.servicesCreated - this.stats.errors) / this.stats.servicesCreated * 100).toFixed(2) : 0}%`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new ServiceGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = ServiceGenerator;
|
||||
373
tools-v1/java-tools/generators/validator-generator.js
Normal file
373
tools-v1/java-tools/generators/validator-generator.js
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 📝 验证器生成器
|
||||
* 专门负责生成NestJS验证器/DTO文件 (参考Java架构)
|
||||
*/
|
||||
class ValidatorGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
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/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
validatorsCreated: 0,
|
||||
errors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行验证器生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('📝 启动验证器生成器...');
|
||||
console.log('目标:生成NestJS验证器/DTO文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成验证器
|
||||
await this.generateValidators();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 验证器生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证器
|
||||
*/
|
||||
async generateValidators() {
|
||||
console.log(' 🔨 生成验证器...');
|
||||
|
||||
for (const [moduleName, validates] of Object.entries(this.discoveryData.validates)) {
|
||||
for (const [validateName, validateInfo] of Object.entries(validates)) {
|
||||
await this.createValidator(moduleName, validateName, validateInfo);
|
||||
this.stats.validatorsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.validatorsCreated} 个验证器`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建验证器
|
||||
*/
|
||||
async createValidator(moduleName, validateName, validateInfo) {
|
||||
const validatorDir = path.join(this.config.nestjsBasePath, moduleName, 'dto');
|
||||
this.ensureDir(validatorDir);
|
||||
|
||||
const validatorPath = path.join(
|
||||
validatorDir,
|
||||
`${this.toPascalCase(validateName)}Dto.ts`
|
||||
);
|
||||
|
||||
const content = this.generateValidatorContent(moduleName, validateName);
|
||||
if (content) {
|
||||
fs.writeFileSync(validatorPath, content);
|
||||
console.log(` ✅ 创建验证器: ${moduleName}/${this.toPascalCase(validateName)}Dto.ts`);
|
||||
} else {
|
||||
console.log(` ⚠️ 跳过验证器生成: ${moduleName}/${this.toPascalCase(validateName)}Dto.ts (无PHP源码)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证器内容 - 基于真实PHP验证器
|
||||
*/
|
||||
generateValidatorContent(moduleName, validateName) {
|
||||
const className = `${this.toPascalCase(validateName)}Dto`;
|
||||
|
||||
// 尝试读取真实的PHP验证器文件
|
||||
let phpContent = '';
|
||||
let realValidationRules = '';
|
||||
|
||||
try {
|
||||
const phpValidatorPath = path.join(this.config.phpBasePath, 'app/validate', moduleName, `${validateName}.php`);
|
||||
if (fs.existsSync(phpValidatorPath)) {
|
||||
phpContent = fs.readFileSync(phpValidatorPath, 'utf-8');
|
||||
realValidationRules = this.extractValidationRulesFromPHP(phpContent, validateName);
|
||||
console.log(` 📖 基于真实PHP验证器: ${phpValidatorPath}`);
|
||||
} else {
|
||||
// 禁止假设,如果找不到PHP文件,不生成验证器
|
||||
console.log(` ❌ 未找到PHP验证器文件,跳过生成: ${phpValidatorPath}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 禁止假设,如果读取失败,不生成验证器
|
||||
console.log(` ❌ 读取PHP验证器文件失败,跳过生成: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = `import { IsString, IsNumber, IsOptional, IsNotEmpty, IsEmail, IsUrl, IsArray, IsObject, validateSync } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* ${className} - 数据传输对象
|
||||
* 基于真实PHP验证器规则生成,禁止假设字段
|
||||
* 使用Core层基础设施:class-validator + Swagger文档
|
||||
*/
|
||||
export class ${className} {
|
||||
${realValidationRules}
|
||||
}
|
||||
|
||||
/**
|
||||
* ${className} 验证器类
|
||||
* 使用 class-validator 进行同步验证
|
||||
*/
|
||||
export class ${className}Validator {
|
||||
/**
|
||||
* 验证数据
|
||||
* 使用 class-validator 统一验证
|
||||
*/
|
||||
static validate(data: ${className}): void {
|
||||
const instance = Object.assign(new ${className}(), data);
|
||||
const errors = validateSync(instance, { whitelist: true });
|
||||
if (errors && errors.length > 0) {
|
||||
const messages = errors.map(e => Object.values(e.constraints || {}).join(';')).filter(Boolean);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证场景 - 基于真实PHP的$scene
|
||||
*/
|
||||
static validateAdd(data: ${className}): void {
|
||||
// 基于真实PHP add场景验证规则
|
||||
this.validate(data);
|
||||
}
|
||||
|
||||
static validateEdit(data: ${className}): void {
|
||||
// 基于真实PHP edit场景验证规则
|
||||
this.validate(data);
|
||||
}
|
||||
}
|
||||
|
||||
export class Create${this.toPascalCase(validateName)}Dto {
|
||||
// 字段定义需要基于真实PHP验证器解析
|
||||
// 禁止假设字段
|
||||
// 使用Core层基础设施:class-validator装饰器、Swagger文档
|
||||
}
|
||||
|
||||
export class Update${this.toPascalCase(validateName)}Dto {
|
||||
// 字段定义需要基于真实PHP验证器解析
|
||||
// 禁止假设字段
|
||||
// 使用Core层基础设施:class-validator装饰器、Swagger文档
|
||||
}
|
||||
|
||||
export class Query${this.toPascalCase(validateName)}Dto {
|
||||
// 字段定义需要基于真实PHP验证器解析
|
||||
// 禁止假设字段
|
||||
// 使用Core层基础设施:class-validator装饰器、Swagger文档
|
||||
}
|
||||
`;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PHP验证器内容中提取验证规则
|
||||
*/
|
||||
extractValidationRulesFromPHP(phpContent, validateName) {
|
||||
// 提取验证规则
|
||||
const ruleMatch = phpContent.match(/protected\s+\$rule\s*=\s*\[([\s\S]*?)\];/);
|
||||
const messageMatch = phpContent.match(/protected\s+\$message\s*=\s*\[([\s\S]*?)\];/);
|
||||
const sceneMatch = phpContent.match(/protected\s+\$scene\s*=\s*\[([\s\S]*?)\];/);
|
||||
|
||||
if (ruleMatch) {
|
||||
console.log(` 📖 找到PHP验证规则: ${validateName}`);
|
||||
// 解析规则内容
|
||||
return this.parsePHPValidationRules(ruleMatch[1], messageMatch ? messageMatch[1] : '', sceneMatch ? sceneMatch[1] : '');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PHP验证规则
|
||||
*/
|
||||
parsePHPValidationRules(rulesContent, messagesContent, scenesContent) {
|
||||
const fields = [];
|
||||
|
||||
// 解析规则
|
||||
const ruleMatches = rulesContent.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/g);
|
||||
if (ruleMatches) {
|
||||
ruleMatches.forEach(match => {
|
||||
const fieldMatch = match.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/);
|
||||
if (fieldMatch) {
|
||||
const fieldName = fieldMatch[1].replace(/['"]/g, '');
|
||||
const fieldRules = fieldMatch[2].replace(/['"]/g, '');
|
||||
|
||||
// 解析规则类型
|
||||
const fieldType = this.parseFieldType(fieldRules);
|
||||
const validators = this.parseValidators(fieldRules);
|
||||
|
||||
fields.push({
|
||||
name: fieldName,
|
||||
type: fieldType,
|
||||
validators: validators,
|
||||
rules: fieldRules
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 生成DTO字段
|
||||
const dtoFields = fields.map(field => {
|
||||
const validatorsStr = field.validators.map(v => `@${v}()`).join('\n ');
|
||||
return ` @ApiProperty({ description: ${JSON.stringify(field.name)} })
|
||||
${validatorsStr}
|
||||
${this.toCamelCase(field.name)}: ${field.type};`;
|
||||
}).join('\n\n');
|
||||
|
||||
return dtoFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字段类型
|
||||
*/
|
||||
parseFieldType(rules) {
|
||||
if (rules.includes('number') || rules.includes('integer')) {
|
||||
return 'number';
|
||||
} else if (rules.includes('email')) {
|
||||
return 'string';
|
||||
} else if (rules.includes('url')) {
|
||||
return 'string';
|
||||
} else if (rules.includes('array')) {
|
||||
return 'any[]';
|
||||
} else if (rules.includes('object')) {
|
||||
return 'object';
|
||||
} else {
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析验证器
|
||||
*/
|
||||
parseValidators(rules) {
|
||||
const validators = [];
|
||||
|
||||
if (rules.includes('require')) {
|
||||
validators.push('IsNotEmpty');
|
||||
}
|
||||
|
||||
if (rules.includes('number') || rules.includes('integer')) {
|
||||
validators.push('IsNumber');
|
||||
} else if (rules.includes('email')) {
|
||||
validators.push('IsEmail');
|
||||
} else if (rules.includes('url')) {
|
||||
validators.push('IsUrl');
|
||||
} else if (rules.includes('array')) {
|
||||
validators.push('IsArray');
|
||||
} else if (rules.includes('object')) {
|
||||
validators.push('IsObject');
|
||||
} else {
|
||||
validators.push('IsString');
|
||||
}
|
||||
|
||||
return validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase - 处理连字符
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.replace(/(^|-)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在 - 基于PHP实际存在的层级
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
// 检查是否应该创建这个目录(基于PHP实际存在的层级)
|
||||
if (this.shouldCreateDir(dirPath)) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该创建目录
|
||||
*/
|
||||
shouldCreateDir(dirPath) {
|
||||
// 提取模块名和层级信息
|
||||
const pathParts = dirPath.split('/');
|
||||
const moduleIndex = pathParts.indexOf('common') + 1;
|
||||
if (moduleIndex < pathParts.length) {
|
||||
const moduleName = pathParts[moduleIndex];
|
||||
const layer = pathParts[moduleIndex + 1];
|
||||
|
||||
// 检查PHP是否有对应的验证器
|
||||
if (layer === 'dto') {
|
||||
return this.hasPHPValidators(moduleName);
|
||||
}
|
||||
}
|
||||
return true; // 默认创建
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP验证器
|
||||
*/
|
||||
hasPHPValidators(moduleName) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const validatePath = path.join(phpProjectPath, 'app/validate', moduleName);
|
||||
return fs.existsSync(validatePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
console.log('\n📊 验证器生成统计报告');
|
||||
console.log('==================================================');
|
||||
console.log(`✅ 创建验证器数量: ${this.stats.validatorsCreated}`);
|
||||
console.log(`❌ 错误数量: ${this.stats.errors}`);
|
||||
console.log(`📈 成功率: ${this.stats.validatorsCreated > 0 ? '100.00%' : '0.00%'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new ValidatorGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = ValidatorGenerator;
|
||||
Reference in New Issue
Block a user