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

242 lines
7.0 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/system-status/route.ts
*/
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
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 }
)
}
// 计算时间范围(30天前和7天前)
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
// 并行查询基础数据
let newUsersThisMonth: any, newMaterialsThisMonth: any, siteSettings: any, recentMaterials: any, totalUsers: number, totalMaterials: number, activeUsers: number, departmentStats: any[], roleStats: any[]
try {
[newUsersThisMonth, newMaterialsThisMonth, siteSettings, recentMaterials, totalUsers, totalMaterials, activeUsers, departmentStats, roleStats] = await Promise.all([
prisma.user.aggregate({
_count: { id: true },
where: { createdAt: { gte: thirtyDaysAgo } },
}),
prisma.material.aggregate({
_count: { id: true },
where: { createdAt: { gte: thirtyDaysAgo }, deletedAt: null } as any,
}),
prisma.systemSettings.findFirst({
select: { siteName: true },
}),
prisma.material.findMany({
take: 10,
orderBy: { createdAt: 'desc' },
where: { deletedAt: null } as any,
select: {
id: true,
productModel: true,
createdAt: true,
user: {
select: {
username: true,
email: true,
},
},
},
}),
prisma.user.count(),
prisma.material.count({ where: { deletedAt: null } as any }),
prisma.user.count({ where: { updatedAt: { gte: sevenDaysAgo } } }),
prisma.user.groupBy({
by: ['department'],
_count: { id: true },
}),
prisma.user.groupBy({
by: ['role'],
_count: { id: true },
}),
])
} catch (error: any) {
console.error('[SystemStatus] Database query error:', error)
// 降级处理
newUsersThisMonth = { _count: { id: 0 } }
newMaterialsThisMonth = { _count: { id: 0 } }
siteSettings = null
recentMaterials = []
totalUsers = 0
totalMaterials = 0
activeUsers = 0
departmentStats = []
roleStats = []
}
// 获取素材统计(兼容旧数据库)
let materialStats: any = {}
try {
// 尝试按素材类型分组统计
const materialTypeStats = await prisma.material.groupBy({
by: ['materialType'],
_count: {
id: true,
},
where: {
deletedAt: null, // 只统计未删除的素材
} as any,
})
const totalMaterials = materialTypeStats.reduce((sum, stat) => sum + stat._count.id, 0)
materialStats = {
total: totalMaterials,
byType: materialTypeStats.reduce((acc, stat) => {
acc[stat.materialType] = stat._count.id
return acc
}, {} as Record<string, number>),
}
// 获取已删除素材数量
try {
const deletedCount = await prisma.material.count({
where: {
deletedAt: { not: null },
} as any,
})
materialStats.deleted = deletedCount
} catch {
// deletedAt 字段不存在,跳过
materialStats.deleted = 0
}
} catch (error: any) {
// 如果 deletedAt 字段不存在,降级统计
if (error?.meta?.column?.includes('deletedAt')) {
const materialTypeStats = await prisma.material.groupBy({
by: ['materialType'],
_count: {
id: true,
},
})
const totalMaterials = materialTypeStats.reduce((sum, stat) => sum + stat._count.id, 0)
materialStats = {
total: totalMaterials,
byType: materialTypeStats.reduce((acc, stat) => {
acc[stat.materialType] = stat._count.id
return acc
}, {} as Record<string, number>),
deleted: 0, // 旧数据库没有软删除
}
} else {
throw error
}
}
// 获取下载统计(最近7天)
let downloadStats: any = {}
try {
const recentDownloads = await (prisma as any).materialDownloadLog.count({
where: {
downloadedAt: {
gte: sevenDaysAgo,
},
},
})
const totalDownloads = await (prisma as any).materialDownloadLog.count()
downloadStats = {
total: totalDownloads,
recent7Days: recentDownloads,
}
} catch (error: any) {
// 如果下载日志表不存在
if (error?.meta?.table?.includes('material_download_logs')) {
downloadStats = {
total: 0,
recent7Days: 0,
}
} else {
throw error
}
}
// 获取收藏统计
let favoriteStats: any = {}
try {
const totalFavorites = await (prisma as any).materialFavorite.count()
favoriteStats = {
total: totalFavorites,
}
} catch (error: any) {
// 如果收藏表不存在
if (error?.meta?.table?.includes('material_favorites')) {
favoriteStats = {
total: 0,
}
} else {
throw error
}
}
// 获取存储统计(素材文件大小)
const storageStats = await prisma.material.aggregate({
_sum: {
zipFileSize: true,
},
})
const totalStorageBytes = Number(storageStats._sum.zipFileSize || 0)
const totalStorageGB = (totalStorageBytes / (1024 * 1024 * 1024)).toFixed(2)
// 从roleStats计算管理员和普通用户数量
const adminUsers = roleStats.find((r: any) => r.role === 'ADMIN')?._count?.id || 0
const regularUsers = roleStats.find((r: any) => r.role === 'USER')?._count?.id || 0
// 系统信息
const systemInfo = {
nodeVersion: process.version,
platform: process.platform,
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
timestamp: new Date().toISOString(),
}
return NextResponse.json({
users: {
total: totalUsers,
admin: adminUsers,
regular: regularUsers,
},
materials: materialStats,
downloads: downloadStats,
favorites: favoriteStats,
storage: {
totalBytes: totalStorageBytes,
totalGB: parseFloat(totalStorageGB),
},
system: systemInfo,
})
} catch (error) {
console.error('[Admin] System status error:', error)
return NextResponse.json(
{ error: '获取系统状态失败' },
{ status: 500 }
)
}
}