67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
// shared/lib/audit/write-audit-log.ts — 通用审计日志写入函数
|
||
export interface AuditLogEntry {
|
||
userId?: number | null
|
||
username?: string | null
|
||
apiKeyId?: number | null
|
||
action: string
|
||
entityType: string
|
||
entityId?: number | null
|
||
details?: Record<string, unknown> | null
|
||
ipAddress?: string | null
|
||
}
|
||
|
||
export interface AuditStore {
|
||
exec(sql: string): void
|
||
}
|
||
|
||
// 写入审计日志(通用版本,调用方传入 store 执行 SQL)
|
||
export function writeAuditLog(store: AuditStore, entry: AuditLogEntry): void {
|
||
const userId = entry.userId ?? 'NULL'
|
||
const username = entry.username ? `'${entry.username.replace(/'/g, "''")}'` : 'NULL'
|
||
const apiKeyId = entry.apiKeyId ?? 'NULL'
|
||
const action = `'${entry.action.replace(/'/g, "''")}'`
|
||
const entityType = `'${entry.entityType.replace(/'/g, "''")}'`
|
||
const entityId = entry.entityId ?? 'NULL'
|
||
const details = entry.details ? `'${JSON.stringify(entry.details).replace(/'/g, "''")}'` : 'NULL'
|
||
const ipAddress = entry.ipAddress ? `'${entry.ipAddress.replace(/'/g, "''")}'` : 'NULL'
|
||
|
||
const sql = `INSERT INTO audit_logs (user_id, username, api_key_id, action, entity_type, entity_id, details, ip_address, created_at)
|
||
VALUES (${userId}, ${username}, ${apiKeyId}, ${action}, ${entityType}, ${entityId}, ${details}, ${ipAddress}, datetime('now', '+8 hours'))`
|
||
|
||
try {
|
||
store.exec(sql)
|
||
} catch (e) {
|
||
// 审计写入失败不阻断主操作
|
||
console.error('审计日志写入失败:', e)
|
||
}
|
||
}
|
||
|
||
// 对象差异对比(用于审计详情)
|
||
export function diffObjects(
|
||
before: Record<string, unknown>,
|
||
after: Record<string, unknown>,
|
||
): Record<string, { from: unknown; to: unknown }> {
|
||
const changes: Record<string, { from: unknown; to: unknown }> = {}
|
||
const keys = new Set([...Object.keys(before), ...Object.keys(after)])
|
||
|
||
for (const key of keys) {
|
||
// 跳过系统字段
|
||
if (['created_at', 'updated_at'].includes(key)) continue
|
||
|
||
const from = before[key]
|
||
const to = after[key]
|
||
if (JSON.stringify(from) !== JSON.stringify(to)) {
|
||
changes[key] = { from, to }
|
||
}
|
||
}
|
||
|
||
return changes
|
||
}
|
||
|
||
// 获取客户端 IP
|
||
export function getClientIP(request: Request): string | null {
|
||
const forwarded = request.headers.get('x-forwarded-for')
|
||
if (forwarded) return forwarded.split(',')[0].trim()
|
||
return request.headers.get('x-real-ip') ?? null
|
||
}
|