import { executeQuery, executeQuerySingle, buildFilter, buildPagination, buildOrderBy } from './clickhouse'; import type { Event, EventsSummary, TimeSeriesData, GeoData, DeviceAnalytics, DeviceType } from './types'; // 时间粒度枚举 export enum TimeGranularity { HOUR = 'hour', DAY = 'day', WEEK = 'week', MONTH = 'month' } // 获取事件列表 export async function getEvents(params: { startTime?: string; endTime?: string; eventType?: string; linkId?: string; linkSlug?: string; userId?: string; teamId?: string; projectId?: string; page?: number; pageSize?: number; sortBy?: string; sortOrder?: 'asc' | 'desc'; }): Promise<{ events: Event[]; total: number }> { const filter = buildFilter(params); const pagination = buildPagination(params.page, params.pageSize); const orderBy = buildOrderBy(params.sortBy, params.sortOrder); // 获取总数 const countQuery = ` SELECT count() as total FROM events ${filter} `; const totalResult = await executeQuerySingle<{ total: number }>(countQuery); const total = totalResult?.total || 0; // 获取事件列表 const query = ` SELECT * FROM events ${filter} ${orderBy} ${pagination} `; const events = await executeQuery(query); return { events, total }; } // 获取事件概览 export async function getEventsSummary(params: { startTime?: string; endTime?: string; linkId?: string; teamIds?: string[]; projectIds?: string[]; tagIds?: string[]; }): Promise { const filter = buildFilter(params); // 获取基本统计数据 const baseQuery = ` SELECT count() as totalEvents, uniq(visitor_id) as uniqueVisitors, countIf(event_type = 'conversion') as totalConversions, avg(time_spent_sec) as averageTimeSpent, -- 设备类型统计 countIf(device_type = 'mobile') as mobileCount, countIf(device_type = 'desktop') as desktopCount, countIf(device_type = 'tablet') as tabletCount, countIf(device_type = 'other') as otherCount FROM events ${filter} `; // 获取浏览器统计数据 const browserQuery = ` SELECT browser as name, count() as count FROM events ${filter} GROUP BY browser ORDER BY count DESC `; // 获取操作系统统计数据 const osQuery = ` SELECT os as name, count() as count FROM events ${filter} GROUP BY os ORDER BY count DESC `; try { const [baseResult, browserResults, osResults] = await Promise.all([ executeQuerySingle<{ totalEvents: number; uniqueVisitors: number; totalConversions: number; averageTimeSpent: number; mobileCount: number; desktopCount: number; tabletCount: number; otherCount: number; }>(baseQuery), executeQuery<{ name: string; count: number }>(browserQuery), executeQuery<{ name: string; count: number }>(osQuery) ]); if (!baseResult) { throw new Error('Failed to get events summary'); } // 安全转换数字类型 const safeNumber = (value: any): number => { if (value === null || value === undefined) return 0; const num = Number(value); return isNaN(num) ? 0 : num; }; // 计算百分比 const calculatePercentage = (count: number, total: number) => { if (!total) return 0; // 防止除以零 return Number(((count / total) * 100).toFixed(2)); }; // 处理浏览器数据 const browsers = browserResults.map(item => ({ name: item.name || 'Unknown', count: safeNumber(item.count), percentage: calculatePercentage(safeNumber(item.count), safeNumber(baseResult.totalEvents)) })); // 处理操作系统数据 const operatingSystems = osResults.map(item => ({ name: item.name || 'Unknown', count: safeNumber(item.count), percentage: calculatePercentage(safeNumber(item.count), safeNumber(baseResult.totalEvents)) })); return { totalEvents: safeNumber(baseResult.totalEvents), uniqueVisitors: safeNumber(baseResult.uniqueVisitors), totalConversions: safeNumber(baseResult.totalConversions), averageTimeSpent: baseResult.averageTimeSpent ? Number(baseResult.averageTimeSpent.toFixed(2)) : 0, deviceTypes: { mobile: safeNumber(baseResult.mobileCount), desktop: safeNumber(baseResult.desktopCount), tablet: safeNumber(baseResult.tabletCount), other: safeNumber(baseResult.otherCount) }, browsers, operatingSystems }; } catch (error) { console.error('Error in getEventsSummary:', error); throw error; } } // 获取时间序列数据 export async function getTimeSeriesData(params: { startTime: string; endTime: string; linkId?: string; granularity: 'hour' | 'day' | 'week' | 'month'; }): Promise { const filter = buildFilter(params); // 根据粒度选择时间间隔 const interval = { hour: '1 HOUR', day: '1 DAY', week: '1 WEEK', month: '1 MONTH' }[params.granularity]; const query = ` SELECT toStartOfInterval(event_time, INTERVAL ${interval}) as timestamp, count() as events, uniq(visitor_id) as visitors, countIf(event_type = 'conversion') as conversions FROM events ${filter} GROUP BY timestamp ORDER BY timestamp `; return executeQuery(query); } // 获取地理位置分析 export async function getGeoAnalytics(params: { startTime?: string; endTime?: string; linkId?: string; groupBy?: 'country' | 'city'; }): Promise { const filter = buildFilter(params); const groupByField = 'ip_address'; // 暂时按 IP 地址分组 const query = ` SELECT ${groupByField} as location, count() as visits, uniq(visitor_id) as visitors, count() * 100.0 / sum(count()) OVER () as percentage FROM events ${filter} GROUP BY ${groupByField} HAVING location != '' ORDER BY visits DESC LIMIT 10 `; return executeQuery(query); } // 获取设备分析 export async function getDeviceAnalytics(params: { startTime?: string; endTime?: string; linkId?: string; }): Promise { const filter = buildFilter(params); // 获取总数 const totalQuery = ` SELECT count() as total FROM events ${filter} `; // 获取设备类型统计 const deviceTypesQuery = ` SELECT device_type as name, count() as count FROM events ${filter} GROUP BY device_type ORDER BY count DESC `; // 获取浏览器统计 const browsersQuery = ` SELECT browser as name, count() as count FROM events ${filter} GROUP BY browser ORDER BY count DESC `; // 获取操作系统统计 const osQuery = ` SELECT os as name, count() as count FROM events ${filter} GROUP BY os ORDER BY count DESC `; const [totalResult, deviceTypes, browsers, operatingSystems] = await Promise.all([ executeQuerySingle<{ total: number }>(totalQuery), executeQuery<{ name: string; count: number }>(deviceTypesQuery), executeQuery<{ name: string; count: number }>(browsersQuery), executeQuery<{ name: string; count: number }>(osQuery) ]); if (!totalResult) { throw new Error('Failed to get device analytics'); } // 计算百分比 const calculatePercentage = (count: number) => { if (!totalResult || totalResult.total === 0) return 0; return Number(((count / totalResult.total) * 100).toFixed(2)); }; return { deviceTypes: deviceTypes.map(item => ({ type: item.name.toLowerCase() as DeviceType, count: item.count, percentage: calculatePercentage(item.count) })), browsers: browsers.map(item => ({ name: item.name, count: item.count, percentage: calculatePercentage(item.count) })), operatingSystems: operatingSystems.map(item => ({ name: item.name, count: item.count, percentage: calculatePercentage(item.count) })) }; }