import { createError, defineEventHandler } from 'h3' import { db } from '~/drizzle/db' import { CacheService } from '~~/server/services/cacheService' import { songs, users, votes, userIdentities } from '~/drizzle/schema' import { count, eq, gte, inArray } from 'drizzle-orm' import { getBeijingHour, getBeijingStartOfDay } from '~/utils/timeUtils' import { getUserAvatarUrl } from '~~/server/utils/user-avatar' function formatRelativeTime(date: Date | null | undefined): string { if (!date) return '未知' const now = new Date() const diff = now.getTime() - new Date(date).getTime() const minutes = Math.floor(diff / 60000) if (minutes < 1) return '刚刚' if (minutes < 60) return `${minutes}分钟前` const hours = Math.floor(minutes / 60) if (hours < 24) return `${hours}小时前` return `${Math.floor(hours / 24)}天前` } export default defineEventHandler(async (event) => { const user = event.context.user if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) { throw createError({ statusCode: 403, message: '需要管理员权限' }) } try { const cacheService = CacheService.getInstance() const cachedStats = await cacheService.getRealtimeStats() if (cachedStats) { console.log('[Cache] 实时统计数据缓存命中') return cachedStats } const now = new Date() const today = getBeijingStartOfDay() const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000) const [activeUsersData, todayRequests, popularGenres, peakHours] = await Promise.all([ (async () => { try { const recentSongUsers = await db .select({ requesterId: songs.requesterId, requesterName: users.name, requesterUsername: users.username, requesterId2: users.id, requesterLastActive: songs.createdAt, requesterAvatarProvider: users.avatarProvider, requesterAvatarProviderUserId: users.avatarProviderUserId, requesterEmail: users.email }) .from(songs) .innerJoin(users, eq(songs.requesterId, users.id)) .where(gte(songs.createdAt, oneHourAgo)) .groupBy(songs.requesterId, users.id, users.name, users.username, users.lastLogin, users.avatarProvider, users.avatarProviderUserId, users.email, songs.createdAt) const recentLoginUsers = await db .select({ id: users.id, username: users.username, name: users.name, lastLogin: users.lastLogin, avatarProvider: users.avatarProvider, avatarProviderUserId: users.avatarProviderUserId, email: users.email }) .from(users) .where(gte(users.lastLogin, oneHourAgo)) const recentVoteUsers = await db .select({ userId: votes.userId, userName: users.name, userUsername: users.username, userId2: users.id, voteCreatedAt: votes.createdAt, userAvatarProvider: users.avatarProvider, userAvatarProviderUserId: users.avatarProviderUserId, userEmail: users.email }) .from(votes) .innerJoin(users, eq(votes.userId, users.id)) .where(gte(votes.createdAt, oneHourAgo)) .groupBy(votes.userId, users.id, users.name, users.username, users.avatarProvider, users.avatarProviderUserId, users.email, votes.createdAt) const userMap = new Map() recentSongUsers.forEach((song) => { const id = song.requesterId2 const existing = userMap.get(id) const lastActive = existing?.lastActive ? new Date(existing.lastActive) > new Date(song.requesterLastActive) ? existing.lastActive : song.requesterLastActive : song.requesterLastActive userMap.set(id, { id, username: song.requesterUsername, name: song.requesterName, email: song.requesterEmail, avatarProvider: song.requesterAvatarProvider, avatarProviderUserId: song.requesterAvatarProviderUserId, lastActive }) }) recentLoginUsers.forEach((u) => { const existing = userMap.get(u.id) const lastActive = existing?.lastActive ? new Date(existing.lastActive) > new Date(u.lastLogin) ? existing.lastActive : u.lastLogin : u.lastLogin userMap.set(u.id, { id: u.id, username: u.username, name: u.name, email: u.email, avatarProvider: u.avatarProvider, avatarProviderUserId: u.avatarProviderUserId, lastActive }) }) recentVoteUsers.forEach((vote) => { const id = vote.userId2 const existing = userMap.get(id) const lastActive = existing?.lastActive ? new Date(existing.lastActive) > new Date(vote.voteCreatedAt) ? existing.lastActive : vote.voteCreatedAt : vote.voteCreatedAt userMap.set(id, { id, username: vote.userUsername, name: vote.userName, email: vote.userEmail, avatarProvider: vote.userAvatarProvider, avatarProviderUserId: vote.userAvatarProviderUserId, lastActive }) }) const userIds = Array.from(userMap.keys()) let identitiesData: any[] = [] if (userIds.length > 0) { try { identitiesData = await db .select() .from(userIdentities) .where(inArray(userIdentities.userId, userIds)) } catch (e) { identitiesData = [] } } const identitiesMap = new Map() identitiesData.forEach((identity) => { if (!identitiesMap.has(identity.userId)) { identitiesMap.set(identity.userId, []) } identitiesMap.get(identity.userId)!.push(identity) }) const activeUsersList = Array.from(userMap.values()).map((u) => { const userIdentitiesData = identitiesMap.get(u.id) || [] const avatarUrl = getUserAvatarUrl( { id: u.id, name: u.name, username: u.username, email: u.email, avatarProvider: u.avatarProvider, avatarProviderUserId: u.avatarProviderUserId }, userIdentitiesData ) return { id: u.id, username: u.username, name: u.name, avatarUrl, lastActive: formatRelativeTime(u.lastActive) } }) return { count: activeUsersList.length, users: activeUsersList } } catch (error) { console.error('[Realtime] 获取活跃用户失败:', error) return { count: 0, users: [] } } })(), (async () => { try { const todayCountResult = await db .select({ count: count() }) .from(songs) .where(gte(songs.createdAt, today)) return todayCountResult[0].count } catch (error) { return 0 } })(), (async () => { return [] })(), (async () => { try { const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) const songsData = await db .select({ createdAt: songs.createdAt }) .from(songs) .where(gte(songs.createdAt, sevenDaysAgo)) const hourCount = songsData.reduce( (acc, song) => { const hour = getBeijingHour(song.createdAt) acc[hour] = (acc[hour] || 0) + 1 return acc }, {} as Record ) return Object.entries(hourCount) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([hour, count]) => ({ hour: parseInt(hour), count, label: `${hour}:00-${parseInt(hour) + 1}:00` })) } catch (error) { return [] } })() ]) const result = { activeUsers: activeUsersData.count, activeUsersList: activeUsersData.users, todayRequests, popularGenres, peakHours, timestamp: now.toISOString() } await cacheService.setRealtimeStats(result) console.log('[Cache] 实时统计数据已缓存') return result } catch (error) { console.error('[Realtime] 获取失败:', error) throw createError({ statusCode: 500, message: '获取实时统计数据失败' }) } })