UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

180 lines (179 loc) 6.02 kB
/** AuditStore - 审计日志存储(全 Drizzle 类型安全) */ import { and, avg, count, desc, eq, gte, lte, sql } from 'drizzle-orm'; import { getDrizzle } from '../database/drizzle/index.js'; import { auditLogs } from '../database/drizzle/schema.js'; export class AuditStore { #drizzle; constructor(db, drizzle) { this.#drizzle = drizzle ?? getDrizzle(); } /** 保存审计日志 */ async save(entry) { this.#drizzle .insert(auditLogs) .values({ id: entry.id, timestamp: entry.timestamp, actor: entry.actor, actorContext: entry.actor_context, action: entry.action, resource: entry.resource, operationData: entry.operation_data, result: entry.result, errorMessage: entry.error_message, duration: entry.duration, }) .run(); } /** 查询审计日志(动态多条件,全 Drizzle) */ query(filters = {}) { const conditions = []; if (filters.actor) { conditions.push(eq(auditLogs.actor, filters.actor)); } if (filters.action) { conditions.push(eq(auditLogs.action, filters.action)); } if (filters.result) { conditions.push(eq(auditLogs.result, filters.result)); } if (filters.startDate) { conditions.push(gte(auditLogs.timestamp, filters.startDate)); } if (filters.endDate) { conditions.push(lte(auditLogs.timestamp, filters.endDate)); } const condition = conditions.length > 0 ? and(...conditions) : undefined; let query = this.#drizzle .select() .from(auditLogs) .where(condition) .orderBy(desc(auditLogs.timestamp)); if (filters.limit) { query = query.limit(filters.limit); } return query.all(); } /** 根据请求 ID 查询 */ findByRequestId(requestId) { return this.#drizzle.select().from(auditLogs).where(eq(auditLogs.id, requestId)).get(); } /** 根据角色查询 */ findByActor(actor, limit = 100) { return this.#drizzle .select() .from(auditLogs) .where(eq(auditLogs.actor, actor)) .orderBy(desc(auditLogs.timestamp)) .limit(limit) .all(); } /** 根据操作查询 */ findByAction(action, limit = 100) { return this.#drizzle .select() .from(auditLogs) .where(eq(auditLogs.action, action)) .orderBy(desc(auditLogs.timestamp)) .limit(limit) .all(); } /** 根据结果查询 */ findByResult(result, limit = 100) { return this.#drizzle .select() .from(auditLogs) .where(eq(auditLogs.result, result)) .orderBy(desc(auditLogs.timestamp)) .limit(limit) .all(); } /** 获取统计数据(全 Drizzle) */ getStats(timeRange = '24h') { const hours = timeRange === '24h' ? 24 : timeRange === '7d' ? 168 : 720; // 30d const startTime = Date.now() - hours * 60 * 60 * 1000; const startCondition = gte(auditLogs.timestamp, startTime); // 总数 const [totalRow] = this.#drizzle .select({ count: count() }) .from(auditLogs) .where(startCondition) .all(); const total = totalRow?.count ?? 0; // 成功数 const [successRow] = this.#drizzle .select({ count: count() }) .from(auditLogs) .where(and(startCondition, eq(auditLogs.result, 'success'))) .all(); const successCount = successRow?.count ?? 0; // 失败数 const [failureRow] = this.#drizzle .select({ count: count() }) .from(auditLogs) .where(and(startCondition, eq(auditLogs.result, 'failure'))) .all(); const failureCount = failureRow?.count ?? 0; // 按角色统计 const byActor = this.#drizzle .select({ actor: auditLogs.actor, count: count(), }) .from(auditLogs) .where(startCondition) .groupBy(auditLogs.actor) .orderBy(desc(count())) .all(); // 按操作统计 const byAction = this.#drizzle .select({ action: auditLogs.action, count: count(), }) .from(auditLogs) .where(startCondition) .groupBy(auditLogs.action) .orderBy(desc(count())) .all(); // 平均响应时间 const [avgRow] = this.#drizzle .select({ avg_duration: avg(auditLogs.duration), }) .from(auditLogs) .where(and(startCondition, sql `${auditLogs.duration} IS NOT NULL`)) .all(); const avgDuration = avgRow?.avg_duration ? `${Math.round(Number(avgRow.avg_duration))}ms` : 'N/A'; return { timeRange, total, success: successCount, failure: failureCount, successRate: total > 0 ? `${((successCount / total) * 100).toFixed(2)}%` : '0%', avgDuration, byActor, byAction, }; } /** * 清理过期审计日志 * @param [opts.maxAgeDays=90] 保留天数 */ cleanup({ maxAgeDays = 90 } = {}) { try { const cutoff = Date.now() - maxAgeDays * 86400000; const result = this.#drizzle .delete(auditLogs) .where(sql `${auditLogs.timestamp} < ${cutoff}`) .run(); return { deleted: result.changes || 0 }; } catch { return { deleted: 0 }; } } } export default AuditStore;