#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const BaseGenerator = require('./base-generator'); /** * 🏗️ 实体生成器 * 专门负责生成NestJS实体文件 */ class EntityGenerator extends BaseGenerator { constructor() { super('EntityGenerator'); this.config = { phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest/src/core', discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools/php-discovery-result.json' }; this.discoveryData = null; this.entityStats = { entitiesCreated: 0, entitiesSkipped: 0 }; } /** * 运行实体生成 */ async run() { try { console.log('🏗️ 启动实体生成器...'); console.log('目标:生成NestJS实体文件\n'); // 加载PHP文件发现结果 await this.loadDiscoveryData(); // 生成实体 await this.generateEntities(); // 输出统计报告 this.printStats(); } catch (error) { console.error('❌ 实体生成失败:', error); this.stats.errors++; } } /** * 加载PHP文件发现结果 */ async loadDiscoveryData() { try { const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8'); this.discoveryData = JSON.parse(data); console.log(' ✅ 成功加载PHP文件发现结果'); } catch (error) { console.error('❌ 加载发现结果失败:', error); throw error; } } /** * 生成实体 */ async 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)) { // 检查PHP项目是否有对应的模型目录 if (!this.hasPHPModels(moduleName)) { console.log(` ⚠️ 模块 ${moduleName} 在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'; import { BaseEntity } from '@wwjCommon/base/base.entity'; /** * ${className} - 数据库实体 * 继承Core层BaseEntity,包含site_id、create_time等通用字段 (对应PHP Model继承BaseModel) * 使用Core层基础设施:索引管理、性能监控 */ @Entity('${tableName}') export class ${className} extends BaseEntity { ${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;