Files
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

374 lines
12 KiB
TypeScript

import { createError, defineEventHandler, getQuery } from 'h3'
import { db } from '~/drizzle/db'
import { schedules, semesters, songBlacklists, songs, users } from '~/drizzle/schema'
import { and, count, eq, gte, lt } from 'drizzle-orm'
import { cacheService } from '../../services/cacheService'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js'
import timezone from 'dayjs/plugin/timezone.js'
dayjs.extend(utc)
dayjs.extend(timezone)
const BEIJING_TIMEZONE = 'Asia/Shanghai'
export default defineEventHandler(async (event) => {
// 检查认证和权限
const user = event.context.user
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({
statusCode: 403,
message: '需要管理员权限'
})
}
const query = getQuery(event)
const semester = query.semester as string
try {
// 尝试从缓存获取数据
const cachedStats = await cacheService.getAdminStats(semester)
if (cachedStats) {
console.log('[Cache] 管理员统计数据缓存命中')
return cachedStats
}
// 获取当前时间相关的日期
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000)
// 构建查询条件
const where = semester && semester !== 'all' ? { semester: semester } : {}
// 构建排期查询条件
const scheduleBaseWhere = semester && semester !== 'all' ? { song: { semester: semester } } : {}
// 添加日期条件到今日排期查询
const todayScheduleWhere = {
...scheduleBaseWhere,
playDate: {
gte: today,
lt: new Date(today.getTime() + 24 * 60 * 60 * 1000)
}
}
// 并行获取所有统计数据
const [
totalSongs,
totalUsers,
todaySchedules,
totalSchedules,
weeklyRequests,
previousWeekRequests,
songsThisWeek,
songsLastWeek,
usersThisWeek,
usersLastWeek,
currentSemester,
blacklistCount
] = await Promise.all([
// 总歌曲数
(async () => {
const whereCondition =
semester && semester !== 'all' ? eq(songs.semester, semester) : undefined
const result = await db.select({ count: count() }).from(songs).where(whereCondition)
return result[0].count
})(),
// 总用户数
(async () => {
const result = await db.select({ count: count() }).from(users)
return result[0].count
})(),
// 今日排期数 (按天计算)
(async () => {
const query = db.select({ playDate: schedules.playDate }).from(schedules)
const whereConditions = [
gte(schedules.playDate, today),
lt(schedules.playDate, new Date(today.getTime() + 24 * 60 * 60 * 1000))
]
if (semester && semester !== 'all') {
query.innerJoin(songs, eq(schedules.songId, songs.id))
whereConditions.push(eq(songs.semester, semester))
}
const todaySchedulesList = await query.where(and(...whereConditions))
// 按北京时间日期去重计算天数
const uniqueDates = new Set(
todaySchedulesList.map((s) => dayjs(s.playDate).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'))
)
return uniqueDates.size
})(),
// 总排期数 (按天计算)
(async () => {
const query = db.select({ playDate: schedules.playDate }).from(schedules)
if (semester && semester !== 'all') {
query.innerJoin(songs, eq(schedules.songId, songs.id)).where(eq(songs.semester, semester))
}
const allSchedules = await query
// 按北京时间日期去重计算天数
const uniqueDates = new Set(
allSchedules.map((s) => dayjs(s.playDate).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'))
)
return uniqueDates.size
})(),
// 本周点歌数
(async () => {
const whereConditions = [gte(songs.createdAt, weekAgo)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
return result[0].count
})(),
// 上周点歌数
(async () => {
const whereConditions = [gte(songs.createdAt, twoWeeksAgo), lt(songs.createdAt, weekAgo)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
return result[0].count
})(),
// 本周新增歌曲
(async () => {
const whereConditions = [gte(songs.createdAt, weekAgo)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
return result[0].count
})(),
// 上周新增歌曲
(async () => {
const whereConditions = [gte(songs.createdAt, twoWeeksAgo), lt(songs.createdAt, weekAgo)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
return result[0].count
})(),
// 本周新增用户
(async () => {
const result = await db
.select({ count: count() })
.from(users)
.where(gte(users.createdAt, weekAgo))
return result[0].count
})(),
// 上周新增用户
(async () => {
const result = await db
.select({ count: count() })
.from(users)
.where(and(gte(users.createdAt, twoWeeksAgo), lt(users.createdAt, weekAgo)))
return result[0].count
})(),
// 当前学期
(async () => {
const result = await db
.select({ name: semesters.name })
.from(semesters)
.where(eq(semesters.isActive, true))
.limit(1)
return result[0]
})(),
// 黑名单项目数
(async () => {
const blacklistCount = await db
.select({ count: count() })
.from(songBlacklists)
.where(eq(songBlacklists.isActive, true))
return blacklistCount[0].count
})()
])
// 计算变化百分比
const requestsChange =
previousWeekRequests > 0
? Math.round(((weeklyRequests - previousWeekRequests) / previousWeekRequests) * 100)
: weeklyRequests > 0
? 100
: 0
const songsChange = songsThisWeek - songsLastWeek
const usersChange = usersThisWeek - usersLastWeek
// 获取趋势数据 (最近7天)
const trendData = await Promise.all([
// 歌曲趋势
(async () => {
const trends = []
for (let i = 6; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const endOfDay = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000)
const whereConditions = [gte(songs.createdAt, startOfDay), lt(songs.createdAt, endOfDay)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
const countValue = result[0].count
trends.push({
date: dayjs(startOfDay).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'),
count: countValue
})
}
return trends
})(),
// 用户趋势
(async () => {
const trends = []
for (let i = 6; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const endOfDay = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000)
const result = await db
.select({ count: count() })
.from(users)
.where(and(gte(users.createdAt, startOfDay), lt(users.createdAt, endOfDay)))
const countValue = result[0].count
trends.push({
date: dayjs(startOfDay).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'),
count: countValue
})
}
return trends
})(),
// 排期趋势
(async () => {
const trends = []
for (let i = 6; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const endOfDay = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000)
const query = db.select({ playDate: schedules.playDate }).from(schedules)
const whereConditions = [
gte(schedules.playDate, startOfDay),
lt(schedules.playDate, endOfDay)
]
if (semester && semester !== 'all') {
query.innerJoin(songs, eq(schedules.songId, songs.id))
whereConditions.push(eq(songs.semester, semester))
}
const schedulesList = await query.where(and(...whereConditions))
// 按北京时间日期去重计算天数
const uniqueDates = new Set(
schedulesList.map((s) => dayjs(s.playDate).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'))
)
trends.push({
date: dayjs(startOfDay).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'),
count: uniqueDates.size
})
}
return trends
})(),
// 点歌请求趋势
(async () => {
const trends = []
for (let i = 6; i >= 0; i--) {
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const endOfDay = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000)
const whereConditions = [gte(songs.createdAt, startOfDay), lt(songs.createdAt, endOfDay)]
if (semester && semester !== 'all') {
whereConditions.push(eq(songs.semester, semester))
}
const result = await db
.select({ count: count() })
.from(songs)
.where(and(...whereConditions))
const countValue = result[0].count
trends.push({
date: dayjs(startOfDay).tz(BEIJING_TIMEZONE).format('YYYY-MM-DD'),
count: countValue
})
}
return trends
})()
])
const result = {
totalSongs,
totalUsers,
todaySchedules,
totalSchedules,
weeklyRequests,
songsChange,
usersChange,
requestsChange,
currentSemester: currentSemester?.name || null,
blacklistCount,
// 趋势数据
songsTrend: trendData[0],
usersTrend: trendData[1],
schedulesTrend: trendData[2],
requestsTrend: trendData[3]
}
// 缓存结果
await cacheService.setAdminStats(semester, result)
// 只在Redis可用时显示缓存提示
const { isRedisReady } = await import('../../utils/redis')
if (isRedisReady()) {
console.log('[Cache] 管理员统计数据已缓存')
}
return result
} catch (error) {
console.error('获取统计数据失败:', error)
throw createError({
statusCode: 500,
message: '获取统计数据失败'
})
}
})