feat: 完成PHP到NestJS的100%功能迁移
- 迁移25个模块,包含95个控制器和160个服务 - 新增验证码管理、登录配置、云编译等模块 - 完善认证授权、会员管理、支付系统等核心功能 - 实现完整的队列系统、配置管理、监控体系 - 确保100%功能对齐和命名一致性 - 支持生产环境部署
This commit is contained in:
103
scripts/generate-entities-from-sql.js
Normal file
103
scripts/generate-entities-from-sql.js
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const sqlFile = path.join(repoRoot, 'sql', 'wwjcloud.sql');
|
||||
const outDir = path.join(repoRoot, 'temp', 'entities');
|
||||
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const sql = fs.readFileSync(sqlFile, 'utf8');
|
||||
|
||||
// crude parser: split by CREATE TABLE `table`
|
||||
const tableRegex = /CREATE TABLE\s+`([^`]+)`\s*\(([^;]+)\)\s*ENGINE=[^;]+;/gim;
|
||||
|
||||
function pascalCase(name) {
|
||||
return name
|
||||
.replace(/^[^a-zA-Z]+/, '')
|
||||
.split(/[_\-\s]+/)
|
||||
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function mapColumnType(def) {
|
||||
const d = def.toLowerCase();
|
||||
if (d.startsWith('int') || d.startsWith('tinyint') || d.startsWith('smallint') || d.startsWith('bigint')) return { type: 'int' };
|
||||
if (d.startsWith('varchar')) {
|
||||
const m = d.match(/varchar\((\d+)\)/);
|
||||
return { type: 'varchar', length: m ? parseInt(m[1], 10) : 255 };
|
||||
}
|
||||
if (d.startsWith('text') || d.includes('longtext')) return { type: 'text' };
|
||||
if (d.startsWith('decimal')) {
|
||||
const m = d.match(/decimal\((\d+)\s*,\s*(\d+)\)/);
|
||||
return { type: 'decimal', precision: m ? parseInt(m[1], 10) : 10, scale: m ? parseInt(m[2], 10) : 2 };
|
||||
}
|
||||
if (d.startsWith('timestamp')) return { type: 'int' };
|
||||
if (d.startsWith('datetime')) return { type: 'int' };
|
||||
if (d.startsWith('enum')) return { type: 'varchar', length: 255 };
|
||||
return { type: 'varchar', length: 255 };
|
||||
}
|
||||
|
||||
function parseDefault(defPart) {
|
||||
const m = defPart.match(/default\s+([^\s]+)/i);
|
||||
if (!m) return undefined;
|
||||
let v = m[1].trim();
|
||||
v = v.replace(/^'/, '').replace(/'$/, '');
|
||||
if (v.toLowerCase() === 'null') return undefined;
|
||||
if (/^[0-9.]+$/.test(v)) return Number(v);
|
||||
return `'${v}'`;
|
||||
}
|
||||
|
||||
function generateEntity(tableName, columnsBlock) {
|
||||
const className = pascalCase(tableName);
|
||||
const lines = columnsBlock.split(/\n/).map((l) => l.trim()).filter(Boolean);
|
||||
const fields = [];
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('PRIMARY KEY') || line.startsWith('UNIQUE') || line.startsWith('KEY') || line.startsWith(')')) continue;
|
||||
const m = line.match(/^`([^`]+)`\s+([^\s,]+)([^,]*),?$/);
|
||||
if (!m) continue;
|
||||
const col = m[1];
|
||||
const typeDef = m[2];
|
||||
const rest = m[3] || '';
|
||||
const isPk = /auto_increment/i.test(rest) || col === 'id';
|
||||
const { type, length, precision, scale } = mapColumnType(typeDef);
|
||||
const defVal = parseDefault(rest);
|
||||
fields.push({ col, isPk, type, length, precision, scale, defVal });
|
||||
}
|
||||
|
||||
const imports = new Set(['Entity', 'Column']);
|
||||
if (fields.some((f) => f.isPk)) imports.add('PrimaryGeneratedColumn');
|
||||
const importLine = `import { ${Array.from(imports).join(', ')} } from 'typeorm';`;
|
||||
|
||||
const props = fields.map((f) => {
|
||||
if (f.isPk) {
|
||||
return ` @PrimaryGeneratedColumn({ type: 'int' })\n id: number;`;
|
||||
}
|
||||
const opts = [];
|
||||
opts.push(`name: '${f.col}'`);
|
||||
opts.push(`type: '${f.type}'`);
|
||||
if (f.length) opts.push(`length: ${f.length}`);
|
||||
if (f.precision) opts.push(`precision: ${f.precision}`);
|
||||
if (f.scale !== undefined) opts.push(`scale: ${f.scale}`);
|
||||
if (f.defVal !== undefined) opts.push(`default: ${f.defVal}`);
|
||||
const propName = f.col.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
||||
return ` @Column({ ${opts.join(', ')} })\n ${propName}: ${f.type === 'decimal' ? 'string' : 'any'};`;
|
||||
}).join('\n\n');
|
||||
|
||||
return `${importLine}\n\n@Entity('${tableName}')\nexport class ${className} {\n${props}\n}\n`;
|
||||
}
|
||||
|
||||
let match;
|
||||
let count = 0;
|
||||
while ((match = tableRegex.exec(sql)) !== null) {
|
||||
const table = match[1];
|
||||
const body = match[2];
|
||||
const ts = generateEntity(table, body);
|
||||
const outFile = path.join(outDir, `${table}.ts`);
|
||||
fs.writeFileSync(outFile, ts, 'utf8');
|
||||
count++;
|
||||
}
|
||||
|
||||
console.log(`Generated ${count} entities into ${path.relative(repoRoot, outDir)}`);
|
||||
Reference in New Issue
Block a user