/** * 视频转码统计 API * * GET: 获取视频转码队列状况、失败任务等统计数据 * * 参考:flnxi_sucai/app/api/admin/video-transcode-stats/route.ts * 适配当前系统使用 materialType 字段 */ import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' import { prisma } from '@/lib/prisma' import { LEGACY_COMPATIBLE_VIDEO_MATERIAL_TYPES } from '@/lib/video-material-types' import { USER_TRANSCODE_FAILURE_MESSAGE } from '@/lib/video-transcode-task' export async function GET(req: NextRequest) { try { const session = await getServerSession(authOptions) // 检查管理员权限 if (!session || (session.user as any).role !== 'ADMIN') { return NextResponse.json( { error: '无权限' }, { status: 403 } ) } // 识别视频素材类型 const videoMaterialTypes = [...LEGACY_COMPATIBLE_VIDEO_MATERIAL_TYPES] // 获取视频素材统计(兼容旧数据库) let videoStats: any = {} try { // 查询所有视频素材 const videoMaterials = await prisma.material.findMany({ where: { AND: [ { materialType: { in: videoMaterialTypes, }, }, { deletedAt: null, // 只统计未删除的素材 } as any, ], } as any, select: { id: true, materialType: true, videoProcessingStatus: true, transcodeLastError: true, transcodeAttempts: true, createdAt: true, } as any, // 使用 as any 兼容旧数据库 }) // 按转码状态分组统计 const statusCounts = { pending: 0, // 待转码 processing: 0, // 转码中 completed: 0, // 转码完成 failed: 0, // 转码失败 unknown: 0, // 状态未知 } const failedTasks: any[] = [] const recentTasks: any[] = [] videoMaterials.forEach((material: any) => { const status = material.videoProcessingStatus || 'unknown' switch (status) { case 'pending': case 'queued': statusCounts.pending++ break case 'processing': case 'transcoding': statusCounts.processing++ break case 'completed': case 'success': statusCounts.completed++ break case 'failed': case 'error': statusCounts.failed++ failedTasks.push({ id: material.id, materialType: material.materialType, error: USER_TRANSCODE_FAILURE_MESSAGE, attempts: material.transcodeAttempts || 0, createdAt: material.createdAt, }) break default: statusCounts.unknown++ } // 收集最近的任务(最近24小时) const oneDayAgo = new Date() oneDayAgo.setDate(oneDayAgo.getDate() - 1) if (material.createdAt && new Date(material.createdAt) > oneDayAgo) { recentTasks.push({ id: material.id, materialType: material.materialType, status: material.videoProcessingStatus || 'unknown', createdAt: material.createdAt, }) } }) videoStats = { total: videoMaterials.length, byStatus: statusCounts, byType: videoMaterialTypes.reduce((acc, type) => { acc[type] = videoMaterials.filter((m: any) => m.materialType === type).length return acc }, {} as Record), failedTasks: failedTasks.slice(0, 10), // 最多返回10个失败任务 recentTasks: recentTasks.slice(0, 20), // 最多返回20个最近任务 } } catch (error: any) { // 兼容旧数据库:如果视频相关字段不存在 if (error?.meta?.column?.includes('videoProcessingStatus') || error?.meta?.column?.includes('deletedAt')) { console.warn('[Admin] Video fields not found, using fallback query') // 降级查询:只统计视频素材数量 const videoMaterials = await prisma.material.findMany({ where: { materialType: { in: videoMaterialTypes, }, }, select: { id: true, materialType: true, createdAt: true, }, }) videoStats = { total: videoMaterials.length, byStatus: { pending: 0, processing: 0, completed: videoMaterials.length, // 假设都已完成 failed: 0, unknown: 0, }, byType: videoMaterialTypes.reduce((acc, type) => { acc[type] = videoMaterials.filter(m => m.materialType === type).length return acc }, {} as Record), failedTasks: [], recentTasks: videoMaterials.slice(0, 20).map(m => ({ id: m.id, materialType: m.materialType, status: 'completed', createdAt: m.createdAt, })), } } else { throw error } } // 系统资源使用情况 const systemResources = { memory: process.memoryUsage(), uptime: process.uptime(), timestamp: new Date().toISOString(), } return NextResponse.json({ video: videoStats, system: systemResources, }) } catch (error) { console.error('[Admin] Video transcode stats error:', error) return NextResponse.json( { error: '获取转码统计失败' }, { status: 500 } ) } }