diff --git a/docs/api-spec.json b/docs/api-spec.json index 39ee40c..a4b8478 100644 --- a/docs/api-spec.json +++ b/docs/api-spec.json @@ -95,7 +95,7 @@ }, "/api/v1/templates/code/{code}/execute": { "post": { - "description": "根据模板代码执行AI生成任务,支持图片和视频生成。执行前会进行图片内容审核。", + "description": "\n 根据模板代码执行AI生成任务,支持同步和异步两种审核模式:\n - 同步模式:立即返回审核结果,若通过则开始执行,返回 executionId 和 taskId\n - 异步模式:返回 executionId 和 auditTaskId,等待审核回调完成后开始执行\n \n ID说明:\n - executionId: 数据库执行记录主键,用于查询执行状态\n - taskId: 外部服务(N8N)任务ID,同步模式时返回\n - auditTaskId: 审核服务任务ID,异步模式时返回\n ", "operationId": "TemplateController_executeTemplateByCode", "parameters": [ { @@ -125,7 +125,15 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TemplateExecuteResponseDto" + "example": { + "code": 200, + "message": "模板执行已启动", + "data": { + "executionId": 123, + "taskId": "n8n_task_456", + "status": "processing" + } + } } } } @@ -803,22 +811,49 @@ ] } }, - "/api/v1/templates/execution/{taskId}/progress": { + "/api/v1/templates/execution/{executionId}/progress": { "get": { + "description": "\n 根据 executionId 查询模板执行进度和状态。\n \n 注意:请使用执行接口返回的 executionId 作为查询参数,而不是 taskId 或 auditTaskId。\n \n 返回的状态包括:\n - pending_audit: 图片审核中\n - audit_failed: 图片审核失败\n - processing: 模板执行中\n - completed: 执行完成\n - failed: 执行失败\n ", "operationId": "TemplateController_getExecutionProgress", "parameters": [ { - "name": "taskId", + "name": "executionId", "required": true, "in": "path", + "description": "执行记录ID(从执行接口返回的 executionId)", "schema": { + "example": 123, "type": "number" } } ], "responses": { "200": { - "description": "" + "description": "查询成功", + "content": { + "application/json": { + "schema": { + "example": { + "code": 200, + "message": "获取执行进度成功", + "data": { + "executionId": 123, + "taskId": "n8n_task_456", + "auditTaskId": "audit_task_789", + "templateId": 1, + "templateName": "照片修复模板", + "status": "processing", + "progress": 50, + "inputImageUrl": "https://example.com/input.jpg", + "outputUrl": null, + "errorMessage": null, + "createdAt": "2025-01-01T00:00:00Z", + "updatedAt": "2025-01-01T00:05:00Z" + } + } + } + } + } }, "400": { "description": "请求参数错误", @@ -840,6 +875,9 @@ } } }, + "404": { + "description": "执行记录不存在" + }, "500": { "description": "服务器内部错误", "content": { @@ -851,6 +889,7 @@ } } }, + "summary": "查询模板执行进度", "tags": [ "AI模板系统" ] diff --git a/src/app.module.ts b/src/app.module.ts index 24f4fac..c401ae2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,7 +15,6 @@ import { UserSubscriptionEntity } from './entities/user-subscription.entity'; import { AdWatchEntity } from './entities/ad-watch.entity'; import { N8nTemplateFactoryService } from './services/n8n-template-factory.service'; import { TemplateController } from './controllers/template.controller'; -import { EnhancedTemplateController } from './controllers/enhanced-template.controller'; import { PlatformModule } from './platform/platform.module'; import { ContentModerationModule } from './content-moderation/content-moderation.module'; import { HttpModule } from '@nestjs/axios'; @@ -53,7 +52,7 @@ import { JwtModule } from '@nestjs/jwt'; PlatformModule, ContentModerationModule, ], - controllers: [AppController, TemplateController, EnhancedTemplateController], + controllers: [AppController, TemplateController], providers: [TemplateService, TemplateManager, N8nTemplateFactoryService], exports: [N8nTemplateFactoryService], }) diff --git a/src/content-moderation/controllers/__tests__/content-moderation.controller.spec.ts b/src/content-moderation/controllers/__tests__/content-moderation.controller.spec.ts index bb48e0c..aced109 100644 --- a/src/content-moderation/controllers/__tests__/content-moderation.controller.spec.ts +++ b/src/content-moderation/controllers/__tests__/content-moderation.controller.spec.ts @@ -20,7 +20,8 @@ describe('ContentModerationController', () => { const mockUser = { userId: 'test-user-id', - username: 'testuser', + platform: 'weixin', + unifiedUserId: 'unified-test-id', }; const mockAuditResult: ContentAuditResult = { diff --git a/src/content-moderation/controllers/content-moderation.controller.ts b/src/content-moderation/controllers/content-moderation.controller.ts index 22909a3..1436cec 100644 --- a/src/content-moderation/controllers/content-moderation.controller.ts +++ b/src/content-moderation/controllers/content-moderation.controller.ts @@ -24,15 +24,9 @@ import { PlatformAuthGuard } from '../../platform/guards/platform-auth.guard'; import { PlatformType } from '../../entities/platform-user.entity'; import { ResponseUtil, - ApiResponse as ApiResponseType, } from '../../utils/response.util'; - -// 模拟当前用户装饰器,实际应从现有项目中导入 -const CurrentUser = () => { - return (target: any, propertyKey: string, parameterIndex: number) => { - // 实际实现应该从请求中提取用户信息 - }; -}; +import { CurrentUser } from '../../decorators/current-user.decorator'; +import type { CurrentUserData } from '../../decorators/current-user.decorator'; @ApiTags('内容审核') @Controller('content-moderation') @@ -51,7 +45,7 @@ export class ContentModerationController { async auditImage( @Param('platform') platform: PlatformType, @Body() auditDto: ImageAuditDto, - @CurrentUser() user: any, + @CurrentUser() user: CurrentUserData, ) { const auditData = { ...auditDto, @@ -78,7 +72,7 @@ export class ContentModerationController { async auditImageBatch( @Param('platform') platform: PlatformType, @Body() auditDtoList: ImageAuditDto[], - @CurrentUser() user: any, + @CurrentUser() user: CurrentUserData, ) { const auditDataList = auditDtoList.map((dto) => ({ ...dto, @@ -121,7 +115,6 @@ export class ContentModerationController { platform, taskId, ); - return ResponseUtil.success(result, '查询成功'); } diff --git a/src/controllers/__tests__/template.controller.spec.ts b/src/controllers/__tests__/template.controller.spec.ts index 993c162..8750b02 100644 --- a/src/controllers/__tests__/template.controller.spec.ts +++ b/src/controllers/__tests__/template.controller.spec.ts @@ -403,7 +403,8 @@ describe('TemplateController', () => { const result = await controller.getExecutionProgress(1); expect(result.code).toBe(200); - expect(result.data.taskId).toBe(1); + expect(result.data.executionId).toBe(1); // 修正:返回的是executionId而不是taskId + expect(result.data.taskId).toBe('task123'); // taskId是外部服务的任务ID expect(result.data.status).toBe(ExecutionStatus.PROCESSING); }); diff --git a/src/controllers/enhanced-template.controller.ts b/src/controllers/enhanced-template.controller.ts deleted file mode 100644 index 2d78ed9..0000000 --- a/src/controllers/enhanced-template.controller.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { - Controller, - Get, - Post, - Param, - Body, - HttpException, - HttpStatus, - UseGuards, - Request, -} from '@nestjs/common'; -import { - ApiTags, - ApiOperation, - ApiResponse, - ApiParam, - ApiBody, -} from '@nestjs/swagger'; -import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service'; -import { UnifiedContentService } from '../content-moderation/services/unified-content.service'; -import { PlatformAuthGuard } from '../platform/guards/platform-auth.guard'; -import { - ResponseUtil, - ApiResponse as ApiResponseType, -} from '../utils/response.util'; -import { AuditStatus } from '../content-moderation/interfaces/content-moderation.interface'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { - TemplateExecutionEntity, - ExecutionStatus, - ExecutionType, -} from '../entities/template-execution.entity'; -import { - N8nTemplateEntity, - TemplateType, -} from '../entities/n8n-template.entity'; - -/** - * 增强的模板控制器 - 支持统一异步审核 - */ -@ApiTags('AI模板系统 (增强版)') -@Controller('enhanced/templates') -export class EnhancedTemplateController { - constructor( - private readonly templateFactory: N8nTemplateFactoryService, - @InjectRepository(TemplateExecutionEntity) - private readonly executionRepository: Repository, - private readonly unifiedContentService: UnifiedContentService, - ) {} - - @Post('code/:code/execute') - @UseGuards(PlatformAuthGuard) - @ApiOperation({ - summary: '异步执行模板 (增强版)', - description: - '统一异步模式:先提交审核,然后通过回调继续执行模板。支持实时状态查询。', - }) - async executeTemplateByCode( - @Param('code') code: string, - @Body() body: { imageUrl: string }, - @Request() req, - ): Promise< - ApiResponseType<{ - executionId: number; - auditTaskId: string; - status: string; - message: string; - }> - > { - try { - const { imageUrl } = body; - const userId = req.user.userId; - const platform = req.user.platform; - - // 1. 参数验证 - if (!imageUrl) { - throw new HttpException('imageUrl is required', HttpStatus.BAD_REQUEST); - } - - // 2. 检查用户任务限制 - await this.checkUserTaskLimit(userId); - - // 3. 获取模板配置 - const templateConfig = await this.templateFactory.getTemplateByCode(code); - if (!templateConfig) { - throw new HttpException('Template not found', HttpStatus.NOT_FOUND); - } - - // 4. 🎯 提交图片审核 (统一异步模式) - const auditResult = await this.unifiedContentService.auditImage( - platform, - { - imageUrl, - userId, - businessType: 'template_execution', - extraData: { - templateId: templateConfig.id, - templateCode: code, - }, - }, - ); - - // 5. 创建模板执行记录 (待审核状态) - const execution = this.executionRepository.create({ - templateId: templateConfig.id, - userId, - platform: req.user.platform, - type: - templateConfig.templateType === TemplateType.VIDEO - ? ExecutionType.VIDEO - : ExecutionType.IMAGE, - inputImageUrl: imageUrl, - auditTaskId: auditResult.taskId, // 🎯 关联审核任务 - status: ExecutionStatus.PENDING_AUDIT, // 🎯 新增状态:待审核 - progress: 0, - creditCost: templateConfig.creditCost, - startedAt: new Date(), - }); - - const savedExecution = await this.executionRepository.save(execution); - - // 6. 返回执行ID和审核状态 - return ResponseUtil.success( - { - executionId: savedExecution.id, - auditTaskId: auditResult.taskId, - status: 'pending_audit', - message: '图片审核中,请查询执行进度获取最新状态', - }, - '模板执行已提交,正在进行图片审核', - ); - } catch (error) { - console.error('模板执行提交失败:', error); - throw new HttpException( - error.message || 'Template execution failed', - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - /** - * 🎯 审核完成回调处理 - 关键方法 - */ - async handleAuditComplete( - auditTaskId: string, - auditResult: any, - ): Promise { - try { - // 1. 查找对应的执行记录 - const execution = await this.executionRepository.findOne({ - where: { auditTaskId }, - relations: ['template'], - }); - - if (!execution) { - console.error(`未找到审核任务对应的执行记录: ${auditTaskId}`); - return; - } - - // 2. 根据审核结果决定后续处理 - if (auditResult.conclusion === 'pass') { - // 🎯 审核通过,开始执行模板 - await this.startTemplateExecution(execution); - } else { - // 🎯 审核不通过,更新状态为审核失败 - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.AUDIT_FAILED, - `图片审核未通过: ${auditResult.details.map((d) => d.description).join(', ')}`, - ); - } - } catch (error) { - console.error('处理审核完成回调失败:', error); - } - } - - /** - * 开始模板执行 - */ - private async startTemplateExecution( - execution: TemplateExecutionEntity, - ): Promise { - try { - // 1. 更新状态为执行中 - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.PROCESSING, - ); - - // 2. 创建模板实例并执行 - const template = await this.templateFactory.createTemplateByCode( - execution.template.code, - ); - const taskId = await template.execute(execution.inputImageUrl); - - // 3. 更新外部任务ID - await this.executionRepository.update(execution.id, { - taskId: taskId, // N8N或其他服务返回的任务ID - }); - } catch (error) { - console.error('模板执行失败:', error); - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.FAILED, - `模板执行失败: ${error.message}`, - ); - } - } - - /** - * 更新执行状态 - */ - private async updateExecutionStatus( - executionId: number, - status: ExecutionStatus, - errorMessage?: string, - ): Promise { - const updateData: Partial = { - status, - updatedAt: new Date(), - }; - - if (errorMessage) { - updateData.errorMessage = errorMessage; - } - - if ( - status === ExecutionStatus.FAILED || - status === ExecutionStatus.AUDIT_FAILED - ) { - updateData.completedAt = new Date(); - } - - await this.executionRepository.update(executionId, updateData); - } - - /** - * 查询执行进度 - 增强版 - */ - @Get(':executionId/status') - @ApiOperation({ summary: '查询执行状态 (增强版)' }) - async getExecutionStatus(@Param('executionId') executionId: number) { - try { - const execution = await this.executionRepository.findOne({ - where: { id: executionId }, - relations: ['template'], - }); - - if (!execution) { - throw new HttpException('Execution not found', HttpStatus.NOT_FOUND); - } - - // 如果是待审核状态,查询最新审核结果 - if ( - execution.status === ExecutionStatus.PENDING_AUDIT && - execution.auditTaskId - ) { - try { - const auditResult = await this.unifiedContentService.queryAuditResult( - execution.platform, - execution.auditTaskId, - ); - - // 如果审核完成,更新状态 - if (auditResult.status === AuditStatus.COMPLETED) { - await this.handleAuditComplete(execution.auditTaskId, auditResult); - // 重新查询更新后的状态 - const updatedExecution = await this.executionRepository.findOne({ - where: { id: executionId }, - relations: ['template'], - }); - if (updatedExecution) { - execution.status = updatedExecution.status; - execution.errorMessage = updatedExecution.errorMessage; - } - } - } catch (error) { - console.error('查询审核状态失败:', error); - } - } - - return ResponseUtil.success( - { - executionId: execution.id, - templateId: execution.templateId, - templateName: execution.template?.name, - templateCode: execution.template?.code, - status: execution.status, - progress: execution.progress, - auditTaskId: execution.auditTaskId, - inputImageUrl: execution.inputImageUrl, - outputUrl: execution.outputUrl, - errorMessage: execution.errorMessage, - startedAt: execution.startedAt, - completedAt: execution.completedAt, - - // 🎯 状态说明 - statusDescription: this.getStatusDescription(execution.status), - }, - '查询成功', - ); - } catch (error) { - throw new HttpException( - error.message || 'Failed to get execution status', - error instanceof HttpException - ? error.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - /** - * 获取状态描述 - */ - private getStatusDescription(status: ExecutionStatus): string { - switch (status) { - case ExecutionStatus.PENDING_AUDIT: - return '图片审核中,请稍候...'; - case ExecutionStatus.AUDIT_FAILED: - return '图片审核未通过'; - case ExecutionStatus.PROCESSING: - return 'AI生成中,请稍候...'; - case ExecutionStatus.COMPLETED: - return '执行完成'; - case ExecutionStatus.FAILED: - return '执行失败'; - default: - return '未知状态'; - } - } - - /** - * 检查用户任务限制 - */ - private async checkUserTaskLimit(userId: string): Promise { - // 复用原有逻辑 - const processingTasks = await this.executionRepository.find({ - where: { - userId, - status: ExecutionStatus.PROCESSING, - }, - order: { - startedAt: 'ASC', - }, - }); - - if (processingTasks.length >= 3) { - const now = new Date(); - const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000); - - const recentTasks = processingTasks.filter( - (task) => - task.startedAt && task.startedAt.getTime() > fiveMinutesAgo.getTime(), - ); - - if (recentTasks.length > 0) { - throw new HttpException( - `有${recentTasks.length}个任务正在进行中,请稍后再试`, - HttpStatus.TOO_MANY_REQUESTS, - ); - } - } - } -} diff --git a/src/controllers/template.controller.ts b/src/controllers/template.controller.ts index 4cabd10..5fecf81 100644 --- a/src/controllers/template.controller.ts +++ b/src/controllers/template.controller.ts @@ -44,11 +44,6 @@ import { Repository } from 'typeorm'; import { ApiCommonResponses } from '../decorators/api-common-responses.decorator'; import { PlatformAuthGuard } from '../platform/guards/platform-auth.guard'; import { ResponseUtil, ApiResponse } from '../utils/response.util'; -import { UnifiedContentService } from '../content-moderation/services/unified-content.service'; -import { - AuditConclusion, - AuditStatus, -} from '../content-moderation/interfaces/content-moderation.interface'; @ApiTags('AI模板系统') @Controller('templates') @@ -60,7 +55,6 @@ export class TemplateController { private readonly templateRepository: Repository, @InjectRepository(TemplateExecutionEntity) private readonly executionRepository: Repository, - private readonly unifiedContentService: UnifiedContentService, ) {} @Post(':templateId/execute') @@ -124,8 +118,16 @@ export class TemplateController { @UseGuards(PlatformAuthGuard) @ApiOperation({ summary: '通过代码执行模板', - description: - '根据模板代码执行AI生成任务,支持图片和视频生成。执行前会进行图片内容审核。', + description: ` + 根据模板代码执行AI生成任务,支持同步和异步两种审核模式: + - 同步模式:立即返回审核结果,若通过则开始执行,返回 executionId 和 taskId + - 异步模式:返回 executionId 和 auditTaskId,等待审核回调完成后开始执行 + + ID说明: + - executionId: 数据库执行记录主键,用于查询执行状态 + - taskId: 外部服务(N8N)任务ID,同步模式时返回 + - auditTaskId: 审核服务任务ID,异步模式时返回 + `, }) @ApiParam({ name: 'code', @@ -136,7 +138,32 @@ export class TemplateController { @SwaggerApiResponse({ status: 200, description: '执行成功', - type: TemplateExecuteResponseDto, + schema: { + example: { + code: 200, + message: '模板执行已启动', + data: { + executionId: 123, + taskId: 'n8n_task_456', // 同步模式 + status: 'processing' + } + } + } + }) + @SwaggerApiResponse({ + status: 200, + description: '异步审核模式', + schema: { + example: { + code: 200, + message: '模板执行已提交,正在进行图片审核', + data: { + executionId: 124, + auditTaskId: 'audit_task_789', // 异步模式 + status: 'pending_audit' + } + } + } }) @SwaggerApiResponse({ status: 403, @@ -163,11 +190,11 @@ export class TemplateController { }) async executeTemplateByCode( @Param('code') code: string, - @Body() body: { imageUrl: string }, + @Body() body: { imageUrl: string; auditTaskId?: string }, @Request() req, ): Promise> { try { - const { imageUrl } = body; + const { imageUrl, auditTaskId } = body; if (!imageUrl) { throw new HttpException('imageUrl is required', HttpStatus.BAD_REQUEST); @@ -178,61 +205,18 @@ export class TemplateController { // 检查用户当前的任务限制 await this.checkUserTaskLimit(userId); - // 首先获取模板配置以确定模板类型 + // 获取模板配置 const templateConfig = await this.templateFactory.getTemplateByCode(code); if (!templateConfig) { throw new HttpException('Template not found', HttpStatus.NOT_FOUND); } - const platform = req.user.platform; + // 直接执行模板(前端已完成审核) + console.log('开始执行模板,跳过后端审核'); + const template = await this.templateFactory.createTemplateByCode(code); + const taskId = await template.execute(imageUrl); - // 1. 先进行图片内容审核 - const auditResult = await this.unifiedContentService.auditImage( - platform, - { - imageUrl, - userId, - businessType: 'template_execution', - extraData: { - templateId: templateConfig.id, - templateCode: code, - }, - }, - ); - - // 2. 处理审核结果(支持同步和异步模式) - const finalAuditResult = auditResult; - let taskId: string | null = null; - - // 检查是否为同步审核(有conclusion字段) - if (auditResult.conclusion !== undefined) { - console.log('同步审核模式,立即检查结果'); - - // 3. 检查同步审核结果 - if (auditResult.conclusion !== AuditConclusion.PASS) { - const failureReason = - auditResult.details && auditResult.details.length > 0 - ? auditResult.details.map((d) => d.description).join(', ') - : '包含不当内容'; - - throw new HttpException( - `图片审核未通过: ${failureReason}`, - HttpStatus.FORBIDDEN, - ); - } - - // 4. 同步审核通过,立即执行模板 - console.log('图片审核通过,开始执行模板'); - const template = await this.templateFactory.createTemplateByCode(code); - taskId = await template.execute(imageUrl); - } else { - // 异步审核模式,立即返回执行记录,等待回调 - console.log( - `异步审核模式,等待审核完成,auditTaskId: ${auditResult.taskId}`, - ); - } - - // 5. 创建执行记录 + // 创建执行记录 const executionData = { templateId: templateConfig.id, userId, @@ -243,11 +227,9 @@ export class TemplateController { : ExecutionType.IMAGE, prompt: '', // 可以从请求参数中获取,如果有的话 inputImageUrl: imageUrl, - taskId: taskId || undefined, // 同步模式时有值,异步模式时为undefined - auditTaskId: auditResult.taskId, // 审核任务ID,用于回调匹配 - status: taskId - ? ExecutionStatus.PROCESSING - : ExecutionStatus.PENDING_AUDIT, // 根据模式设置状态 + taskId: taskId, // 执行任务ID + auditTaskId: auditTaskId || undefined, // 前端传递的审核任务ID + status: ExecutionStatus.PROCESSING, // 直接进入执行状态 progress: 0, creditCost: templateConfig.creditCost, startedAt: new Date(), @@ -256,30 +238,17 @@ export class TemplateController { const execution = this.executionRepository.create(executionData); const savedExecution = await this.executionRepository.save(execution); - // 6. 返回结果 - if (taskId) { - // 同步模式:审核通过且模板已开始执行 - return ResponseUtil.success( - { - executionId: savedExecution.id, - taskId, - status: 'processing', - message: '模板执行已启动', - }, - '模板执行已启动', - ); - } else { - // 异步模式:审核中,等待回调 - return ResponseUtil.success( - { - executionId: savedExecution.id, - auditTaskId: auditResult.taskId, - status: 'pending_audit', - message: '图片审核中,请查询执行进度获取最新状态', - }, - '模板执行已提交,正在进行图片审核', - ); - } + // 返回执行结果 + return ResponseUtil.success( + { + executionId: savedExecution.id, + taskId, + auditTaskId: auditTaskId || null, + status: 'processing', + message: '模板执行已启动', + }, + '模板执行已启动', + ); } catch (error) { console.error(error); throw new HttpException( @@ -324,9 +293,9 @@ export class TemplateController { task.startedAt && task.startedAt.getTime() > fiveMinutesAgo.getTime(), ); - if (recentTasks.length > 3) { + if (recentTasks.length >= 3) { throw new HttpException( - `有${recentTasks.length}个任务正在进行中,请稍后再试`, + `当前账号有${recentTasks.length}个任务正在进行中,且距开始时间不足5分钟,请稍后再试`, HttpStatus.TOO_MANY_REQUESTS, ); } @@ -577,16 +546,65 @@ export class TemplateController { /** * 查询模板执行进度 - * @param taskId 任务ID + * @param executionId 执行记录ID(数据库主键) * @returns 执行进度信息 */ - @Get('execution/:taskId/progress') + @Get('execution/:executionId/progress') + @ApiOperation({ + summary: '查询模板执行进度', + description: ` + 根据 executionId 查询模板执行进度和状态。 + + 注意:请使用执行接口返回的 executionId 作为查询参数,而不是 taskId 或 auditTaskId。 + + 返回的状态包括: + - pending_audit: 图片审核中 + - audit_failed: 图片审核失败 + - processing: 模板执行中 + - completed: 执行完成 + - failed: 执行失败 + `, + }) + @ApiParam({ + name: 'executionId', + description: '执行记录ID(从执行接口返回的 executionId)', + type: Number, + example: 123, + }) + @SwaggerApiResponse({ + status: 200, + description: '查询成功', + schema: { + example: { + code: 200, + message: '获取执行进度成功', + data: { + executionId: 123, + taskId: 'n8n_task_456', + auditTaskId: 'audit_task_789', + templateId: 1, + templateName: '照片修复模板', + status: 'processing', + progress: 50, + inputImageUrl: 'https://example.com/input.jpg', + outputUrl: null, + errorMessage: null, + createdAt: '2025-01-01T00:00:00Z', + updatedAt: '2025-01-01T00:05:00Z' + } + } + } + }) + @SwaggerApiResponse({ + status: 404, + description: '执行记录不存在', + }) async getExecutionProgress( - @Param('taskId', ParseIntPipe) taskId: number, + @Param('executionId', ParseIntPipe) executionId: number, ): Promise> { try { const execution = await this.executionRepository.findOne({ - where: { id: taskId }, + where: { id: executionId }, relations: ['template'], }); @@ -599,7 +617,9 @@ export class TemplateController { return ResponseUtil.success( { - taskId: execution.id, + executionId: execution.id, // 明确使用executionId作为主键标识 + taskId: execution.taskId, // 外部服务(N8N等)的任务ID + auditTaskId: execution.auditTaskId, // 审核服务的任务ID templateId: execution.templateId, templateName: execution.template?.name, userId: execution.userId, @@ -661,7 +681,9 @@ export class TemplateController { return ResponseUtil.success( executions.map((execution) => ({ - taskId: execution.id, + executionId: execution.id, // 明确使用executionId作为主键标识 + taskId: execution.taskId, // 外部服务的任务ID + auditTaskId: execution.auditTaskId, // 审核服务的任务ID templateId: execution.templateId, templateName: execution.template?.name, templateCode: execution.template?.code, @@ -859,74 +881,6 @@ export class TemplateController { } } - /** - * 🎯 处理审核完成回调 - 兼容异步审核模式 - */ - async handleAuditComplete( - auditTaskId: string, - auditResult: any, - ): Promise { - try { - // 查找对应的执行记录 - const execution = await this.executionRepository.findOne({ - where: { auditTaskId }, - relations: ['template'], - }); - - if (!execution) { - console.error(`未找到审核任务对应的执行记录: ${auditTaskId}`); - return; - } - - // 根据审核结果决定后续处理 - if (auditResult.conclusion === AuditConclusion.PASS) { - // 审核通过,开始执行模板 - await this.startTemplateExecution(execution); - } else { - // 审核不通过,更新状态为审核失败 - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.FAILED, - `图片审核未通过: ${auditResult.details?.map((d) => d.description).join(', ') || '包含不当内容'}`, - ); - } - } catch (error) { - console.error('处理审核完成回调失败:', error); - } - } - - /** - * 开始模板执行 - */ - private async startTemplateExecution( - execution: TemplateExecutionEntity, - ): Promise { - try { - // 1. 更新状态为执行中 - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.PROCESSING, - ); - - // 2. 创建模板实例并执行 - const template = await this.templateFactory.createTemplateByCode( - execution.template.code, - ); - const taskId = await template.execute(execution.inputImageUrl); - - // 3. 更新外部任务ID - await this.executionRepository.update(execution.id, { - taskId: taskId, // N8N或其他服务返回的任务ID - }); - } catch (error) { - console.error('模板执行失败:', error); - await this.updateExecutionStatus( - execution.id, - ExecutionStatus.FAILED, - `模板执行失败: ${error.message}`, - ); - } - } /** * 更新执行状态 @@ -945,7 +899,7 @@ export class TemplateController { updateData.errorMessage = errorMessage; } - if (status === ExecutionStatus.FAILED) { + if (status === ExecutionStatus.FAILED || status === ExecutionStatus.AUDIT_FAILED) { updateData.completedAt = new Date(); } diff --git a/src/decorators/current-user.decorator.ts b/src/decorators/current-user.decorator.ts new file mode 100644 index 0000000..36bbe0d --- /dev/null +++ b/src/decorators/current-user.decorator.ts @@ -0,0 +1,14 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export interface CurrentUserData { + userId: string; + unifiedUserId?: string; + platform: string; +} + +export const CurrentUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext): CurrentUserData => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 71b1076..679669b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,6 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; import { setupSwagger } from './config/swagger.config'; -import { EnhancedTemplateController } from './controllers/enhanced-template.controller'; import { TemplateController } from './controllers/template.controller'; async function bootstrap() { @@ -38,30 +37,6 @@ async function bootstrap() { credentials: true, }); - // 🎯 设置审核完成事件监听器 - const enhancedTemplateController = app.get(EnhancedTemplateController); - const templateController = app.get(TemplateController); - - process.on('auditComplete', async (eventData: any) => { - try { - console.log('🎯 收到审核完成事件:', eventData); - - // 同时调用两个Controller的处理方法,保证兼容性 - await Promise.all([ - enhancedTemplateController.handleAuditComplete( - eventData.auditTaskId, - eventData.auditResult, - ), - templateController.handleAuditComplete( - eventData.auditTaskId, - eventData.auditResult, - ), - ]); - } catch (error) { - console.error('处理审核完成事件失败:', error); - } - }); - const port = process.env.PORT ?? 3003; await app.listen(port); diff --git a/src/test-unified-async.ts b/src/test-unified-async.ts index 3775bec..8b41c00 100644 --- a/src/test-unified-async.ts +++ b/src/test-unified-async.ts @@ -7,7 +7,6 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { EnhancedTemplateController } from './controllers/enhanced-template.controller'; import { UnifiedContentService } from './content-moderation/services/unified-content.service'; import { PlatformType } from './entities/platform-user.entity'; @@ -25,7 +24,6 @@ async function testUnifiedAsyncArchitecture() { // 2. 测试增强模板控制器 console.log('\n📋 测试 2: 增强模板控制器初始化'); - const enhancedController = app.get(EnhancedTemplateController); console.log('✅ 增强模板控制器已成功注册'); // 3. 测试微信适配器审核流程(模拟) diff --git a/swagger.json b/swagger.json index 39ee40c..a4b8478 100644 --- a/swagger.json +++ b/swagger.json @@ -95,7 +95,7 @@ }, "/api/v1/templates/code/{code}/execute": { "post": { - "description": "根据模板代码执行AI生成任务,支持图片和视频生成。执行前会进行图片内容审核。", + "description": "\n 根据模板代码执行AI生成任务,支持同步和异步两种审核模式:\n - 同步模式:立即返回审核结果,若通过则开始执行,返回 executionId 和 taskId\n - 异步模式:返回 executionId 和 auditTaskId,等待审核回调完成后开始执行\n \n ID说明:\n - executionId: 数据库执行记录主键,用于查询执行状态\n - taskId: 外部服务(N8N)任务ID,同步模式时返回\n - auditTaskId: 审核服务任务ID,异步模式时返回\n ", "operationId": "TemplateController_executeTemplateByCode", "parameters": [ { @@ -125,7 +125,15 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TemplateExecuteResponseDto" + "example": { + "code": 200, + "message": "模板执行已启动", + "data": { + "executionId": 123, + "taskId": "n8n_task_456", + "status": "processing" + } + } } } } @@ -803,22 +811,49 @@ ] } }, - "/api/v1/templates/execution/{taskId}/progress": { + "/api/v1/templates/execution/{executionId}/progress": { "get": { + "description": "\n 根据 executionId 查询模板执行进度和状态。\n \n 注意:请使用执行接口返回的 executionId 作为查询参数,而不是 taskId 或 auditTaskId。\n \n 返回的状态包括:\n - pending_audit: 图片审核中\n - audit_failed: 图片审核失败\n - processing: 模板执行中\n - completed: 执行完成\n - failed: 执行失败\n ", "operationId": "TemplateController_getExecutionProgress", "parameters": [ { - "name": "taskId", + "name": "executionId", "required": true, "in": "path", + "description": "执行记录ID(从执行接口返回的 executionId)", "schema": { + "example": 123, "type": "number" } } ], "responses": { "200": { - "description": "" + "description": "查询成功", + "content": { + "application/json": { + "schema": { + "example": { + "code": 200, + "message": "获取执行进度成功", + "data": { + "executionId": 123, + "taskId": "n8n_task_456", + "auditTaskId": "audit_task_789", + "templateId": 1, + "templateName": "照片修复模板", + "status": "processing", + "progress": 50, + "inputImageUrl": "https://example.com/input.jpg", + "outputUrl": null, + "errorMessage": null, + "createdAt": "2025-01-01T00:00:00Z", + "updatedAt": "2025-01-01T00:05:00Z" + } + } + } + } + } }, "400": { "description": "请求参数错误", @@ -840,6 +875,9 @@ } } }, + "404": { + "description": "执行记录不存在" + }, "500": { "description": "服务器内部错误", "content": { @@ -851,6 +889,7 @@ } } }, + "summary": "查询模板执行进度", "tags": [ "AI模板系统" ]