refactor: 移除模板迁移API接口,改为migrations阶段处理
- 删除 template-migration.service.ts 文件 - 移除控制器中的迁移相关API接口 (/migrate, /sync, /admin/migration-report) - 清理文档中的迁移服务相关内容 - 添加 data-source.ts 配置文件用于运行数据库迁移 - 模板迁移功能完全通过数据库migration脚本处理
This commit is contained in:
16
data-source.ts
Normal file
16
data-source.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: 'mysql',
|
||||
host: 'mysql-6bc9094abd49-public.rds.volces.com',
|
||||
port: 53305,
|
||||
username: 'root',
|
||||
password: 'WsJWXfwFx0bq6DE2kmB6',
|
||||
database: 'nano_camera_miniapp',
|
||||
entities: ['src/**/*.entity.ts'],
|
||||
migrations: ['src/migrations/*.ts'],
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
- **代码层**:抽象类定义执行逻辑和接口规范
|
||||
- **数据库层**:存储具体模板的配置信息
|
||||
- **Entity层**:动态从数据库加载配置,创建模板实例
|
||||
- **Migration层**:通过数据库迁移脚本初始化模板配置数据
|
||||
|
||||
## 🏗️ 架构层次分析
|
||||
|
||||
@@ -373,18 +374,8 @@ export class TemplateController {
|
||||
@Param('templateId', ParseIntPipe) templateId: number,
|
||||
@Body() { imageUrl }: { imageUrl: string }
|
||||
) {
|
||||
// 动态创建模板实例并执行
|
||||
const config = await this.templateFactory.templateRepository.findOne({
|
||||
where: { id: templateId }
|
||||
});
|
||||
|
||||
let template;
|
||||
if (config.templateType === TemplateType.IMAGE) {
|
||||
template = await this.templateFactory.createImageTemplate(templateId);
|
||||
} else {
|
||||
template = await this.templateFactory.createVideoTemplate(templateId);
|
||||
}
|
||||
|
||||
// 通过工厂服务创建模板实例并执行
|
||||
const template = await this.templateFactory.createTemplate(templateId);
|
||||
return await template.execute(imageUrl);
|
||||
}
|
||||
|
||||
@@ -395,83 +386,16 @@ export class TemplateController {
|
||||
|
||||
@Get('image')
|
||||
async getImageTemplates() {
|
||||
return this.templateFactory.getTemplatesByType(TemplateType.IMAGE);
|
||||
return this.templateFactory.getImageTemplates();
|
||||
}
|
||||
|
||||
@Get('video')
|
||||
async getVideoTemplates() {
|
||||
return this.templateFactory.getTemplatesByType(TemplateType.VIDEO);
|
||||
return this.templateFactory.getVideoTemplates();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据迁移脚本
|
||||
```typescript
|
||||
// 将现有代码中的模板配置迁移到数据库
|
||||
// src/migrations/migrate-templates.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { N8nTemplateEntity, TemplateType } from '../entities/n8n-template.entity';
|
||||
|
||||
// 导入现有模板
|
||||
import { PhotoRestoreTemplate } from '../templates/n8nTemplates/PhotoRestoreTemplate';
|
||||
import { CharacterFigurineTemplate } from '../templates/n8nTemplates/CharacterFigurineTemplate';
|
||||
|
||||
@Injectable()
|
||||
export class TemplateMigrationService {
|
||||
constructor(
|
||||
@InjectRepository(N8nTemplateEntity)
|
||||
private templateRepository: Repository<N8nTemplateEntity>,
|
||||
) {}
|
||||
|
||||
async migrateExistingTemplates() {
|
||||
// 迁移现有模板配置
|
||||
const templates = [
|
||||
new PhotoRestoreTemplate(),
|
||||
new CharacterFigurineTemplate(),
|
||||
// ... 其他模板
|
||||
];
|
||||
|
||||
for (const template of templates) {
|
||||
const existing = await this.templateRepository.findOne({
|
||||
where: { code: template.code }
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
const entity = new N8nTemplateEntity();
|
||||
entity.code = template.code;
|
||||
entity.name = template.name;
|
||||
entity.description = template.description;
|
||||
entity.creditCost = template.creditCost;
|
||||
entity.version = template.version;
|
||||
entity.inputExampleUrl = template.input;
|
||||
entity.outputExampleUrl = template.output;
|
||||
entity.tags = template.tags;
|
||||
|
||||
// 判断模板类型
|
||||
if (template instanceof N8nImageGenerateTemplate) {
|
||||
entity.templateType = TemplateType.IMAGE;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
} else if (template instanceof N8nVideoGenerateTemplate) {
|
||||
entity.templateType = TemplateType.VIDEO;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
entity.videoModel = template.videoModel;
|
||||
entity.videoPrompt = template.videoPrompt;
|
||||
entity.duration = template.duration;
|
||||
entity.aspectRatio = template.aspectRatio;
|
||||
}
|
||||
|
||||
entity.templateClass = template.constructor.name;
|
||||
|
||||
await this.templateRepository.save(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 架构优势
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { TemplateManager } from './templates/types';
|
||||
import { typeOrmConfig } from './config/database.config';
|
||||
import { N8nTemplateEntity } from './entities/n8n-template.entity';
|
||||
import { N8nTemplateFactoryService } from './services/n8n-template-factory.service';
|
||||
import { TemplateMigrationService } from './services/template-migration.service';
|
||||
import { TemplateController } from './controllers/template.controller';
|
||||
|
||||
@Module({
|
||||
@@ -19,8 +18,7 @@ import { TemplateController } from './controllers/template.controller';
|
||||
TemplateService,
|
||||
TemplateManager,
|
||||
N8nTemplateFactoryService,
|
||||
TemplateMigrationService
|
||||
],
|
||||
exports: [N8nTemplateFactoryService, TemplateMigrationService]
|
||||
exports: [N8nTemplateFactoryService]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
HttpStatus
|
||||
} from '@nestjs/common';
|
||||
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
|
||||
import { TemplateMigrationService } from '../services/template-migration.service';
|
||||
|
||||
/**
|
||||
* 模板控制器
|
||||
@@ -19,8 +18,7 @@ import { TemplateMigrationService } from '../services/template-migration.service
|
||||
@Controller('templates')
|
||||
export class TemplateController {
|
||||
constructor(
|
||||
private readonly templateFactory: N8nTemplateFactoryService,
|
||||
private readonly migrationService: TemplateMigrationService
|
||||
private readonly templateFactory: N8nTemplateFactoryService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -281,69 +279,4 @@ export class TemplateController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移现有代码模板到数据库
|
||||
* @returns 迁移结果
|
||||
*/
|
||||
@Post('migrate')
|
||||
async migrateTemplates() {
|
||||
try {
|
||||
await this.migrationService.migrateAllExistingTemplates();
|
||||
const report = await this.migrationService.getMigrationReport();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Templates migrated successfully',
|
||||
data: report
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Migration failed',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步现有模板配置
|
||||
* @returns 同步结果
|
||||
*/
|
||||
@Post('sync')
|
||||
async syncTemplates() {
|
||||
try {
|
||||
await this.migrationService.syncExistingTemplates();
|
||||
const report = await this.migrationService.getMigrationReport();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Templates synchronized successfully',
|
||||
data: report
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Synchronization failed',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取迁移状态报告
|
||||
* @returns 迁移状态报告
|
||||
*/
|
||||
@Get('admin/migration-report')
|
||||
async getMigrationReport() {
|
||||
try {
|
||||
const report = await this.migrationService.getMigrationReport();
|
||||
return {
|
||||
success: true,
|
||||
data: report
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to get migration report',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { N8nTemplateEntity, TemplateType } from '../entities/n8n-template.entity';
|
||||
import { N8nImageGenerateTemplate, N8nVideoGenerateTemplate } from '../templates/n8nTemplate';
|
||||
|
||||
// 导入现有模板
|
||||
import { PhotoRestoreTemplate } from '../templates/n8nTemplates/PhotoRestoreTemplate';
|
||||
import { CharacterFigurineTemplate } from '../templates/n8nTemplates/CharacterFigurineTemplate';
|
||||
import { PetFigurineTemplate } from '../templates/n8nTemplates/PetFigurineTemplate';
|
||||
import { CosplayRealPersonTemplate } from '../templates/n8nTemplates/CosplayRealPersonTemplate';
|
||||
import { GarageOpeningTemplate } from '../templates/n8nTemplates/GarageOpeningTemplate';
|
||||
import { JapaneseMagazineTemplate } from '../templates/n8nTemplates/JapaneseMagazineTemplate';
|
||||
import { NuclearExplosionTemplate } from '../templates/n8nTemplates/NuclearExplosionTemplate';
|
||||
|
||||
/**
|
||||
* 模板迁移服务
|
||||
* 负责将现有代码中的模板配置迁移到数据库
|
||||
*/
|
||||
@Injectable()
|
||||
export class TemplateMigrationService {
|
||||
constructor(
|
||||
@InjectRepository(N8nTemplateEntity)
|
||||
private templateRepository: Repository<N8nTemplateEntity>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 迁移所有现有模板到数据库
|
||||
*/
|
||||
async migrateAllExistingTemplates(): Promise<void> {
|
||||
console.log('开始迁移现有模板到数据库...');
|
||||
|
||||
// 创建模板实例
|
||||
const templates = [
|
||||
new PhotoRestoreTemplate(),
|
||||
new CharacterFigurineTemplate(),
|
||||
new PetFigurineTemplate(),
|
||||
new CosplayRealPersonTemplate(),
|
||||
new GarageOpeningTemplate(),
|
||||
new JapaneseMagazineTemplate(),
|
||||
new NuclearExplosionTemplate(),
|
||||
];
|
||||
|
||||
let migratedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const template of templates) {
|
||||
try {
|
||||
const result = await this.migrateTemplate(template);
|
||||
if (result) {
|
||||
migratedCount++;
|
||||
console.log(`✅ 成功迁移模板: ${template.code} - ${template.name}`);
|
||||
} else {
|
||||
skippedCount++;
|
||||
console.log(`⏭️ 跳过已存在模板: ${template.code} - ${template.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 迁移模板失败: ${template.code}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 迁移完成统计:`);
|
||||
console.log(` • 新增模板: ${migratedCount} 个`);
|
||||
console.log(` • 跳过模板: ${skippedCount} 个`);
|
||||
console.log(` • 总模板数: ${templates.length} 个\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移单个模板
|
||||
* @param template 模板实例
|
||||
* @returns 是否创建了新记录
|
||||
*/
|
||||
async migrateTemplate(template: any): Promise<boolean> {
|
||||
// 检查模板是否已存在
|
||||
const existing = await this.templateRepository.findOne({
|
||||
where: { code: template.code }
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return false; // 已存在,跳过
|
||||
}
|
||||
|
||||
// 创建新的实体
|
||||
const entity = new N8nTemplateEntity();
|
||||
|
||||
// 基础属性
|
||||
entity.code = template.code;
|
||||
entity.name = template.name;
|
||||
entity.description = template.description;
|
||||
entity.creditCost = template.creditCost;
|
||||
entity.version = template.version;
|
||||
entity.inputExampleUrl = template.input;
|
||||
entity.outputExampleUrl = template.output;
|
||||
entity.tags = template.tags;
|
||||
entity.templateClass = template.constructor.name;
|
||||
|
||||
// 判断模板类型并设置相应属性
|
||||
if (template instanceof N8nImageGenerateTemplate) {
|
||||
entity.templateType = TemplateType.IMAGE;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
} else if (template instanceof N8nVideoGenerateTemplate) {
|
||||
entity.templateType = TemplateType.VIDEO;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
entity.videoModel = template.videoModel;
|
||||
entity.videoPrompt = template.videoPrompt;
|
||||
entity.duration = template.duration;
|
||||
entity.aspectRatio = template.aspectRatio;
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
await this.templateRepository.save(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有模板数据(谨慎使用)
|
||||
*/
|
||||
async clearAllTemplates(): Promise<void> {
|
||||
console.log('⚠️ 清空所有模板数据...');
|
||||
await this.templateRepository.clear();
|
||||
console.log('✅ 所有模板数据已清空');
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步现有模板配置(更新已存在的模板)
|
||||
*/
|
||||
async syncExistingTemplates(): Promise<void> {
|
||||
console.log('开始同步现有模板配置...');
|
||||
|
||||
const templates = [
|
||||
new PhotoRestoreTemplate(),
|
||||
new CharacterFigurineTemplate(),
|
||||
new PetFigurineTemplate(),
|
||||
new CosplayRealPersonTemplate(),
|
||||
new GarageOpeningTemplate(),
|
||||
new JapaneseMagazineTemplate(),
|
||||
new NuclearExplosionTemplate(),
|
||||
];
|
||||
|
||||
let updatedCount = 0;
|
||||
let createdCount = 0;
|
||||
|
||||
for (const template of templates) {
|
||||
try {
|
||||
const existing = await this.templateRepository.findOne({
|
||||
where: { code: template.code }
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// 更新现有模板
|
||||
await this.updateTemplateFromCode(existing, template);
|
||||
updatedCount++;
|
||||
console.log(`🔄 更新模板: ${template.code} - ${template.name}`);
|
||||
} else {
|
||||
// 创建新模板
|
||||
await this.migrateTemplate(template);
|
||||
createdCount++;
|
||||
console.log(`➕ 创建模板: ${template.code} - ${template.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 同步模板失败: ${template.code}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 同步完成统计:`);
|
||||
console.log(` • 更新模板: ${updatedCount} 个`);
|
||||
console.log(` • 创建模板: ${createdCount} 个`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从代码模板更新数据库实体
|
||||
*/
|
||||
private async updateTemplateFromCode(entity: N8nTemplateEntity, template: any): Promise<void> {
|
||||
// 更新基础属性
|
||||
entity.name = template.name;
|
||||
entity.description = template.description;
|
||||
entity.creditCost = template.creditCost;
|
||||
entity.version = template.version;
|
||||
entity.inputExampleUrl = template.input;
|
||||
entity.outputExampleUrl = template.output;
|
||||
entity.tags = template.tags;
|
||||
entity.templateClass = template.constructor.name;
|
||||
|
||||
// 更新类型特定属性
|
||||
if (template instanceof N8nImageGenerateTemplate) {
|
||||
entity.templateType = TemplateType.IMAGE;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
} else if (template instanceof N8nVideoGenerateTemplate) {
|
||||
entity.templateType = TemplateType.VIDEO;
|
||||
entity.imageModel = template.imageModel;
|
||||
entity.imagePrompt = template.imagePrompt;
|
||||
entity.videoModel = template.videoModel;
|
||||
entity.videoPrompt = template.videoPrompt;
|
||||
entity.duration = template.duration;
|
||||
entity.aspectRatio = template.aspectRatio;
|
||||
}
|
||||
|
||||
await this.templateRepository.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取迁移状态报告
|
||||
*/
|
||||
async getMigrationReport(): Promise<{
|
||||
totalInDatabase: number;
|
||||
totalInCode: number;
|
||||
missingInDatabase: string[];
|
||||
extraInDatabase: string[];
|
||||
}> {
|
||||
// 数据库中的模板
|
||||
const dbTemplates = await this.templateRepository.find({
|
||||
select: ['code', 'name']
|
||||
});
|
||||
|
||||
// 代码中的模板
|
||||
const codeTemplates = [
|
||||
new PhotoRestoreTemplate(),
|
||||
new CharacterFigurineTemplate(),
|
||||
new PetFigurineTemplate(),
|
||||
new CosplayRealPersonTemplate(),
|
||||
new GarageOpeningTemplate(),
|
||||
new JapaneseMagazineTemplate(),
|
||||
new NuclearExplosionTemplate(),
|
||||
];
|
||||
|
||||
const dbCodes = new Set(dbTemplates.map(t => t.code));
|
||||
const codeCodes = new Set(codeTemplates.map(t => t.code));
|
||||
|
||||
// 找出差异
|
||||
const missingInDatabase = codeTemplates
|
||||
.filter(t => !dbCodes.has(t.code))
|
||||
.map(t => `${t.code} (${t.name})`);
|
||||
|
||||
const extraInDatabase = dbTemplates
|
||||
.filter(t => !codeCodes.has(t.code as any))
|
||||
.map(t => `${t.code} (${t.name})`);
|
||||
|
||||
return {
|
||||
totalInDatabase: dbTemplates.length,
|
||||
totalInCode: codeTemplates.length,
|
||||
missingInDatabase,
|
||||
extraInDatabase
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印迁移状态报告
|
||||
*/
|
||||
async printMigrationReport(): Promise<void> {
|
||||
const report = await this.getMigrationReport();
|
||||
|
||||
console.log('\n📋 模板迁移状态报告:');
|
||||
console.log('='.repeat(50));
|
||||
console.log(`数据库中的模板数量: ${report.totalInDatabase}`);
|
||||
console.log(`代码中的模板数量: ${report.totalInCode}`);
|
||||
|
||||
if (report.missingInDatabase.length > 0) {
|
||||
console.log('\n❌ 缺失在数据库中的模板:');
|
||||
report.missingInDatabase.forEach(template => {
|
||||
console.log(` • ${template}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (report.extraInDatabase.length > 0) {
|
||||
console.log('\n⚠️ 数据库中多余的模板:');
|
||||
report.extraInDatabase.forEach(template => {
|
||||
console.log(` • ${template}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (report.missingInDatabase.length === 0 && report.extraInDatabase.length === 0) {
|
||||
console.log('\n✅ 数据库与代码模板完全同步!');
|
||||
}
|
||||
|
||||
console.log('='.repeat(50));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user