refactor: 优化模板执行API并移除重复控制器

- 简化模板执行流程,移除后端图片审核逻辑,改为前端预审核
- 删除增强版模板控制器(enhanced-template.controller.ts),统一使用标准控制器
- 更新API文档,明确executionId、taskId、auditTaskId的使用场景
- 修复执行进度查询接口,使用executionId作为主要查询参数
- 完善单元测试,确保API变更后功能正常
- 添加CurrentUser装饰器,标准化用户信息获取方式
This commit is contained in:
imeepos
2025-09-08 10:55:43 +08:00
parent c250ba43ef
commit 6ef0fe5bb1
11 changed files with 231 additions and 585 deletions

View File

@@ -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模板系统"
]

View File

@@ -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],
})

View File

@@ -20,7 +20,8 @@ describe('ContentModerationController', () => {
const mockUser = {
userId: 'test-user-id',
username: 'testuser',
platform: 'weixin',
unifiedUserId: 'unified-test-id',
};
const mockAuditResult: ContentAuditResult = {

View File

@@ -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, '查询成功');
}

View File

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

View File

@@ -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<TemplateExecutionEntity>,
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<void> {
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<void> {
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<void> {
const updateData: Partial<TemplateExecutionEntity> = {
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<void> {
// 复用原有逻辑
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,
);
}
}
}
}

View File

@@ -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<N8nTemplateEntity>,
@InjectRepository(TemplateExecutionEntity)
private readonly executionRepository: Repository<TemplateExecutionEntity>,
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<ApiResponse<any>> {
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<ApiResponse<any>> {
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<void> {
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<void> {
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();
}

View File

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

View File

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

View File

@@ -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. 测试微信适配器审核流程(模拟)

View File

@@ -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模板系统"
]