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, @InjectRepository(TemplateExecutionEntity) private executionRepository: Repository, ) { } /** * 创建图片模板实例 * @param templateId 模板ID * @returns 初始化后的图片模板实例 */ async createImageTemplate(templateId: number): Promise { const instance = new DynamicN8nImageTemplate(templateId, this.templateRepository); return await instance.onInit(); } /** * 创建视频模板实例 * @param templateId 模板ID * @returns 初始化后的视频模板实例 */ async createVideoTemplate(templateId: number): Promise { const instance = new DynamicN8nVideoTemplate(templateId, this.templateRepository); return await instance.onInit(); } /** * 通过模板代码创建实例 * @param code 模板代码 * @returns 对应类型的模板实例 */ async createTemplateByCode( code: string ): Promise { 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 { 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 { return this.templateRepository.find({ where: { isActive: true }, order: { sortOrder: 'DESC', createdAt: 'DESC' } }); } /** * 按类型获取模板配置 * @param type 模板类型 * @returns 指定类型的模板配置列表 */ async getTemplatesByType(type: TemplateType): Promise { return this.templateRepository.find({ where: { templateType: type, isActive: true }, order: { sortOrder: 'DESC', createdAt: 'DESC' } }); } /** * 获取图片模板配置列表 * @returns 图片类型的模板配置 */ async getImageTemplates(): Promise { return this.getTemplatesByType(TemplateType.IMAGE); } /** * 获取视频模板配置列表 * @returns 视频类型的模板配置 */ async getVideoTemplates(): Promise { return this.getTemplatesByType(TemplateType.VIDEO); } /** * 通过代码获取模板配置 * @param code 模板代码 * @returns 模板配置信息 */ async getTemplateByCode(code: string): Promise { return this.templateRepository.findOne({ where: { code, isActive: true } }); } /** * 批量执行模板 * @param templateId 模板ID * @param imageUrls 图片URL数组 * @returns 执行结果数组 */ async batchExecute(templateId: number, imageUrls: string[]): Promise { 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 { // 这里可以实现更复杂的推荐算法 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): Promise { const execution = this.executionRepository.create(data); return this.executionRepository.save(execution); } /** * 更新执行记录 * @param id 执行记录ID * @param data 更新数据 * @returns 更新后的执行记录 */ async updateExecution(id: number, data: Partial): Promise { 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 { 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') }; } }