2025-12-17 20:15:18 +08:00

186 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { generatedImage, user } from "@/lib/db/schema";
import { eq, sql, desc, count, and, gte, lt } from "drizzle-orm";
import { auth } from "@/app/(auth)/auth";
export async function GET(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// 检查是否为管理员
const currentUser = await db.query.user.findFirst({
where: eq(user.id, session.user.id),
});
if (currentUser?.role !== "admin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
// 获取查询参数
const searchParams = request.nextUrl.searchParams;
const month = searchParams.get("month"); // 格式: YYYY-MM
const selectedDate = searchParams.get("date"); // 格式: YYYY-MM-DD,用于高峰时段筛选
// 1. 总体统计
const totalStats = await db
.select({
totalImages: sql<number>`count(*)::int`,
totalUsers: sql<number>`count(distinct ${generatedImage.userId})::int`,
})
.from(generatedImage);
// 2. 按用户统计(Top 20)
const userStats = await db
.select({
userId: generatedImage.userId,
userName: user.name,
userEmail: user.email,
imageCount: sql<number>`count(*)::int`,
})
.from(generatedImage)
.leftJoin(user, eq(generatedImage.userId, user.id))
.groupBy(generatedImage.userId, user.name, user.email)
.orderBy(desc(sql`count(*)`))
.limit(20);
// 3. 按日期统计(最近30天)
const dailyStats = await db
.select({
date: sql<string>`date(${generatedImage.createdAt})::text`,
count: sql<number>`count(*)::int`,
})
.from(generatedImage)
.where(sql`${generatedImage.createdAt} >= now() - interval '30 days'`)
.groupBy(sql`date(${generatedImage.createdAt})`)
.orderBy(sql`date(${generatedImage.createdAt})`);
// 4. 按图片尺寸统计
const sizeStats = await db
.select({
imageSize: generatedImage.imageSize,
count: sql<number>`count(*)::int`,
})
.from(generatedImage)
.groupBy(generatedImage.imageSize)
.orderBy(desc(sql`count(*)`));
// 5. 按模型统计
const modelStats = await db
.select({
modelId: generatedImage.modelId,
count: sql<number>`count(*)::int`,
})
.from(generatedImage)
.groupBy(generatedImage.modelId)
.orderBy(desc(sql`count(*)`))
.limit(10);
// 6. 按月份统计
const monthlyStats = await db
.select({
month: sql<string>`to_char(${generatedImage.createdAt}, 'YYYY-MM')`,
count: sql<number>`count(*)::int`,
})
.from(generatedImage)
.groupBy(sql`to_char(${generatedImage.createdAt}, 'YYYY-MM')`)
.orderBy(sql`to_char(${generatedImage.createdAt}, 'YYYY-MM')`);
// 7. 按小时统计(用于分析高峰时段,支持按日期筛选)
let hourlyWhereClause = undefined;
if (selectedDate) {
const dateStart = new Date(selectedDate);
const dateEnd = new Date(selectedDate);
dateEnd.setDate(dateEnd.getDate() + 1);
hourlyWhereClause = and(
gte(generatedImage.createdAt, dateStart),
lt(generatedImage.createdAt, dateEnd)
);
}
const hourlyStats = await db
.select({
hour: sql<number>`extract(hour from ${generatedImage.createdAt})::int`,
count: sql<number>`count(*)::int`,
})
.from(generatedImage)
.where(hourlyWhereClause)
.groupBy(sql`extract(hour from ${generatedImage.createdAt})`)
.orderBy(sql`extract(hour from ${generatedImage.createdAt})`);
// 8. 今日统计
const todayStats = await db
.select({
count: sql<number>`count(*)::int`,
users: sql<number>`count(distinct ${generatedImage.userId})::int`,
})
.from(generatedImage)
.where(sql`date(${generatedImage.createdAt}) = current_date`);
// 9. 本周统计
const weekStats = await db
.select({
count: sql<number>`count(*)::int`,
users: sql<number>`count(distinct ${generatedImage.userId})::int`,
})
.from(generatedImage)
.where(sql`${generatedImage.createdAt} >= date_trunc('week', current_date)`);
// 10. 指定月份统计(支持选择月份)
let monthStart: Date;
let monthEnd: Date;
if (month) {
// 解析指定月份
const [year, mon] = month.split("-").map(Number);
monthStart = new Date(year, mon - 1, 1);
monthEnd = new Date(year, mon, 1); // 下个月第一天
} else {
// 默认当前月
const now = new Date();
monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 1);
}
const monthStats = await db
.select({
count: sql<number>`count(*)::int`,
users: sql<number>`count(distinct ${generatedImage.userId})::int`,
})
.from(generatedImage)
.where(and(
gte(generatedImage.createdAt, monthStart),
lt(generatedImage.createdAt, monthEnd)
));
return NextResponse.json({
overview: {
totalImages: totalStats[0]?.totalImages || 0,
totalUsers: totalStats[0]?.totalUsers || 0,
todayImages: todayStats[0]?.count || 0,
todayUsers: todayStats[0]?.users || 0,
weekImages: weekStats[0]?.count || 0,
weekUsers: weekStats[0]?.users || 0,
monthImages: monthStats[0]?.count || 0,
monthUsers: monthStats[0]?.users || 0,
selectedMonth: month || `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`,
selectedDate: selectedDate || null,
},
userStats,
dailyStats,
sizeStats,
modelStats,
monthlyStats,
hourlyStats,
});
} catch (error) {
console.error("[Admin Images Analytics API] Error:", error);
return NextResponse.json({ error: "Failed to fetch analytics" }, { status: 500 });
}
}