Some checks failed
CI Check / check (push) Has been cancelled
- 移除高亮卡片效果,避免与虚位卡片视觉重复 - 松开鼠标后立即更新布局,不再等待动画完成 - 优化拖拽动画效果,使用弹性缓动函数 - 更新 CHANGELOG.md 记录本次优化
224 lines
6.8 KiB
TypeScript
224 lines
6.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { prisma } from '@/lib/prisma'
|
||
import { getServerSession } from 'next-auth'
|
||
import { authOptions } from '@/lib/auth'
|
||
|
||
export const dynamic = 'force-dynamic'
|
||
|
||
export async function GET(req: NextRequest) {
|
||
try {
|
||
// 获取会话
|
||
let session
|
||
try {
|
||
session = await getServerSession(authOptions)
|
||
} catch (sessionError) {
|
||
console.error('[SystemStatus] Session error:', sessionError)
|
||
return NextResponse.json(
|
||
{ error: '会话获取失败', message: sessionError instanceof Error ? sessionError.message : '未知错误' },
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
|
||
const isSuperAdmin = (session?.user as any)?.isSuperAdmin
|
||
if (!session || ((session.user as any)?.role !== 'ADMIN' && !isSuperAdmin)) {
|
||
return NextResponse.json(
|
||
{ error: '无权限访问' },
|
||
{ status: 403 }
|
||
)
|
||
}
|
||
|
||
// 并行获取各项统计数据(优化:减少查询次数)
|
||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||
|
||
let userStats: { _count: { id: number } }
|
||
let materialStats: { _count: { id: number } }
|
||
let systemInfo: { enableRegistration: boolean; enableUserActivation: boolean; siteName: string } | null
|
||
let recentActivity: Array<{ id: string; productModel: string | null; createdAt: Date; user: { username: string; email: string } | null }>
|
||
let totalUsers: number
|
||
let totalMaterials: number
|
||
let activeUsers: number
|
||
let departmentStats: Array<{ department: string | null; _count: { id: number } }>
|
||
let roleDistribution: Array<{ role: string; isSuperAdmin: boolean | null; _count: { id: number } }>
|
||
|
||
try {
|
||
[
|
||
userStats,
|
||
materialStats,
|
||
systemInfo,
|
||
recentActivity,
|
||
totalUsers,
|
||
totalMaterials,
|
||
activeUsers,
|
||
departmentStats,
|
||
roleDistribution
|
||
] = await Promise.all([
|
||
// 用户统计(最近30天)
|
||
prisma.user.aggregate({
|
||
_count: { id: true },
|
||
where: { createdAt: { gte: thirtyDaysAgo } }
|
||
}),
|
||
|
||
// 素材统计(最近30天)
|
||
prisma.material.aggregate({
|
||
_count: { id: true },
|
||
where: { createdAt: { gte: thirtyDaysAgo } }
|
||
}),
|
||
|
||
// 系统设置(使用缓存,减少数据库查询)
|
||
prisma.systemSettings.findFirst({
|
||
select: {
|
||
enableRegistration: true,
|
||
enableUserActivation: true,
|
||
siteName: true,
|
||
}
|
||
}),
|
||
|
||
// 最近活动(限制为10条,减少数据传输)
|
||
prisma.material.findMany({
|
||
take: 10, // 减少到10条
|
||
orderBy: { createdAt: 'desc' },
|
||
select: {
|
||
id: true,
|
||
productModel: true,
|
||
createdAt: true,
|
||
user: {
|
||
select: { username: true, email: true }
|
||
}
|
||
}
|
||
}),
|
||
|
||
// 总用户数
|
||
prisma.user.count(),
|
||
|
||
// 总素材数
|
||
prisma.material.count(),
|
||
|
||
// 活跃用户(最近7天)
|
||
prisma.user.count({
|
||
where: {
|
||
updatedAt: { gte: sevenDaysAgo }
|
||
}
|
||
}),
|
||
|
||
// 部门统计
|
||
prisma.user.groupBy({
|
||
by: ['department'],
|
||
_count: { id: true },
|
||
where: {
|
||
department: { not: '' }
|
||
}
|
||
}),
|
||
|
||
// 权限分布统计
|
||
prisma.user.groupBy({
|
||
by: ['role', 'isSuperAdmin'],
|
||
_count: { id: true }
|
||
})
|
||
])
|
||
} catch (dbError) {
|
||
console.error('[SystemStatus] Database query error:', dbError)
|
||
// 如果数据库查询失败,返回默认值
|
||
userStats = { _count: { id: 0 } }
|
||
materialStats = { _count: { id: 0 } }
|
||
systemInfo = null
|
||
recentActivity = []
|
||
totalUsers = 0
|
||
totalMaterials = 0
|
||
activeUsers = 0
|
||
departmentStats = []
|
||
roleDistribution = []
|
||
}
|
||
|
||
// 计算素材上传趋势(按月统计,最近12个月)
|
||
const twelveMonthsAgo = new Date()
|
||
twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12)
|
||
|
||
let materialTrend: Array<{ date: Date, count: bigint }> = []
|
||
|
||
try {
|
||
materialTrend = await prisma.$queryRaw<Array<{ date: Date, count: bigint }>>`
|
||
SELECT
|
||
DATE_TRUNC('month', created_at)::date as date,
|
||
COUNT(*)::bigint as count
|
||
FROM materials
|
||
WHERE created_at >= ${twelveMonthsAgo}
|
||
GROUP BY DATE_TRUNC('month', created_at)
|
||
ORDER BY date ASC
|
||
`
|
||
} catch (queryError) {
|
||
console.error('[SystemStatus] Material trend query error:', queryError)
|
||
materialTrend = []
|
||
}
|
||
|
||
const statusData = {
|
||
overview: {
|
||
totalUsers,
|
||
activeUsers,
|
||
totalMaterials,
|
||
newUsersThisMonth: userStats._count.id,
|
||
newMaterialsThisMonth: materialStats._count.id
|
||
},
|
||
|
||
trends: {
|
||
materialUploads: materialTrend.map(item => ({
|
||
date: item.date.toISOString().split('T')[0].substring(0, 7), // 转换为 YYYY-MM 格式
|
||
count: Number(item.count)
|
||
}))
|
||
},
|
||
|
||
distribution: {
|
||
departments: departmentStats.map(item => ({
|
||
department: item.department || '未设置',
|
||
count: item._count.id
|
||
})),
|
||
roles: roleDistribution.map(item => ({
|
||
role: item.role || 'USER',
|
||
isSuperAdmin: item.isSuperAdmin || false,
|
||
count: item._count.id
|
||
}))
|
||
},
|
||
|
||
systemInfo: {
|
||
registrationEnabled: systemInfo?.enableRegistration || false,
|
||
userActivationRequired: systemInfo?.enableUserActivation || false,
|
||
siteName: systemInfo?.siteName || '默认站点'
|
||
},
|
||
|
||
recentActivity: recentActivity
|
||
.filter(activity => activity.user) // 过滤掉没有用户的记录
|
||
.map(activity => ({
|
||
id: activity.id,
|
||
title: activity.productModel || '未命名素材',
|
||
createdAt: activity.createdAt.toISOString(),
|
||
uploadedBy: {
|
||
username: activity.user?.username || '未知用户',
|
||
email: activity.user?.email || ''
|
||
}
|
||
})),
|
||
|
||
// 系统状态
|
||
status: {
|
||
database: 'healthy', // 可以扩展为实际数据库连接检查
|
||
timestamp: new Date().toISOString(),
|
||
uptime: process.uptime()
|
||
}
|
||
}
|
||
|
||
return NextResponse.json(statusData)
|
||
} catch (error) {
|
||
console.error('[SystemStatus] GET error:', error)
|
||
// 输出更详细的错误信息
|
||
if (error instanceof Error) {
|
||
console.error('[SystemStatus] Error message:', error.message)
|
||
console.error('[SystemStatus] Error stack:', error.stack)
|
||
}
|
||
return NextResponse.json(
|
||
{
|
||
error: '获取系统状态失败',
|
||
message: error instanceof Error ? error.message : '未知错误'
|
||
},
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
} |