UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

273 lines (272 loc) 9.38 kB
/** * AuditRepository — 审计日志的仓储实现 * * 从 AuditStore 提取的数据操作, * 使用 Drizzle 类型安全 API 操作 audit_logs 表。 */ import { and, avg, count, desc, eq, gt, gte, like, lte, sql } from 'drizzle-orm'; import { auditLogs } from '../../infrastructure/database/drizzle/schema.js'; import { RepositoryBase } from '../base/RepositoryBase.js'; /* ═══ Repository 实现 ═══ */ export class AuditRepositoryImpl extends RepositoryBase { constructor(drizzle) { super(drizzle, auditLogs); } /* ─── CRUD ─── */ async findById(id) { const row = this.drizzle.select().from(this.table).where(eq(this.table.id, id)).limit(1).get(); return row ? this.#mapRow(row) : null; } async create(data) { this.drizzle .insert(this.table) .values({ id: data.id, timestamp: data.timestamp, actor: data.actor, actorContext: data.actorContext ?? '{}', action: data.action, resource: data.resource ?? null, operationData: data.operationData ?? '{}', result: data.result, errorMessage: data.errorMessage ?? null, duration: data.duration ?? null, }) .run(); return (await this.findById(data.id)); } async delete(id) { const result = this.drizzle.delete(this.table).where(eq(this.table.id, id)).run(); return result.changes > 0; } /* ─── 查询 ─── */ /** 动态多条件查询 */ async query(filters = {}) { const conditions = []; if (filters.actor) { conditions.push(eq(this.table.actor, filters.actor)); } if (filters.action) { conditions.push(eq(this.table.action, filters.action)); } if (filters.result) { conditions.push(eq(this.table.result, filters.result)); } if (filters.startDate) { conditions.push(gte(this.table.timestamp, filters.startDate)); } if (filters.endDate) { conditions.push(lte(this.table.timestamp, filters.endDate)); } const condition = conditions.length > 0 ? and(...conditions) : undefined; let query = this.drizzle .select() .from(this.table) .where(condition) .orderBy(desc(this.table.timestamp)); if (filters.limit) { query = query.limit(filters.limit); } return query.all().map((r) => this.#mapRow(r)); } /** 根据请求 ID 查询 */ async findByRequestId(requestId) { return this.findById(requestId); } /** 根据角色查询 */ async findByActor(actor, limit = 100) { const rows = this.drizzle .select() .from(this.table) .where(eq(this.table.actor, actor)) .orderBy(desc(this.table.timestamp)) .limit(limit) .all(); return rows.map((r) => this.#mapRow(r)); } /** 根据操作查询 */ async findByAction(action, limit = 100) { const rows = this.drizzle .select() .from(this.table) .where(eq(this.table.action, action)) .orderBy(desc(this.table.timestamp)) .limit(limit) .all(); return rows.map((r) => this.#mapRow(r)); } /** 根据结果查询 */ async findByResult(result, limit = 100) { const rows = this.drizzle .select() .from(this.table) .where(eq(this.table.result, result)) .orderBy(desc(this.table.timestamp)) .limit(limit) .all(); return rows.map((r) => this.#mapRow(r)); } /* ─── 统计 ─── */ /** 获取统计数据 */ async getStats(timeRange = '24h') { const hours = timeRange === '24h' ? 24 : timeRange === '7d' ? 168 : 720; // 30d const startTime = Date.now() - hours * 60 * 60 * 1000; const startCondition = gte(this.table.timestamp, startTime); // 总数 const [totalRow] = this.drizzle .select({ cnt: count() }) .from(this.table) .where(startCondition) .all(); const total = totalRow?.cnt ?? 0; // 成功数 const [successRow] = this.drizzle .select({ cnt: count() }) .from(this.table) .where(and(startCondition, eq(this.table.result, 'success'))) .all(); const success = successRow?.cnt ?? 0; // 失败数 const [failureRow] = this.drizzle .select({ cnt: count() }) .from(this.table) .where(and(startCondition, eq(this.table.result, 'failure'))) .all(); const failure = failureRow?.cnt ?? 0; // 按角色统计 const byActor = this.drizzle .select({ actor: this.table.actor, count: count(), }) .from(this.table) .where(startCondition) .groupBy(this.table.actor) .orderBy(desc(count())) .all(); // 按操作统计 const byAction = this.drizzle .select({ action: this.table.action, count: count(), }) .from(this.table) .where(startCondition) .groupBy(this.table.action) .orderBy(desc(count())) .all(); // 平均响应时间 const [avgRow] = this.drizzle .select({ avgDuration: avg(this.table.duration), }) .from(this.table) .where(and(startCondition, sql `${this.table.duration} IS NOT NULL`)) .all(); const avgDuration = avgRow?.avgDuration ? `${Math.round(Number(avgRow.avgDuration))}ms` : 'N/A'; return { timeRange, total, success, failure, successRate: total > 0 ? `${((success / total) * 100).toFixed(2)}%` : '0%', avgDuration, byActor, byAction, }; } /* ─── 清理 ─── */ /** * 清理过期审计日志 * @param maxAgeDays 保留天数 */ async cleanup(maxAgeDays = 90) { try { const cutoff = Date.now() - maxAgeDays * 86400000; const result = this.drizzle .delete(this.table) .where(sql `${this.table.timestamp} < ${cutoff}`) .run(); return { deleted: result.changes ?? 0 }; } catch { return { deleted: 0 }; } } /** * Guard 违规规则名 TOP-N (SkillAdvisor.#getGuardPatterns) */ async findTopGuardViolationRules(minCount, limit) { return this.drizzle .select({ ruleName: sql `json_extract(${this.table.operationData}, '$.ruleName')`.as('ruleName'), cnt: count(), }) .from(this.table) .where(and(like(this.table.action, 'guard%'), eq(this.table.result, 'violation'))) .groupBy(sql `json_extract(${this.table.operationData}, '$.ruleName')`) .having(sql `count(*) >= ${minCount}`) .orderBy(desc(count())) .limit(limit) .all(); } /** * Guard 违规信号 (SignalCollector.#collectGuardSignals) */ async findGuardViolationSignals(limit) { return this.drizzle .select({ ruleName: sql `json_extract(${this.table.operationData}, '$.ruleName')`.as('ruleName'), cnt: count(), lastAt: sql `MAX(${this.table.timestamp})`.as('lastAt'), }) .from(this.table) .where(and(like(this.table.action, 'guard%'), eq(this.table.result, 'violation'))) .groupBy(sql `json_extract(${this.table.operationData}, '$.ruleName')`) .having(sql `count(*) > 0`) .orderBy(desc(count())) .limit(limit) .all(); } /** * 最近动作日志 (SignalCollector.#collectActionSignals) */ async findRecentActions(sinceTs, limit) { return this.drizzle .select({ actor: this.table.actor, action: this.table.action, resource: this.table.resource, result: this.table.result, timestamp: this.table.timestamp, }) .from(this.table) .where(gt(this.table.timestamp, sinceTs)) .orderBy(desc(this.table.timestamp)) .limit(limit) .all(); } /* ─── 内部辅助 ─── */ #mapRow(row) { return { id: row.id, timestamp: row.timestamp, actor: row.actor, actorContext: safeParseJSON(row.actorContext, {}), action: row.action, resource: row.resource ?? null, operationData: safeParseJSON(row.operationData, {}), result: row.result, errorMessage: row.errorMessage ?? null, duration: row.duration ?? null, }; } } function safeParseJSON(str, fallback) { try { return str ? JSON.parse(str) : fallback; } catch { return fallback; } }