Files
bw-mini-app-server/src/services/n8n-template-factory.service.ts
imeepos de2858012d feat: 实现模板执行记录系统和完整的CRUD API接口
- 新增TemplateExecutionEntity实体替代原有的GenerationTask方式
- 实现模板执行记录的完整生命周期管理,包含状态跟踪和性能指标
- 添加模板管理的完整CRUD操作API(创建、更新、删除、查询)
- 更新数据库迁移文件,包含n8n_templates和template_executions表
- 完善模板工厂服务,支持执行记录管理和统计分析
- 重构DynamicN8nImageTemplate和DynamicN8nVideoTemplate类名
- 添加用户相关实体:User、PlatformUser、ExtensionData、UserCredit等
- 实现完整的数据库表结构和索引优化
2025-09-04 16:29:24 +08:00

275 lines
8.5 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { N8nTemplateEntity, TemplateType } from '../entities/n8n-template.entity';
import { TemplateExecutionEntity, ExecutionStatus } from '../entities/template-execution.entity';
import {
DynamicN8nImageTemplate,
DynamicN8nVideoTemplate
} from '../templates/n8n-dynamic-template';
/**
* N8n模板工厂服务
* 负责创建动态模板实例,支持从数据库加载配置
*/
@Injectable()
export class N8nTemplateFactoryService {
constructor(
@InjectRepository(N8nTemplateEntity)
private templateRepository: Repository<N8nTemplateEntity>,
@InjectRepository(TemplateExecutionEntity)
private executionRepository: Repository<TemplateExecutionEntity>,
) { }
/**
* 创建图片模板实例
* @param templateId 模板ID
* @returns 初始化后的图片模板实例
*/
async createImageTemplate(templateId: number): Promise<DynamicN8nImageTemplate> {
const instance = new DynamicN8nImageTemplate(templateId, this.templateRepository);
return await instance.onInit();
}
/**
* 创建视频模板实例
* @param templateId 模板ID
* @returns 初始化后的视频模板实例
*/
async createVideoTemplate(templateId: number): Promise<DynamicN8nVideoTemplate> {
const instance = new DynamicN8nVideoTemplate(templateId, this.templateRepository);
return await instance.onInit();
}
/**
* 通过模板代码创建实例
* @param code 模板代码
* @returns 对应类型的模板实例
*/
async createTemplateByCode(
code: string
): Promise<DynamicN8nImageTemplate | DynamicN8nVideoTemplate> {
const config = await this.templateRepository.findOne({
where: { code, isActive: true }
});
if (!config) {
throw new Error(`Template with code ${code} not found or inactive`);
}
if (config.templateType === TemplateType.IMAGE) {
return this.createImageTemplate(config.id);
} else {
return this.createVideoTemplate(config.id);
}
}
/**
* 通用模板创建方法
* @param templateId 模板ID
* @returns 对应类型的模板实例
*/
async createTemplate(
templateId: number
): Promise<DynamicN8nImageTemplate | DynamicN8nVideoTemplate> {
const config = await this.templateRepository.findOne({
where: { id: templateId, isActive: true }
});
if (!config) {
throw new Error(`Template with id ${templateId} not found or inactive`);
}
if (config.templateType === TemplateType.IMAGE) {
return this.createImageTemplate(templateId);
} else {
return this.createVideoTemplate(templateId);
}
}
/**
* 获取所有可用模板配置
* @returns 所有激活状态的模板配置
*/
async getAllTemplates(): Promise<N8nTemplateEntity[]> {
return this.templateRepository.find({
where: { isActive: true },
order: { sortOrder: 'DESC', createdAt: 'DESC' }
});
}
/**
* 按类型获取模板配置
* @param type 模板类型
* @returns 指定类型的模板配置列表
*/
async getTemplatesByType(type: TemplateType): Promise<N8nTemplateEntity[]> {
return this.templateRepository.find({
where: { templateType: type, isActive: true },
order: { sortOrder: 'DESC', createdAt: 'DESC' }
});
}
/**
* 获取图片模板配置列表
* @returns 图片类型的模板配置
*/
async getImageTemplates(): Promise<N8nTemplateEntity[]> {
return this.getTemplatesByType(TemplateType.IMAGE);
}
/**
* 获取视频模板配置列表
* @returns 视频类型的模板配置
*/
async getVideoTemplates(): Promise<N8nTemplateEntity[]> {
return this.getTemplatesByType(TemplateType.VIDEO);
}
/**
* 通过代码获取模板配置
* @param code 模板代码
* @returns 模板配置信息
*/
async getTemplateByCode(code: string): Promise<N8nTemplateEntity | null> {
return this.templateRepository.findOne({
where: { code, isActive: true }
});
}
/**
* 批量执行模板
* @param templateId 模板ID
* @param imageUrls 图片URL数组
* @returns 执行结果数组
*/
async batchExecute(templateId: number, imageUrls: string[]): Promise<string[]> {
const template = await this.createTemplate(templateId);
return Promise.all(imageUrls.map(url => template.execute(url)));
}
/**
* 根据用户偏好推荐模板(示例实现)
* @param userTags 用户偏好标签
* @param limit 推荐数量限制
* @returns 推荐的模板配置
*/
async recommendTemplates(userTags: string[], limit: number = 5): Promise<N8nTemplateEntity[]> {
// 这里可以实现更复杂的推荐算法
const query = this.templateRepository
.createQueryBuilder('template')
.where('template.isActive = :isActive', { isActive: true })
.orderBy('template.sortOrder', 'DESC')
.addOrderBy('template.createdAt', 'DESC')
.limit(limit);
// 如果提供了用户标签,可以基于标签匹配进行推荐
if (userTags.length > 0) {
query.andWhere(
'JSON_OVERLAPS(template.tags, :userTags)',
{ userTags: JSON.stringify(userTags) }
);
}
return query.getMany();
}
/**
* 创建执行记录
* @param data 执行记录数据
* @returns 创建的执行记录
*/
async createExecution(data: Partial<TemplateExecutionEntity>): Promise<TemplateExecutionEntity> {
const execution = this.executionRepository.create(data);
return this.executionRepository.save(execution);
}
/**
* 更新执行记录
* @param id 执行记录ID
* @param data 更新数据
* @returns 更新后的执行记录
*/
async updateExecution(id: number, data: Partial<TemplateExecutionEntity>): Promise<TemplateExecutionEntity> {
await this.executionRepository.update(id, data);
const item = await this.executionRepository.findOne({ where: { id } });
if (item) return item;
throw new Error(`not found ${id} from template execution table`)
}
/**
* 获取用户执行记录
* @param userId 用户ID
* @param limit 记录数量限制
* @returns 用户的执行记录列表
*/
async getUserExecutions(userId: string, limit = 20): Promise<TemplateExecutionEntity[]> {
return this.executionRepository.find({
where: { userId },
relations: ['template'],
order: { createdAt: 'DESC' },
take: limit
});
}
/**
* 获取模板执行统计
* @param templateId 模板ID
* @returns 执行统计信息
*/
async getTemplateExecutionStats(templateId: number): Promise<{
totalUsage: number;
successRate: number;
averageExecutionTime: number;
totalCreditCost: number;
}> {
const stats = await this.executionRepository
.createQueryBuilder('execution')
.select('execution.status', 'status')
.addSelect('COUNT(*)', 'count')
.addSelect('AVG(execution.executionDuration)', 'avgDuration')
.addSelect('SUM(execution.creditCost)', 'totalCredit')
.where('execution.templateId = :templateId', { templateId })
.groupBy('execution.status')
.getRawMany();
const total = stats.reduce((sum, stat) => sum + parseInt(stat.count), 0);
const completed = stats.find(stat => stat.status === 'completed');
const successRate = total > 0 ? (parseInt(completed?.count || '0') / total) : 1.0;
const avgTime = completed ? parseFloat(completed.avgDuration || '0') : 0;
const totalCredit = stats.reduce((sum, stat) => sum + parseInt(stat.totalCredit || '0'), 0);
return {
totalUsage: total,
successRate,
averageExecutionTime: avgTime,
totalCreditCost: totalCredit
};
}
/**
* 获取用户积分消耗统计
* @param userId 用户ID
* @returns 积分消耗统计
*/
async getUserCreditStats(userId: string): Promise<{
totalCreditSpent: number;
totalExecutions: number;
averageCreditPerExecution: number;
}> {
const stats = await this.executionRepository
.createQueryBuilder('execution')
.select('COUNT(*)', 'totalExecutions')
.addSelect('SUM(execution.creditCost)', 'totalCreditSpent')
.addSelect('AVG(execution.creditCost)', 'avgCreditPerExecution')
.where('execution.userId = :userId', { userId })
.andWhere('execution.status = :status', { status: ExecutionStatus.COMPLETED })
.getRawOne();
return {
totalCreditSpent: parseInt(stats.totalCreditSpent || '0'),
totalExecutions: parseInt(stats.totalExecutions || '0'),
averageCreditPerExecution: parseFloat(stats.avgCreditPerExecution || '0')
};
}
}