Files
shorturl-analytics/lib/clickhouse.ts
2025-03-31 16:46:33 +08:00

127 lines
3.4 KiB
TypeScript

import { createClient } from '@clickhouse/client';
import type { EventsQueryParams } from './types';
// 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 ')}` : '';
}
// 字符串转义函数
function escapeString(str: string): string {
return str.replace(/'/g, "\\'");
}
// 构建通用过滤条件
export function buildFilter(params: Partial<EventsQueryParams>): string {
const filters = [];
// 时间范围过滤
if (params.startTime || params.endTime) {
const dateFilter = buildDateFilter(params.startTime, params.endTime).replace('WHERE ', '');
if (dateFilter) {
filters.push(dateFilter);
}
}
// 事件类型过滤
if (params.eventType) {
filters.push(`event_type = '${escapeString(params.eventType)}'`);
}
// 链接ID过滤
if (params.linkId) {
filters.push(`link_id = '${escapeString(params.linkId)}'`);
}
// 链接短码过滤
if (params.linkSlug) {
filters.push(`link_slug = '${escapeString(params.linkSlug)}'`);
}
// 用户ID过滤
if (params.userId) {
filters.push(`user_id = '${escapeString(params.userId)}'`);
}
// 团队ID过滤
if (params.teamId) {
filters.push(`team_id = '${escapeString(params.teamId)}'`);
}
// 项目ID过滤
if (params.projectId) {
filters.push(`project_id = '${escapeString(params.projectId)}'`);
}
// 标签筛选
if (params.tags && params.tags.length > 0) {
const tagConditions = params.tags.map(tag =>
`JSONHas(JSONExtractArrayRaw(link_tags), JSON_QUOTE('${escapeString(tag)}'))`
);
filters.push(`(${tagConditions.join(' OR ')})`);
}
// Slug 模糊搜索
if (params.searchSlug) {
filters.push(`positionCaseInsensitive(link_slug, '${escapeString(params.searchSlug)}') > 0`);
}
return filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
}
// 构建分页
export function buildPagination(page?: number, pageSize?: number): string {
const limit = pageSize || 20;
const offset = ((page || 1) - 1) * limit;
return `LIMIT ${limit} OFFSET ${offset}`;
}
// 构建排序
export function buildOrderBy(sortBy?: string, sortOrder?: 'asc' | 'desc'): string {
if (!sortBy) {
return 'ORDER BY event_time DESC';
}
return `ORDER BY ${sortBy} ${sortOrder || 'desc'}`;
}
// 执行查询并处理错误
export async function executeQuery<T>(query: string): Promise<T[]> {
try {
const resultSet = await clickhouse.query({
query,
format: 'JSONEachRow'
});
const rows = await resultSet.json<T>();
return Array.isArray(rows) ? rows : [rows];
} catch (error) {
console.error('ClickHouse query error:', error);
throw error;
}
}
// 执行查询并返回单个结果
export async function executeQuerySingle<T>(query: string): Promise<T | null> {
const results = await executeQuery<T>(query);
return results.length > 0 ? results[0] : null;
}
export default clickhouse;