utm
This commit is contained in:
@@ -81,6 +81,8 @@ export async function POST(req: NextRequest) {
|
||||
utm_source: eventData.utm_source || '',
|
||||
utm_medium: eventData.utm_medium || '',
|
||||
utm_campaign: eventData.utm_campaign || '',
|
||||
utm_term: eventData.utm_term || '',
|
||||
utm_content: eventData.utm_content || '',
|
||||
|
||||
// Interaction information
|
||||
time_spent_sec: eventData.time_spent_sec || 0,
|
||||
|
||||
117
app/api/events/utm/route.ts
Normal file
117
app/api/events/utm/route.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import type { ApiResponse } from '@/lib/types';
|
||||
|
||||
interface UtmData {
|
||||
utm_value: string;
|
||||
clicks: number;
|
||||
visitors: number;
|
||||
avg_time_spent: number;
|
||||
bounces: number;
|
||||
conversions: number;
|
||||
}
|
||||
|
||||
// 格式化日期时间字符串为ClickHouse支持的格式
|
||||
const formatDateTime = (dateStr: string): string => {
|
||||
return dateStr.replace('T', ' ').replace('Z', '');
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
// 获取UTM类型参数
|
||||
const utmType = searchParams.get('utmType') || 'source';
|
||||
|
||||
// 构建WHERE子句
|
||||
let whereClause = '';
|
||||
const conditions = [];
|
||||
|
||||
if (startTime) {
|
||||
conditions.push(`event_time >= toDateTime('${formatDateTime(startTime)}')`);
|
||||
}
|
||||
|
||||
if (endTime) {
|
||||
conditions.push(`event_time <= toDateTime('${formatDateTime(endTime)}')`);
|
||||
}
|
||||
|
||||
if (linkId) {
|
||||
conditions.push(`link_id = '${linkId}'`);
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
whereClause = `WHERE ${conditions.join(' AND ')}`;
|
||||
}
|
||||
|
||||
// 确定要分组的UTM字段
|
||||
let utmField;
|
||||
switch (utmType) {
|
||||
case 'source':
|
||||
utmField = 'utm_source';
|
||||
break;
|
||||
case 'medium':
|
||||
utmField = 'utm_medium';
|
||||
break;
|
||||
case 'campaign':
|
||||
utmField = 'utm_campaign';
|
||||
break;
|
||||
case 'term':
|
||||
utmField = 'utm_term';
|
||||
break;
|
||||
case 'content':
|
||||
utmField = 'utm_content';
|
||||
break;
|
||||
default:
|
||||
utmField = 'utm_source';
|
||||
}
|
||||
|
||||
// 构建SQL查询
|
||||
const query = `
|
||||
SELECT
|
||||
${utmField} AS utm_value,
|
||||
COUNT(*) AS clicks,
|
||||
uniqExact(visitor_id) AS visitors,
|
||||
round(AVG(time_spent_sec), 2) AS avg_time_spent,
|
||||
countIf(is_bounce = 1) AS bounces,
|
||||
countIf(conversion_type IN ('visit', 'stay', 'interact', 'signup', 'subscription', 'purchase')) AS conversions
|
||||
FROM shorturl_analytics.events
|
||||
${whereClause}
|
||||
${whereClause ? 'AND' : 'WHERE'} ${utmField} != ''
|
||||
GROUP BY utm_value
|
||||
ORDER BY clicks DESC
|
||||
LIMIT 100
|
||||
`;
|
||||
|
||||
// 执行查询
|
||||
const result = await clickhouse.query({
|
||||
query,
|
||||
format: 'JSONEachRow',
|
||||
});
|
||||
|
||||
// 获取查询结果
|
||||
const rows = await result.json();
|
||||
const data = rows as UtmData[];
|
||||
|
||||
// 返回数据
|
||||
const response: ApiResponse<UtmData[]> = {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
|
||||
return NextResponse.json(response);
|
||||
} catch (error) {
|
||||
console.error('Error fetching UTM data:', error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
|
||||
return NextResponse.json(response, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,8 @@ export interface Event {
|
||||
utm_source: string;
|
||||
utm_medium: string;
|
||||
utm_campaign: string;
|
||||
utm_term: string;
|
||||
utm_content: string;
|
||||
|
||||
// 交互信息
|
||||
time_spent_sec: number;
|
||||
|
||||
Reference in New Issue
Block a user