Files
bw-mini-app-server/docs/template-hybrid-architecture.md
imeepos cf6844ad6e refactor: 移除模板迁移API接口,改为migrations阶段处理
- 删除 template-migration.service.ts 文件
- 移除控制器中的迁移相关API接口 (/migrate, /sync, /admin/migration-report)
- 清理文档中的迁移服务相关内容
- 添加 data-source.ts 配置文件用于运行数据库迁移
- 模板迁移功能完全通过数据库migration脚本处理
2025-09-04 15:36:26 +08:00

425 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI模板管理系统 - 混合式架构设计
## 🎯 核心设计理念
基于现有的优雅抽象类设计,实现**代码定义逻辑 + 数据库存储配置**的混合式架构:
- **代码层**:抽象类定义执行逻辑和接口规范
- **数据库层**:存储具体模板的配置信息
- **Entity层**:动态从数据库加载配置,创建模板实例
- **Migration层**:通过数据库迁移脚本初始化模板配置数据
## 🏗️ 架构层次分析
### 现有代码结构
```
Template (公共属性抽象类) [src/templates/types.ts:4-13]
├── ImageGenerateTemplate (图片生成抽象) [src/templates/types.ts:18-20]
│ └── N8nImageGenerateTemplate (N8n图片实现) [src/templates/n8nTemplate.ts:4-32]
│ ├── PhotoRestoreTemplate (具体配置) [src/templates/n8nTemplates/PhotoRestoreTemplate.ts]
│ ├── PetFigurineTemplate (具体配置)
│ └── CosplayRealPersonTemplate (具体配置)
└── VideoGenerateTemplate (视频生成抽象) [src/templates/types.ts:23-25]
└── N8nVideoGenerateTemplate (N8n视频实现) [src/templates/n8nTemplate.ts:35-74]
├── CharacterFigurineTemplate (具体配置) [src/templates/n8nTemplates/CharacterFigurineTemplate.ts]
├── GarageOpeningTemplate (具体配置)
└── NuclearExplosionTemplate (具体配置)
```
### 设计要点
1. **抽象类保持不变**Template、ImageGenerateTemplate、VideoGenerateTemplate、N8nTemplate系列
2. **具体配置存数据库**PhotoRestoreTemplate、CharacterFigurineTemplate等的配置信息
3. **动态实例化**通过Entity层从数据库加载配置创建模板实例
## 📊 数据库架构设计
### 1. 模板配置表 (n8n_templates)
```sql
CREATE TABLE n8n_templates (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
-- 基础配置 (对应Template抽象类)
code VARCHAR(100) NOT NULL UNIQUE COMMENT '模板唯一标识码',
name VARCHAR(200) NOT NULL COMMENT '模板名称',
description TEXT COMMENT '模板详细描述',
credit_cost INT NOT NULL DEFAULT 0 COMMENT '积分消耗',
version VARCHAR(50) NOT NULL COMMENT '版本号',
input_example_url TEXT COMMENT '输入示例图片',
output_example_url TEXT COMMENT '输出示例图片',
tags JSON COMMENT '标签数组',
-- 模板类型配置
template_type ENUM('image', 'video') NOT NULL COMMENT '模板类型',
template_class VARCHAR(100) NOT NULL COMMENT '具体模板类名',
-- N8n配置 (对应N8nTemplate系列)
image_model VARCHAR(100) COMMENT '图片生成模型',
image_prompt TEXT COMMENT '图片生成提示词',
video_model VARCHAR(100) COMMENT '视频生成模型',
video_prompt TEXT COMMENT '视频生成提示词',
duration INT COMMENT '视频时长(秒)',
aspect_ratio VARCHAR(20) COMMENT '视频比例',
-- 系统字段
is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用',
sort_order INT DEFAULT 0 COMMENT '排序权重',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_code (code),
KEY idx_type (template_type),
KEY idx_class (template_class),
KEY idx_active (is_active)
) COMMENT 'N8n模板配置表';
```
### 2. 初始化数据示例
```sql
INSERT INTO n8n_templates (
code, name, description, credit_cost, version,
input_example_url, output_example_url, tags,
template_type, template_class,
image_model, image_prompt,
video_model, video_prompt, duration, aspect_ratio
) VALUES
(
'photo_restore_v1',
'老照片修复上色',
'将黑白老照片修复并上色,让历史照片重现生机',
12, '1.0.0',
'https://file.302.ai/gpt/imgs/20250903/6033b7e701a64e4a97d4608d4a0ed308.jpg',
'https://file.302.ai/gpt/imgs/20250903/07ed5fb40090ac6ff51c874cc75ea8f2.jpg',
'["老照片", "修复", "上色", "黑白转彩色", "AI修复"]',
'image', 'PhotoRestoreTemplate',
'gemini-2.5-flash-image-preview',
'Restore this old photo and colorize the entire black and white photo',
NULL, NULL, NULL, NULL
),
(
'character_figurine_v1',
'人物手办',
'将人物照片制作成精细的角色手办模型,展示在收藏家房间中,并生成抚摸手办的视频',
28, '1.0.0',
'https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg',
'https://file.302.ai/gpt/imgs/20250828/2283106b31faf2066e1a72d955f65bca.jpg',
'["人物", "手办", "模型", "收藏", "PVC", "角色模型", "视频生成"]',
'video', 'CharacterFigurineTemplate',
'gemini-2.5-flash-image-preview',
'Transform this photo into a highly detailed character model...',
'302/MiniMax-Hailuo-02',
'一只手摸了摸手办的脑袋',
6, '9:16'
);
```
## 💾 Entity层设计
### 1. 模板配置实体
```typescript
// src/entities/n8n-template.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
export enum TemplateType {
IMAGE = 'image',
VIDEO = 'video'
}
@Entity('n8n_templates')
export class N8nTemplateEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
code: string;
@Column()
name: string;
@Column('text')
description: string;
@Column({ name: 'credit_cost' })
creditCost: number;
@Column()
version: string;
@Column({ name: 'input_example_url', nullable: true })
inputExampleUrl: string;
@Column({ name: 'output_example_url', nullable: true })
outputExampleUrl: string;
@Column('json', { nullable: true })
tags: string[];
@Column({ name: 'template_type', type: 'enum', enum: TemplateType })
templateType: TemplateType;
@Column({ name: 'template_class' })
templateClass: string;
@Column({ name: 'image_model', nullable: true })
imageModel: string;
@Column({ name: 'image_prompt', type: 'text', nullable: true })
imagePrompt: string;
@Column({ name: 'video_model', nullable: true })
videoModel: string;
@Column({ name: 'video_prompt', type: 'text', nullable: true })
videoPrompt: string;
@Column({ nullable: true })
duration: number;
@Column({ name: 'aspect_ratio', nullable: true })
aspectRatio: string;
@Column({ name: 'is_active', default: true })
isActive: boolean;
@Column({ name: 'sort_order', default: 0 })
sortOrder: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
### 2. 动态模板实体类
```typescript
// src/entities/n8n-template-instance.entity.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { N8nTemplateEntity, TemplateType } from './n8n-template.entity';
import { N8nImageGenerateTemplate, N8nVideoGenerateTemplate } from '../templates/n8nTemplate';
import axios from 'axios';
// 动态图片模板实体
export class N8nImageGenerateTemplateEntity extends N8nImageGenerateTemplate {
private config: N8nTemplateEntity;
constructor(
private templateId: number,
private templateRepository: Repository<N8nTemplateEntity>
) {
super();
}
async onInit(): Promise<N8nImageGenerateTemplateEntity> {
this.config = await this.templateRepository.findOne({
where: { id: this.templateId, templateType: TemplateType.IMAGE, isActive: true }
});
if (!this.config) {
throw new Error(`Template with id ${this.templateId} not found`);
}
return this;
}
// 从数据库动态获取配置
get code(): string { return this.config?.code || ''; }
get name(): string { return this.config?.name || ''; }
get description(): string { return this.config?.description || ''; }
get creditCost(): number { return this.config?.creditCost || 0; }
get version(): string { return this.config?.version || ''; }
get input(): string { return this.config?.inputExampleUrl || ''; }
get output(): string { return this.config?.outputExampleUrl || ''; }
get tags(): string[] { return this.config?.tags || []; }
get imageModel(): string { return this.config?.imageModel || ''; }
get imagePrompt(): string { return this.config?.imagePrompt || ''; }
// 执行方法继承自N8nImageGenerateTemplate无需修改
}
// 动态视频模板实体
export class N8nVideoGenerateTemplateEntity extends N8nVideoGenerateTemplate {
private config: N8nTemplateEntity;
constructor(
private templateId: number,
private templateRepository: Repository<N8nTemplateEntity>
) {
super();
}
async onInit(): Promise<N8nVideoGenerateTemplateEntity> {
this.config = await this.templateRepository.findOne({
where: { id: this.templateId, templateType: TemplateType.VIDEO, isActive: true }
});
if (!this.config) {
throw new Error(`Template with id ${this.templateId} not found`);
}
return this;
}
// 从数据库动态获取配置
get code(): string { return this.config?.code || ''; }
get name(): string { return this.config?.name || ''; }
get description(): string { return this.config?.description || ''; }
get creditCost(): number { return this.config?.creditCost || 0; }
get version(): string { return this.config?.version || ''; }
get input(): string { return this.config?.inputExampleUrl || ''; }
get output(): string { return this.config?.outputExampleUrl || ''; }
get tags(): string[] { return this.config?.tags || []; }
get imageModel(): string { return this.config?.imageModel || ''; }
get imagePrompt(): string { return this.config?.imagePrompt || ''; }
get videoModel(): string { return this.config?.videoModel || ''; }
get videoPrompt(): string { return this.config?.videoPrompt || ''; }
get duration(): number { return this.config?.duration || 6; }
get aspectRatio(): string { return this.config?.aspectRatio || '9:16'; }
// 执行方法继承自N8nVideoGenerateTemplate无需修改
}
```
### 3. 模板工厂服务
```typescript
// src/services/n8n-template-factory.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { N8nTemplateEntity, TemplateType } from '../entities/n8n-template.entity';
import { N8nImageGenerateTemplateEntity, N8nVideoGenerateTemplateEntity } from '../entities/n8n-template-instance.entity';
@Injectable()
export class N8nTemplateFactoryService {
constructor(
@InjectRepository(N8nTemplateEntity)
private templateRepository: Repository<N8nTemplateEntity>,
) {}
// 创建图片模板实例
async createImageTemplate(templateId: number): Promise<N8nImageGenerateTemplateEntity> {
const instance = new N8nImageGenerateTemplateEntity(templateId, this.templateRepository);
return await instance.onInit();
}
// 创建视频模板实例
async createVideoTemplate(templateId: number): Promise<N8nVideoGenerateTemplateEntity> {
const instance = new N8nVideoGenerateTemplateEntity(templateId, this.templateRepository);
return await instance.onInit();
}
// 通过模板代码创建实例
async createTemplateByCode(code: string): Promise<N8nImageGenerateTemplateEntity | N8nVideoGenerateTemplateEntity> {
const config = await this.templateRepository.findOne({
where: { code, isActive: true }
});
if (!config) {
throw new Error(`Template with code ${code} not found`);
}
if (config.templateType === TemplateType.IMAGE) {
return this.createImageTemplate(config.id);
} else {
return this.createVideoTemplate(config.id);
}
}
// 获取所有可用模板
async getAllTemplates(): Promise<N8nTemplateEntity[]> {
return this.templateRepository.find({
where: { isActive: true },
order: { sortOrder: 'DESC', createdAt: 'DESC' }
});
}
// 按类型获取模板
async getTemplatesByType(type: TemplateType): Promise<N8nTemplateEntity[]> {
return this.templateRepository.find({
where: { templateType: type, isActive: true },
order: { sortOrder: 'DESC', createdAt: 'DESC' }
});
}
}
```
## 🚀 使用示例
### 1. 您期望的使用方式
```typescript
// 通过ID创建并执行视频模板
const template = await new N8nVideoGenerateTemplateEntity(templateId, templateRepo)
.onInit()
.then(e => e.execute(imgUrl));
// 通过工厂服务创建
const factory = new N8nTemplateFactoryService(templateRepo);
const template = await factory.createVideoTemplate(1);
const result = await template.execute(imageUrl);
```
### 2. 在Controller中的使用
```typescript
// src/controllers/template.controller.ts
@Controller('templates')
export class TemplateController {
constructor(
private readonly templateFactory: N8nTemplateFactoryService,
) {}
@Post(':templateId/execute')
async executeTemplate(
@Param('templateId', ParseIntPipe) templateId: number,
@Body() { imageUrl }: { imageUrl: string }
) {
// 通过工厂服务创建模板实例并执行
const template = await this.templateFactory.createTemplate(templateId);
return await template.execute(imageUrl);
}
@Get()
async getAllTemplates() {
return this.templateFactory.getAllTemplates();
}
@Get('image')
async getImageTemplates() {
return this.templateFactory.getImageTemplates();
}
@Get('video')
async getVideoTemplates() {
return this.templateFactory.getVideoTemplates();
}
}
```
## ✅ 架构优势
### 1. 保持代码优势
- **类型安全**继承现有抽象类保持TypeScript类型检查
- **执行逻辑不变**N8nTemplate的execute方法逻辑完全保持不变
- **抽象层次清晰**Template → ImageGenerate/VideoGenerate → N8nTemplate → 具体实现
### 2. 新增数据库能力
- **配置动态化**:模板配置存储在数据库中,可在线修改
- **运营友好**:通过管理后台可以调整模板参数、启用/禁用模板
- **A/B测试**:可以创建同一功能的不同版本进行测试
- **统计分析**:可以跟踪每个模板的使用情况和效果
### 3. 使用体验优化
```typescript
// 简洁的API调用
const result = await new N8nVideoGenerateTemplateEntity(id, repo)
.onInit()
.then(e => e.execute(imgUrl));
// 工厂模式调用
const template = await templateFactory.createTemplateByCode('character_figurine_v1');
const result = await template.execute(imageUrl);
```
这种设计完美融合了您的面向对象架构优势和数据库配置管理的灵活性!