186 lines
6.1 KiB
TypeScript
186 lines
6.1 KiB
TypeScript
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 });
|
||
}
|
||
}
|
||
|