初次提交
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
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: '获取统计数据失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user