refactor: 移除模板迁移API接口,改为migrations阶段处理

- 删除 template-migration.service.ts 文件
- 移除控制器中的迁移相关API接口 (/migrate, /sync, /admin/migration-report)
- 清理文档中的迁移服务相关内容
- 添加 data-source.ts 配置文件用于运行数据库迁移
- 模板迁移功能完全通过数据库migration脚本处理
This commit is contained in:
imeepos
2025-09-04 15:36:26 +08:00
parent f83859b518
commit cf6844ad6e
5 changed files with 23 additions and 432 deletions

16
data-source.ts Normal file
View 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',
});

View File

@@ -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);
}
}
}
}
```
## ✅ 架构优势

View File

@@ -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 {}

View File

@@ -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
);
}
}
}

View File

@@ -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));
}
}