Files
shorturl-analytics/app/api/events/utm/route.ts
2025-04-08 12:00:58 +08:00

170 lines
4.6 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 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');
// 获取团队、项目和标签筛选参数
const teamIds = searchParams.getAll('teamId');
const projectIds = searchParams.getAll('projectId');
const tagIds = searchParams.getAll('tagId');
const tagNames = searchParams.getAll('tagName');
// 获取UTM类型参数
const utmType = searchParams.get('utmType') || 'source';
// 添加调试日志
console.log('UTM API received parameters:', {
startTime,
endTime,
linkId,
teamIds,
projectIds,
tagIds,
tagNames,
utmType,
url: request.url
});
// 构建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 (teamIds && teamIds.length > 0) {
// 如果只有一个团队ID
if (teamIds.length === 1) {
conditions.push(`team_id = '${teamIds[0]}'`);
} else {
// 多个团队ID
conditions.push(`team_id IN ('${teamIds.join("','")}')`);
}
}
// 添加项目筛选
if (projectIds && projectIds.length > 0) {
// 如果只有一个项目ID
if (projectIds.length === 1) {
conditions.push(`project_id = '${projectIds[0]}'`);
} else {
// 多个项目ID
conditions.push(`project_id IN ('${projectIds.join("','")}')`);
}
}
// 添加标签筛选
if ((tagIds && tagIds.length > 0) || (tagNames && tagNames.length > 0)) {
// 优先使用tagNames如果有的话
const tagsToUse = tagNames.length > 0 ? tagNames : tagIds;
// 使用与buildFilter函数相同的处理方式
const tagConditions = tagsToUse.map(tag =>
`link_tags LIKE '%${tag}%'`
);
conditions.push(`(${tagConditions.join(' OR ')})`);
}
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 });
}
}