80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import type { ApiResponse } from '@/lib/types';
|
|
import { executeQuery } from '@/lib/clickhouse';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
// 获取查询参数
|
|
const searchParams = request.nextUrl.searchParams;
|
|
const startTime = searchParams.get('startTime');
|
|
const endTime = searchParams.get('endTime');
|
|
const linkId = searchParams.get('linkId');
|
|
|
|
if (!startTime || !endTime || !linkId) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: 'Missing required parameters'
|
|
}, { status: 400 });
|
|
}
|
|
|
|
// 查询链接的点击事件
|
|
const query = `
|
|
SELECT event_attributes
|
|
FROM events
|
|
WHERE link_id = '${linkId}'
|
|
AND event_time >= parseDateTimeBestEffort('${startTime}')
|
|
AND event_time <= parseDateTimeBestEffort('${endTime}')
|
|
AND event_type = 'click'
|
|
`;
|
|
|
|
const events = await executeQuery(query);
|
|
|
|
// 处理事件数据,按路径分组
|
|
const pathMap = new Map<string, number>();
|
|
let totalClicks = 0;
|
|
|
|
events.forEach((event: any) => {
|
|
try {
|
|
if (event.event_attributes) {
|
|
const attrs = JSON.parse(event.event_attributes);
|
|
if (attrs.full_url) {
|
|
// 提取URL的路径和参数部分
|
|
const url = new URL(attrs.full_url);
|
|
const pathWithParams = url.pathname + (url.search || '');
|
|
|
|
// 更新路径计数
|
|
const currentCount = pathMap.get(pathWithParams) || 0;
|
|
pathMap.set(pathWithParams, currentCount + 1);
|
|
totalClicks++;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// 忽略解析错误
|
|
}
|
|
});
|
|
|
|
// 转换为数组并按点击数排序
|
|
const pathData = Array.from(pathMap.entries())
|
|
.map(([path, count]) => ({
|
|
path,
|
|
count,
|
|
percentage: totalClicks > 0 ? count / totalClicks : 0,
|
|
}))
|
|
.sort((a, b) => b.count - a.count);
|
|
|
|
const response: ApiResponse<typeof pathData> = {
|
|
success: true,
|
|
data: pathData,
|
|
meta: { total: totalClicks }
|
|
};
|
|
|
|
return NextResponse.json(response);
|
|
} catch (error) {
|
|
console.error('Error fetching path analytics data:', error);
|
|
const response: ApiResponse<null> = {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Internal server error'
|
|
};
|
|
return NextResponse.json(response, { status: 500 });
|
|
}
|
|
}
|