2026-05-07 16:37:22 +08:00

190 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 视频转码统计 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'
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: material.transcodeLastError,
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<string, number>),
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<string, number>),
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 }
)
}
}