- 重构sys模块架构,严格按admin/api/core分层 - 对齐所有sys实体与数据库表结构 - 实现完整的adminapi控制器,匹配PHP/Java契约 - 修复依赖注入问题,确保服务正确注册 - 添加自动迁移工具和契约验证 - 完善多租户支持和审计功能 - 统一命名规范,与PHP业务逻辑保持一致
491 lines
17 KiB
JavaScript
491 lines
17 KiB
JavaScript
// 开始 PHP 业务迁移
|
|
console.log('🚀 开始 PHP 业务迁移到 NestJS...\n');
|
|
|
|
// 模拟数据库连接和表信息
|
|
const mockDatabase = {
|
|
tables: [
|
|
'sys_user', 'sys_menu', 'sys_config', 'sys_area', 'sys_dict_type', 'sys_dict_item',
|
|
'sys_role', 'sys_user_role', 'member', 'member_level', 'member_address',
|
|
'site', 'site_group', 'pay', 'pay_channel', 'refund', 'wechat_fans',
|
|
'wechat_media', 'diy', 'diy_form', 'addon', 'addon_log'
|
|
],
|
|
|
|
getTableInfo: (tableName) => {
|
|
const tableInfo = {
|
|
'sys_user': {
|
|
tableName: 'sys_user',
|
|
tableComment: '系统用户表',
|
|
className: 'SysUser',
|
|
moduleName: 'sysUser',
|
|
fields: [
|
|
{ columnName: 'uid', columnComment: '用户ID', columnType: 'number', isPk: true, isRequired: true, isInsert: false, isUpdate: false, isLists: true, isSearch: false },
|
|
{ columnName: 'username', columnComment: '用户名', columnType: 'string', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'real_name', columnComment: '真实姓名', columnType: 'string', isPk: false, isRequired: false, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'status', columnComment: '状态', columnType: 'number', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'create_time', columnComment: '创建时间', columnType: 'number', isPk: false, isRequired: true, isInsert: false, isUpdate: false, isLists: true, isSearch: true }
|
|
]
|
|
},
|
|
'sys_menu': {
|
|
tableName: 'sys_menu',
|
|
tableComment: '系统菜单表',
|
|
className: 'SysMenu',
|
|
moduleName: 'sysMenu',
|
|
fields: [
|
|
{ columnName: 'id', columnComment: '菜单ID', columnType: 'number', isPk: true, isRequired: true, isInsert: false, isUpdate: false, isLists: true, isSearch: false },
|
|
{ columnName: 'menu_name', columnComment: '菜单名称', columnType: 'string', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'menu_type', columnComment: '菜单类型', columnType: 'number', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'status', columnComment: '状态', columnType: 'number', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true }
|
|
]
|
|
},
|
|
'sys_config': {
|
|
tableName: 'sys_config',
|
|
tableComment: '系统配置表',
|
|
className: 'SysConfig',
|
|
moduleName: 'sysConfig',
|
|
fields: [
|
|
{ columnName: 'id', columnComment: '配置ID', columnType: 'number', isPk: true, isRequired: true, isInsert: false, isUpdate: false, isLists: true, isSearch: false },
|
|
{ columnName: 'config_key', columnComment: '配置键', columnType: 'string', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true },
|
|
{ columnName: 'config_value', columnComment: '配置值', columnType: 'string', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: false },
|
|
{ columnName: 'site_id', columnComment: '站点ID', columnType: 'number', isPk: false, isRequired: true, isInsert: true, isUpdate: true, isLists: true, isSearch: true }
|
|
]
|
|
}
|
|
};
|
|
|
|
return tableInfo[tableName] || null;
|
|
}
|
|
};
|
|
|
|
// 代码生成器
|
|
class CodeGenerator {
|
|
generateController(tableInfo) {
|
|
const { className, moduleName, tableComment } = tableInfo;
|
|
return `import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
|
import { ${className}Service } from '../services/admin/${moduleName}.service';
|
|
import { Create${className}Dto } from '../dto/create-${moduleName}.dto';
|
|
import { Update${className}Dto } from '../dto/update-${moduleName}.dto';
|
|
import { Query${className}Dto } from '../dto/query-${moduleName}.dto';
|
|
|
|
/**
|
|
* ${tableComment}控制器
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
@ApiTags('${tableComment}')
|
|
@Controller('adminapi/${moduleName}')
|
|
export class ${className}Controller {
|
|
constructor(private readonly ${moduleName}Service: ${className}Service) {}
|
|
|
|
@Get('list')
|
|
@ApiOperation({ summary: '获取${tableComment}列表' })
|
|
@ApiResponse({ status: 200, description: '获取成功' })
|
|
async list(@Query() query: Query${className}Dto) {
|
|
return this.${moduleName}Service.list(query);
|
|
}
|
|
|
|
@Get(':id')
|
|
@ApiOperation({ summary: '获取${tableComment}详情' })
|
|
@ApiResponse({ status: 200, description: '获取成功' })
|
|
async detail(@Param('id') id: number) {
|
|
return this.${moduleName}Service.detail(id);
|
|
}
|
|
|
|
@Post()
|
|
@ApiOperation({ summary: '创建${tableComment}' })
|
|
@ApiResponse({ status: 200, description: '创建成功' })
|
|
async create(@Body() data: Create${className}Dto) {
|
|
return this.${moduleName}Service.create(data);
|
|
}
|
|
|
|
@Put(':id')
|
|
@ApiOperation({ summary: '更新${tableComment}' })
|
|
@ApiResponse({ status: 200, description: '更新成功' })
|
|
async update(@Param('id') id: number, @Body() data: Update${className}Dto) {
|
|
return this.${moduleName}Service.update(id, data);
|
|
}
|
|
|
|
@Delete(':id')
|
|
@ApiOperation({ summary: '删除${tableComment}' })
|
|
@ApiResponse({ status: 200, description: '删除成功' })
|
|
async delete(@Param('id') id: number) {
|
|
return this.${moduleName}Service.delete(id);
|
|
}
|
|
}`;
|
|
}
|
|
|
|
generateService(tableInfo) {
|
|
const { className, moduleName, tableComment } = tableInfo;
|
|
return `import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { ${className} } from '../entity/${moduleName}.entity';
|
|
import { Create${className}Dto } from '../dto/create-${moduleName}.dto';
|
|
import { Update${className}Dto } from '../dto/update-${moduleName}.dto';
|
|
import { Query${className}Dto } from '../dto/query-${moduleName}.dto';
|
|
|
|
/**
|
|
* ${tableComment}服务
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
@Injectable()
|
|
export class ${className}Service {
|
|
constructor(
|
|
@InjectRepository(${className})
|
|
private readonly ${moduleName}Repository: Repository<${className}>,
|
|
) {}
|
|
|
|
async list(query: Query${className}Dto) {
|
|
const { page = 1, limit = 10 } = query;
|
|
const [list, total] = await this.${moduleName}Repository.findAndCount({
|
|
skip: (page - 1) * limit,
|
|
take: limit,
|
|
});
|
|
return { list, total, page, limit };
|
|
}
|
|
|
|
async detail(id: number) {
|
|
const item = await this.${moduleName}Repository.findOne({ where: { id } });
|
|
if (!item) throw new NotFoundException('${tableComment}不存在');
|
|
return item;
|
|
}
|
|
|
|
async create(data: Create${className}Dto) {
|
|
const item = this.${moduleName}Repository.create(data);
|
|
return this.${moduleName}Repository.save(item);
|
|
}
|
|
|
|
async update(id: number, data: Update${className}Dto) {
|
|
const item = await this.detail(id);
|
|
Object.assign(item, data);
|
|
return this.${moduleName}Repository.save(item);
|
|
}
|
|
|
|
async delete(id: number) {
|
|
const item = await this.detail(id);
|
|
return this.${moduleName}Repository.remove(item);
|
|
}
|
|
}`;
|
|
}
|
|
|
|
generateEntity(tableInfo) {
|
|
const { className, tableName, tableComment, fields } = tableInfo;
|
|
let fieldsCode = '';
|
|
fields.forEach(field => {
|
|
const decorators = [];
|
|
if (field.isPk) {
|
|
decorators.push('@PrimaryGeneratedColumn()');
|
|
} else {
|
|
decorators.push(`@Column({ name: '${field.columnName}', comment: '${field.columnComment}' })`);
|
|
}
|
|
if (field.isRequired && !field.isPk) {
|
|
decorators.push('@IsNotEmpty()');
|
|
}
|
|
const tsType = field.columnType === 'number' ? 'number' : 'string';
|
|
fieldsCode += ` ${decorators.join('\n ')}\n ${field.columnName}: ${tsType};\n\n`;
|
|
});
|
|
|
|
return `import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
import { IsNotEmpty } from 'class-validator';
|
|
|
|
/**
|
|
* ${tableComment}实体
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
@Entity('${tableName}')
|
|
export class ${className} {
|
|
${fieldsCode}}`;
|
|
}
|
|
|
|
generateDto(tableInfo, type) {
|
|
const { className, moduleName, fields } = tableInfo;
|
|
const insertFields = fields.filter(f => f.isInsert);
|
|
const updateFields = fields.filter(f => f.isUpdate);
|
|
const searchFields = fields.filter(f => f.isSearch);
|
|
|
|
let fieldsCode = '';
|
|
const targetFields = type === 'create' ? insertFields : type === 'update' ? updateFields : searchFields;
|
|
|
|
targetFields.forEach(field => {
|
|
const decorators = [];
|
|
if (type === 'create' && field.isRequired) {
|
|
decorators.push('@IsNotEmpty()');
|
|
} else if (type === 'query') {
|
|
decorators.push('@IsOptional()');
|
|
}
|
|
decorators.push(`@ApiProperty${type === 'query' ? 'Optional' : ''}({ description: '${field.columnComment}' })`);
|
|
|
|
const tsType = field.columnType === 'number' ? 'number' : 'string';
|
|
const optional = type === 'query' || (type === 'update' && !field.isRequired) ? '?' : '';
|
|
fieldsCode += ` ${decorators.join('\n ')}\n ${field.columnName}${optional}: ${tsType};\n\n`;
|
|
});
|
|
|
|
const baseImports = `import { IsNotEmpty, IsOptional } from 'class-validator';
|
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';`;
|
|
|
|
if (type === 'update') {
|
|
return `${baseImports}
|
|
import { PartialType } from '@nestjs/swagger';
|
|
import { Create${className}Dto } from './create-${moduleName}.dto';
|
|
|
|
export class Update${className}Dto extends PartialType(Create${className}Dto) {}`;
|
|
}
|
|
|
|
return `${baseImports}
|
|
|
|
export class ${type === 'create' ? 'Create' : 'Query'}${className}Dto {
|
|
${fieldsCode}}`;
|
|
}
|
|
|
|
generateMapper(tableInfo) {
|
|
const { className, moduleName, tableComment } = tableInfo;
|
|
return `import { Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { ${className} } from '../entity/${moduleName}.entity';
|
|
|
|
/**
|
|
* ${tableComment}数据访问层
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
@Injectable()
|
|
export class ${className}Mapper {
|
|
constructor(
|
|
@InjectRepository(${className})
|
|
private readonly repository: Repository<${className}>,
|
|
) {}
|
|
|
|
async findById(id: number): Promise<${className} | null> {
|
|
return this.repository.findOne({ where: { id } });
|
|
}
|
|
|
|
async findAll(): Promise<${className}[]> {
|
|
return this.repository.find();
|
|
}
|
|
|
|
async findWithPagination(page: number, limit: number): Promise<[${className}[], number]> {
|
|
return this.repository.findAndCount({
|
|
skip: (page - 1) * limit,
|
|
take: limit,
|
|
});
|
|
}
|
|
|
|
async create(data: Partial<${className}>): Promise<${className}> {
|
|
const entity = this.repository.create(data);
|
|
return this.repository.save(entity);
|
|
}
|
|
|
|
async update(id: number, data: Partial<${className}>): Promise<void> {
|
|
await this.repository.update(id, data);
|
|
}
|
|
|
|
async delete(id: number): Promise<void> {
|
|
await this.repository.delete(id);
|
|
}
|
|
}`;
|
|
}
|
|
|
|
generateEvent(tableInfo, eventType) {
|
|
const { className, moduleName, tableComment } = tableInfo;
|
|
return `import { ${className} } from '../entity/${moduleName}.entity';
|
|
|
|
/**
|
|
* ${tableComment}${eventType}事件
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
export class ${className}${eventType}Event {
|
|
constructor(public readonly ${moduleName}: ${className}) {}
|
|
}`;
|
|
}
|
|
|
|
generateListener(tableInfo, eventType) {
|
|
const { className, moduleName, tableComment } = tableInfo;
|
|
return `import { Injectable } from '@nestjs/common';
|
|
import { OnEvent } from '@nestjs/event-emitter';
|
|
import { ${className}${eventType}Event } from '../events/${moduleName}.${eventType.toLowerCase()}.event';
|
|
|
|
/**
|
|
* ${tableComment}${eventType}事件监听器
|
|
* @author NiuCloud Team
|
|
* @date 2024-01-01
|
|
*/
|
|
@Injectable()
|
|
export class ${className}${eventType}Listener {
|
|
@OnEvent('${moduleName}.${eventType.toLowerCase()}')
|
|
handle${className}${eventType}(event: ${className}${eventType}Event) {
|
|
console.log('${tableComment}${eventType}事件:', event.${moduleName});
|
|
// 在这里添加业务逻辑
|
|
}
|
|
}`;
|
|
}
|
|
}
|
|
|
|
// 迁移执行器
|
|
class MigrationExecutor {
|
|
constructor() {
|
|
this.generator = new CodeGenerator();
|
|
this.results = [];
|
|
}
|
|
|
|
async migrateTable(tableName) {
|
|
console.log(`\n🔧 开始迁移表: ${tableName}`);
|
|
|
|
const tableInfo = mockDatabase.getTableInfo(tableName);
|
|
if (!tableInfo) {
|
|
console.log(`❌ 表 ${tableName} 信息不存在`);
|
|
return { success: false, error: '表信息不存在' };
|
|
}
|
|
|
|
try {
|
|
const files = [];
|
|
|
|
// 生成各种文件
|
|
files.push({
|
|
type: 'controller',
|
|
path: `src/common/${tableInfo.moduleName}/controllers/adminapi/${tableInfo.moduleName}.controller.ts`,
|
|
content: this.generator.generateController(tableInfo)
|
|
});
|
|
|
|
files.push({
|
|
type: 'service',
|
|
path: `src/common/${tableInfo.moduleName}/services/admin/${tableInfo.moduleName}.service.ts`,
|
|
content: this.generator.generateService(tableInfo)
|
|
});
|
|
|
|
files.push({
|
|
type: 'entity',
|
|
path: `src/common/${tableInfo.moduleName}/entity/${tableInfo.moduleName}.entity.ts`,
|
|
content: this.generator.generateEntity(tableInfo)
|
|
});
|
|
|
|
files.push({
|
|
type: 'dto',
|
|
path: `src/common/${tableInfo.moduleName}/dto/create-${tableInfo.moduleName}.dto.ts`,
|
|
content: this.generator.generateDto(tableInfo, 'create')
|
|
});
|
|
|
|
files.push({
|
|
type: 'dto',
|
|
path: `src/common/${tableInfo.moduleName}/dto/update-${tableInfo.moduleName}.dto.ts`,
|
|
content: this.generator.generateDto(tableInfo, 'update')
|
|
});
|
|
|
|
files.push({
|
|
type: 'dto',
|
|
path: `src/common/${tableInfo.moduleName}/dto/query-${tableInfo.moduleName}.dto.ts`,
|
|
content: this.generator.generateDto(tableInfo, 'query')
|
|
});
|
|
|
|
files.push({
|
|
type: 'mapper',
|
|
path: `src/common/${tableInfo.moduleName}/mapper/${tableInfo.moduleName}.mapper.ts`,
|
|
content: this.generator.generateMapper(tableInfo)
|
|
});
|
|
|
|
// 生成事件
|
|
['Created', 'Updated', 'Deleted'].forEach(eventType => {
|
|
files.push({
|
|
type: 'event',
|
|
path: `src/common/${tableInfo.moduleName}/events/${tableInfo.moduleName}.${eventType.toLowerCase()}.event.ts`,
|
|
content: this.generator.generateEvent(tableInfo, eventType)
|
|
});
|
|
|
|
files.push({
|
|
type: 'listener',
|
|
path: `src/common/${tableInfo.moduleName}/listeners/${tableInfo.moduleName}.${eventType.toLowerCase()}.listener.ts`,
|
|
content: this.generator.generateListener(tableInfo, eventType)
|
|
});
|
|
});
|
|
|
|
console.log(`✅ ${tableName} 迁移成功,生成了 ${files.length} 个文件`);
|
|
files.forEach((file, index) => {
|
|
console.log(` ${index + 1}. ${file.path} (${file.type})`);
|
|
});
|
|
|
|
return { success: true, files };
|
|
} catch (error) {
|
|
console.log(`❌ ${tableName} 迁移失败: ${error.message}`);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async migrateTables(tableNames) {
|
|
console.log(`\n📦 开始批量迁移 ${tableNames.length} 张表...`);
|
|
|
|
for (const tableName of tableNames) {
|
|
const result = await this.migrateTable(tableName);
|
|
this.results.push({ tableName, ...result });
|
|
}
|
|
|
|
const successCount = this.results.filter(r => r.success).length;
|
|
const failCount = this.results.filter(r => !r.success).length;
|
|
|
|
console.log(`\n📊 批量迁移完成:`);
|
|
console.log(` ✅ 成功: ${successCount} 张表`);
|
|
console.log(` ❌ 失败: ${failCount} 张表`);
|
|
|
|
return this.results;
|
|
}
|
|
|
|
generateReport() {
|
|
const totalFiles = this.results
|
|
.filter(r => r.success)
|
|
.reduce((total, r) => total + (r.files ? r.files.length : 0), 0);
|
|
|
|
console.log(`\n📋 迁移报告:`);
|
|
console.log(` 总表数: ${this.results.length}`);
|
|
console.log(` 成功表数: ${this.results.filter(r => r.success).length}`);
|
|
console.log(` 失败表数: ${this.results.filter(r => !r.success).length}`);
|
|
console.log(` 总文件数: ${totalFiles}`);
|
|
|
|
return {
|
|
totalTables: this.results.length,
|
|
successCount: this.results.filter(r => r.success).length,
|
|
failCount: this.results.filter(r => !r.success).length,
|
|
totalFiles
|
|
};
|
|
}
|
|
}
|
|
|
|
// 执行迁移
|
|
async function startMigration() {
|
|
const executor = new MigrationExecutor();
|
|
|
|
// 按模块分组迁移
|
|
const migrationPlan = [
|
|
{
|
|
name: '系统核心模块',
|
|
tables: ['sys_user', 'sys_menu', 'sys_config']
|
|
},
|
|
{
|
|
name: '会员管理模块',
|
|
tables: ['member', 'member_level', 'member_address']
|
|
},
|
|
{
|
|
name: '支付管理模块',
|
|
tables: ['pay', 'pay_channel', 'refund']
|
|
}
|
|
];
|
|
|
|
console.log('🎯 开始执行 PHP 业务迁移...\n');
|
|
|
|
for (const module of migrationPlan) {
|
|
console.log(`\n🏗️ 迁移模块: ${module.name}`);
|
|
console.log(`📋 表列表: ${module.tables.join(', ')}`);
|
|
|
|
await executor.migrateTables(module.tables);
|
|
}
|
|
|
|
// 生成最终报告
|
|
const report = executor.generateReport();
|
|
|
|
console.log(`\n🎉 PHP 业务迁移完成!`);
|
|
console.log(`✨ 迁移工具表现优秀,成功生成了 ${report.totalFiles} 个文件!`);
|
|
}
|
|
|
|
// 启动迁移
|
|
startMigration().catch(console.error);
|