Files
wwjcloud/tools/generators/entity-generator.js

412 lines
12 KiB
JavaScript
Raw Normal View History

#!/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_idcreate_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;