Files
shorturl-analytics/lib/clickhouse.ts
2025-04-10 18:07:10 +08:00

159 lines
4.6 KiB
TypeScript

import { createClient } from '@clickhouse/client';
import { EventsQueryParams } from './analytics';
// ClickHouse 客户端配置
const clickhouse = createClient({
url: process.env.CLICKHOUSE_URL,
username: process.env.CLICKHOUSE_USER ,
password: process.env.CLICKHOUSE_PASSWORD ,
database: process.env.CLICKHOUSE_DATABASE
});
// 构建日期过滤条件
function buildDateFilter(startTime?: string, endTime?: string): string {
const filters = [];
if (startTime) {
filters.push(`event_time >= parseDateTimeBestEffort('${startTime}')`);
}
if (endTime) {
filters.push(`event_time <= parseDateTimeBestEffort('${endTime}')`);
}
return filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
}
// 构建通用过滤条件
export function buildFilter(params: Partial<EventsQueryParams>): string {
console.log('buildFilter received params:', JSON.stringify(params));
const filters = [];
// 添加日期过滤条件
if (params.startTime || params.endTime) {
const dateFilter = buildDateFilter(params.startTime, params.endTime);
if (dateFilter) {
filters.push(dateFilter.replace('WHERE ', ''));
}
}
// 添加事件类型过滤条件
if (params.eventType) {
filters.push(`event_type = '${params.eventType}'`);
}
// 添加链接ID过滤条件
if (params.linkId) {
console.log('Adding link_id filter:', params.linkId);
filters.push(`link_id = '${params.linkId}'`);
}
// 添加链接Slug过滤条件
if (params.linkSlug) {
filters.push(`link_slug = '${params.linkSlug}'`);
}
// 添加用户ID过滤条件
if (params.userId) {
filters.push(`user_id = '${params.userId}'`);
}
// 添加子路径过滤条件
if (params.subpath) {
console.log('====== SUBPATH DEBUG ======');
console.log('Raw subpath param:', params.subpath);
console.log('Subpath type:', typeof params.subpath);
console.log('Subpath length:', params.subpath.length);
// 确保子路径没有前导斜杠,避免双斜杠问题
const cleanSubpath = params.subpath.startsWith('/')
? params.subpath.substring(1)
: params.subpath;
console.log('Cleaned subpath:', cleanSubpath);
// 在event_attributes JSON的full_url字段中查找subpath
const condition = `JSONExtractString(event_attributes, 'full_url') LIKE '%${cleanSubpath}%'`;
console.log('Final SQL condition:', condition);
console.log('==========================');
filters.push(condition);
}
// 添加团队ID过滤条件
if (params.teamId) {
filters.push(`team_id = '${params.teamId}'`);
}
// 处理多个团队ID
if (params.teamIds && params.teamIds.length > 0) {
filters.push(`team_id IN (${params.teamIds.map(id => `'${id}'`).join(', ')})`);
}
// 添加项目ID过滤条件
if (params.projectId) {
filters.push(`project_id = '${params.projectId}'`);
}
// 处理多个项目ID
if (params.projectIds && params.projectIds.length > 0) {
filters.push(`project_id IN (${params.projectIds.map(id => `'${id}'`).join(', ')})`);
}
// 处理标签过滤 - 使用LIKE来匹配标签字符串
if (params.tagIds && params.tagIds.length > 0) {
const tagConditions = params.tagIds.map(tag =>
`link_tags LIKE '%${tag}%'`
);
filters.push(`(${tagConditions.join(' OR ')})`);
}
return filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
}
// 构建分页条件
export function buildPagination(page: number = 1, pageSize: number = 20): string {
const offset = (page - 1) * pageSize;
return `LIMIT ${pageSize} OFFSET ${offset}`;
}
// 构建排序条件
export function buildOrderBy(sortBy: string = 'event_time', sortOrder: string = 'desc'): string {
return `ORDER BY ${sortBy} ${sortOrder}`;
}
// 执行查询
export async function executeQuery(query: string) {
console.log('Executing query:', query); // 查询日志
try {
const resultSet = await clickhouse.query({
query,
format: 'JSONEachRow',
});
const rows = await resultSet.json();
return rows;
} catch (error) {
console.error('查询执行错误:', error);
throw error;
}
}
// 执行返回单一结果的查询
export async function executeQuerySingle(query: string) {
console.log('Executing single result query:', query); // 查询日志
try {
const resultSet = await clickhouse.query({
query,
format: 'JSONEachRow',
});
const rows = await resultSet.json();
return rows.length > 0 ? rows[0] : null;
} catch (error) {
console.error('单一结果查询执行错误:', error);
throw error;
}
}
export default clickhouse;