242 lines
7.0 KiB
TypeScript
242 lines
7.0 KiB
TypeScript
/**
|
||
* 系统状态监控 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 }
|
||
)
|
||
}
|
||
}
|