From 4c4d14724ac9d531d5f3f0f21791ca1764c95882 Mon Sep 17 00:00:00 2001 From: WangDL Date: Fri, 22 May 2026 22:27:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M0-02=20Admin=20event=20management=20AA?= =?UTF-8?q?PI=20=E2=80=94=20queues=20overview=20+=20failed=20+=20retry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 2 + .../admin-events/admin-events.controller.ts | 87 +++++++++++++++++++ .../admin-events/admin-events.module.ts | 17 ++++ 3 files changed, 106 insertions(+) create mode 100644 src/modules/admin-events/admin-events.controller.ts create mode 100644 src/modules/admin-events/admin-events.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index 523012c..77d11a2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -16,6 +16,7 @@ import { AuthModule } from './modules/auth/auth.module'; import { AdminAuthModule } from './modules/admin-auth/admin-auth.module'; import { AdminDashboardModule } from './modules/admin-dashboard/admin-dashboard.module'; import { AdminUsersModule } from './modules/admin-users/admin-users.module'; +import { AdminEventsModule } from './modules/admin-events/admin-events.module'; import { AdminKnowledgeModule } from './modules/admin-knowledge/admin-knowledge.module'; import { AdminCostsModule } from './modules/admin-costs/admin-costs.module'; import { AdminBillingModule } from './modules/admin-billing/admin-billing.module'; @@ -96,6 +97,7 @@ import appleConfig from './config/apple.config'; AdminAuthModule, AdminDashboardModule, AdminUsersModule, + AdminEventsModule, AdminKnowledgeModule, AdminCostsModule, AdminBillingModule, diff --git a/src/modules/admin-events/admin-events.controller.ts b/src/modules/admin-events/admin-events.controller.ts new file mode 100644 index 0000000..7d6e6c8 --- /dev/null +++ b/src/modules/admin-events/admin-events.controller.ts @@ -0,0 +1,87 @@ +import { Controller, Get, Post, Param, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { InjectQueue, InjectFlowProducer } from '@nestjs/bullmq'; +import { Queue, FlowProducer, Job } from 'bullmq'; +import { AdminAuthGuard } from '../../common/guards/admin-auth.guard'; +import { AdminRolesGuard } from '../../common/guards/admin-roles.guard'; +import { AdminRoles } from '../../common/decorators/admin-roles.decorator'; +import type { AdminRole } from '../../common/types/admin-role.enum'; + +const QUEUES = ['ai-analysis', 'document-import', 'notification', 'domain-events'] as const; + +@ApiTags('admin-events') +@Controller('admin-api/events') +@UseGuards(AdminAuthGuard, AdminRolesGuard) +@ApiBearerAuth() +export class AdminEventsController { + constructor( + @InjectQueue('ai-analysis') private aiQ: Queue, + @InjectQueue('document-import') private importQ: Queue, + @InjectQueue('notification') private notifyQ: Queue, + @InjectQueue('domain-events') private eventQ: Queue, + ) {} + + @Get() + @AdminRoles('SUPER_ADMIN' as AdminRole) + @ApiOperation({ summary: '队列概览' }) + async overview() { + const queues = await Promise.all( + QUEUES.map(async (name) => { + const q = this.getQueue(name); + const [waiting, active, completed, failed, delayed] = await Promise.all([ + q.getWaitingCount(), q.getActiveCount(), q.getCompletedCount(), + q.getFailedCount(), q.getDelayedCount(), + ]); + return { name, waiting, active, completed, failed, delayed, total: waiting + active + completed + failed + delayed }; + }), + ); + return { queues }; + } + + @Get(':queue/failed') + @AdminRoles('SUPER_ADMIN' as AdminRole) + @ApiOperation({ summary: '失败任务列表' }) + async failed(@Param('queue') queueName: string) { + const q = this.getQueue(queueName); + const jobs = await q.getFailed(0, 20); + return { jobs: jobs.map(this.formatJob) }; + } + + @Get(':queue/jobs/:jobId') + @AdminRoles('SUPER_ADMIN' as AdminRole) + @ApiOperation({ summary: '任务详情' }) + async jobDetail(@Param('queue') queueName: string, @Param('jobId') jobId: string) { + const q = this.getQueue(queueName); + const job = await q.getJob(jobId); + if (!job) return { error: 'Job not found' }; + const state = await job.getState(); + return { ...this.formatJob(job), state, data: job.data, stacktrace: job.stacktrace }; + } + + @Post(':queue/jobs/:jobId/retry') + @AdminRoles('SUPER_ADMIN' as AdminRole) + @ApiOperation({ summary: '重试失败任务' }) + async retry(@Param('queue') queueName: string, @Param('jobId') jobId: string) { + const q = this.getQueue(queueName); + const job = await q.getJob(jobId); + if (!job) return { error: 'Job not found' }; + await job.retry(); + return { success: true }; + } + + private getQueue(name: string): Queue { + const map: Record = { + 'ai-analysis': this.aiQ, 'document-import': this.importQ, + 'notification': this.notifyQ, 'domain-events': this.eventQ, + }; + return map[name] || this.eventQ; + } + + private formatJob(job: Job) { + return { + id: job.id, name: job.name, timestamp: job.timestamp, + attemptsMade: job.attemptsMade, failedReason: job.failedReason?.slice(0, 200), + finishedOn: job.finishedOn, processedOn: job.processedOn, + }; + } +} diff --git a/src/modules/admin-events/admin-events.module.ts b/src/modules/admin-events/admin-events.module.ts new file mode 100644 index 0000000..12d43b1 --- /dev/null +++ b/src/modules/admin-events/admin-events.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { AdminEventsController } from './admin-events.controller'; +import { AdminAuthGuard } from '../../common/guards/admin-auth.guard'; +import { AdminRolesGuard } from '../../common/guards/admin-roles.guard'; + +@Module({ + imports: [ + BullModule.registerQueue( + { name: 'ai-analysis' }, { name: 'document-import' }, + { name: 'notification' }, { name: 'domain-events' }, + ), + ], + controllers: [AdminEventsController], + providers: [AdminAuthGuard, AdminRolesGuard], +}) +export class AdminEventsModule {}