diff --git a/app/hosts/[hostname]/time-distribution/route.ts b/app/hosts/[hostname]/time-distribution/route.ts index 824427c..04f008a 100644 --- a/app/hosts/[hostname]/time-distribution/route.ts +++ b/app/hosts/[hostname]/time-distribution/route.ts @@ -1,70 +1,51 @@ import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' import { withCors } from '@/lib/middleware' +import { Prisma } from '@prisma/client' // 为了 Prisma.sql / Prisma.join async function handleTimeDistribution(req: NextRequest) { try { const pathSegments = req.nextUrl.pathname.split('/') const hostnameIndex = pathSegments.indexOf('hosts') + 1 const hostname = pathSegments[hostnameIndex] - + if (!hostname) { return NextResponse.json({ error: '缺少主机名' }, { status: 400 }) } - // Get all records for the hostname and group by hour - const records = await prisma.record.findMany({ - where: { hostname }, - select: { - timestamp: true - } - }) + const sp = req.nextUrl.searchParams + const fromParam = sp.get('from') // ISO,如 2025-09-01T00:00:00Z + const toParam = sp.get('to') // ISO - // Group by hour - interface DistributionEntry { - timestamp: number - count: number - } - - const distribution = records.reduce((acc: DistributionEntry[], record: { timestamp: Date }) => { - const timestamp = new Date(record.timestamp) - // Create hour-level timestamp (set minutes, seconds, ms to 0) - const hourTimestamp = new Date( - timestamp.getFullYear(), - timestamp.getMonth(), - timestamp.getDate(), - timestamp.getHours(), - 0, 0, 0 - ) - - const existingEntry = acc.find(entry => - entry.timestamp === Math.floor(hourTimestamp.getTime() / 1000) - ) - - if (existingEntry) { - existingEntry.count++ - } else { - acc.push({ - timestamp: Math.floor(hourTimestamp.getTime() / 1000), - count: 1 - }) - } - - return acc - }, []) + const whereParts: any[] = [Prisma.sql`"hostname" = ${hostname}`] + if (fromParam) whereParts.push(Prisma.sql`"timestamp" >= ${new Date(fromParam)}`) + if (toParam) whereParts.push(Prisma.sql`"timestamp" < ${new Date(toParam)}`) - // Sort by timestamp - distribution.sort((a: DistributionEntry, b: DistributionEntry) => a.timestamp - b.timestamp) + const rows = await prisma.$queryRaw< + { ts: bigint | number; count: number }[] + >(Prisma.sql` + SELECT + EXTRACT(EPOCH FROM date_trunc('hour', "timestamp"))::bigint AS ts, + COUNT(*)::int AS count + FROM "records" + WHERE ${Prisma.join(whereParts, ' AND ')} + GROUP BY 1 + ORDER BY 1 + `) - return NextResponse.json({ - hostname, - distribution - }) + // JSON 不支持 BigInt,转成 number(秒级时间戳安全) + const distribution = rows.map(r => ({ + timestamp: Number(r.ts), + count: r.count + })) + return NextResponse.json( + { hostname, distribution }, + ) } catch (error) { console.error('获取时间分布统计失败:', error) return NextResponse.json({ error: '获取时间分布统计失败' }, { status: 500 }) } } -export const GET = withCors(handleTimeDistribution) +export const GET = withCors(handleTimeDistribution) \ No newline at end of file diff --git a/ecosystem.config.js b/ecosystem.config.js deleted file mode 100644 index e69de29..0000000 diff --git a/ecosystem.config.json b/ecosystem.config.json deleted file mode 100644 index e69de29..0000000