diff --git a/src/server/index.ts b/src/server/index.ts index 4105cca..ef4cf4d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -192,15 +192,40 @@ function deriveTargetId(path: string): string | null { return m && m[1] !== undefined ? decodeURIComponent(m[1]) : null; } -// 系统操作审计:记录全部写操作(POST/PUT/DELETE)+ 登录,供系统管理员审计。 +/** + * 关键只读操作的审计标签(GET)。仅记录有审计价值的读操作(导出/查看报告/查看评估详情), + * 跳过看板轮询、列表与聚合等高频只读端点(summary/expiring/overdue/accuracy/calibration 等), + * 避免审计日志被噪音淹没。返回 null 表示该 GET 不记录。 + */ +function deriveReadActionLabel(path: string): string | null { + if (/^\/api\/assessments\/[^/]+\/report\/export$/.test(path)) return '导出报告'; + if (/^\/api\/assessments\/[^/]+\/report$/.test(path)) return '查看报告'; + // 单条评估详情查看(排除聚合/列表类只读端点)。 + if (/^\/api\/assessments\/[^/]+$/.test(path)) { + const id = path.split('/')[3] ?? ''; + const skip = ['summary', 'expiring', 'overdue', 'run', 'profitability']; + if (id !== '' && !skip.includes(id)) return '查看评估详情'; + } + return null; +} + +// 系统操作审计:记录全部写操作(POST/PUT/DELETE)+ 登录 + 关键只读操作(导出/查看报告/查看详情),供系统管理员审计。 app.use('/api/*', async (c, next) => { const method = c.req.method; const start = Date.now(); await next(); - if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return; + if (method === 'HEAD' || method === 'OPTIONS') return; if (pool === null) return; const path = c.req.path; if (path === '/api/system-logs') return; // 不记录查询日志自身 + + // GET:仅记录有审计价值的关键只读操作,其余(看板轮询/列表/聚合)跳过。 + let readLabel: string | null = null; + if (method === 'GET') { + readLabel = deriveReadActionLabel(path); + if (readLabel === null) return; + } + const payload = (c as import('hono').Context).get('user') as AuthPayload | undefined; let actorId = payload?.uid ?? null; let actorName = payload?.username ?? null; @@ -218,7 +243,7 @@ app.use('/api/*', async (c, next) => { } // 业务动作增强:解析目标项目名 + 决策(通过/驳回等)。 - let action = deriveActionLabel(method, path); + let action = method === 'GET' ? readLabel as string : deriveActionLabel(method, path); let targetName: string | null = null; const targetId = deriveTargetId(path); const isAssessmentPath = /^\/api\/assessments\/[^/]+/.test(path) && targetId !== null && targetId !== 'run' && targetId !== 'profitability' && targetId !== 'summary' && targetId !== 'expiring' && targetId !== 'overdue';