1. 页面概览

1.1 是什么

审计管理页面是 LightFoundry 面向平台管理员与安全审计人员的操作入口,路由为 /foundry/audit。它提供四类能力:审计事件列表查询、审计统计摘要、审计日志导出(CSV/JSON)与审计热数据手动归档。所有能力都对接 Foundry 后端 18081 端口的 /api/v1/audit/* 路由组,与 AIP 管理台的 AuditLogPage 相对独立,二者共用同一套平台级审计存储,但查询入口与管理能力各自归属所属产品。

从数据流看,页面的数据来源是 platform/audit 审计服务写入的 audit_log 表。系统中任何关键业务动作(登录、数据源操作、SQL 执行、AI 决策、报表派发、工作流触发等)都会经 LogEventRef 记录一条审计事件,包含用户、事件类型、结果、引用对象与详情。审计管理页把这些事件以表格形式呈现,并支持按事件类型过滤、按数量分页读取、按 CSV/JSON 两种格式导出,以及把超过保留天数的热数据归档为 JSON Lines 文件。

1.2 核心价值

价值点说明
事件可见性审计事件列表展示时间、用户、事件类型、结果、引用与详情,便于回溯"谁在什么时候对什么做了什么"
统计摘要按事件类型(by_category)、动作(by_action)、结果(by_status)三个维度分组计数,一眼掌握系统活跃度与失败面
合规导出支持 CSV(UTF-8 BOM + 中文表头,Excel 直接打开不乱码)与 JSON 两种格式,满足外审与归档留痕
热数据治理手动归档可将超过保留天数的热数据导出为 JSON Lines 文件并从热库删除,控制审计表体积
独立治理与 AIP 审计页各自归属产品治理,本页专管 Foundry 域内审计事件

1.3 一句话总结

审计管理页是 Foundry 审计事件的"查询台 + 出口",把平台级审计留痕以列表、摘要、导出、归档四种形态交付给管理员与安全审计人员。

2. 访问入口

2.1 路由与菜单

项目
路由 path/foundry/audit
路由 nameFoundryAudit
路由 titleFoundry 审计管理
requiresAuthtrue
菜单位置Foundry 左侧边栏(FoundryLayout.vue)菜单项「审计管理」
前端源码action/web/src/views/FoundryAuditPage.vue
路由注册action/web/src/router/index.js

侧边栏菜单在 action/web/src/views/FoundryLayout.vue 中注册,菜单项为 { path: '/foundry/audit', label: '审计管理' }。点击后进入页面,同时页面标题区展示「Foundry 审计管理」标题。

2.2 认证与权限

页面 meta 配置 requiresAuth: true,未登录访问会被前端路由守卫拦到登录页。页面所有请求通过 action/web/src/api/client.js 的 axios 实例发出:

所以审计管理页的鉴权标识是 aip_token(与 AIP 共用同一登录体系,Foundry 挂载在 FoundryLayout 下复用同一 Token)。若后端返回 401,页面本身不做特殊提示,而是整体跳转登录页。

2.3 端口与 API 前缀

Foundry 后端默认端口 18081,API 前缀 /api(实际请求路径均带 /v1,即 /api/v1/audit/*)。开发环境下由 Vite(默认 5173)代理转发。

3. 界面布局

+--------------------------------------------------------------------+
| Foundry 审计管理                                                    |
+--------------------------------------------------------------------+
| [操作结果提示 alert(有消息时显示,可关闭)]                            |
+--------------------------------------------------------------------+
| 统计摘要 summary(card,有数据时显示)                                 |
|  + 事件总数(大数字) | 按事件类型 by_category | 按动作 by_action |      |
|  |                  | 按结果 by_status                                |
+--------------------------------------------------------------------+
| 审计事件 events(card)                                               |
|  筛选栏:□事件类型(可多选)  [50条▾] [刷新] [导出CSV] [导出JSON]        |
|  表格:时间 | 用户 | 事件类型 | 结果 | 引用 | 详情 action_details      |
+--------------------------------------------------------------------+
| 归档 archive(card)                                                  |
|  保留天数[90]  [手动归档]                                             |
|  表格:归档时间 | 范围 | 条数 | 大小 | 文件路径                        |
+--------------------------------------------------------------------+

各板块职责:

4. 交互元素详解

4.1 审计事件卡片

元素位置含义必填与默认值操作效果触发的后端调用
事件类型复选框筛选栏按事件类型过滤默认全不选(不过滤)勾选后点「刷新」按逗号拼接传入 event_typeGET /audit/events?event_type=...
每页条数下拉框筛选栏单次加载条数(即后端 limit,上限 1000)默认「50 条」切换后回到第 1 页并按新条数查询GET /audit/events?limit=50|100|200|500|1000
上一页 / 下一页列表下方翻页条offset 翻页第 1 页时「上一页」禁用;最后一页时「下一页」禁用offset=(页码-1)×每页条数 重查GET /audit/events?limit=&offset=
刷新按钮筛选栏重新加载摘要与事件并行刷新摘要与事件列表GET /audit/events/summary + GET /audit/events
导出全量 CSV 按钮筛选栏导出当前过滤范围内全量日志为 CSV(不跟随分页)加载/导出中禁用blob 下载 audit_export_<时间戳>.csvGET /audit/events/export?format=csv
导出全量 JSON 按钮筛选栏导出当前过滤范围内全量日志为 JSON(不跟随分页)加载/导出中禁用blob 下载 audit_export_<时间戳>.jsonGET /audit/events/export?format=json

4.2 审计事件表格列

列名渲染逻辑说明
时间formatTime(log.timestamp)T 替换为空格、截前 19 位服务器本地时间的文本形式
用户log.user_id操作者用户 ID
事件类型log.event_typeUSER_LOGINNLQ_QUERYAI_DECISION
结果log.result,标签着色(tag-success/tag-error)成功/失败等结果状态
引用ref_type:ref_id(无则显示 -关联对象类型与对象 ID
详情 action_detailsformatDetails:对象转 JSON 字符串,字符串原样动作详情,超宽省略号截断

4.3 归档卡片

元素位置含义必填与默认值操作效果触发的后端调用
保留天数输入框归档表单超过该天数的热数据将被归档,0 = 全部热数据默认 90,type=number min=0改动后点「手动归档」生效POST /audit/archive,body {retention_days}
手动归档按钮归档表单触发归档归档进行中显示「归档中...」并禁用成功后提示"归档完成:共 N 条,文件 ...",并刷新归档状态、摘要、事件列表POST /audit/archive
归档记录表格归档卡片下方展示历史归档只读展示GET /audit/archive/status

归档记录表列:归档时间(archived_at)、范围(start_time ~ end_time)、条数(count)、大小(formatSize(size_bytes),自动 B/KB/MB)、文件路径(file_path)。

5. 后端关联

5.1 API 客户端

页面使用 action/web/src/api/client.js 的默认导出 apiClient

页面中所有 apiClient.get/post 均不带全路径前缀,直接写 /audit/... 相对路径。

5.2 端点表

方法路径请求参数超时
GET/audit/eventsquery:limit(默认 50,上限 1000)、offset(默认 0)、event_type(逗号分隔列表)、user_idstart_time/end_time(RFC3339)30s
GET/audit/events/summaryquery:start_time/end_time(可选,缺省统计全部)30s
GET/audit/events/exportquery:format(csv|json,默认 json)、event_typeuser_idactionresource_typeresultstart_time/end_time;前端带 responseType: 'blob'30s
POST/audit/archivebody:{retention_days}(或 query retention_days,默认 90)30s
GET/audit/archive/status30s
注意:前端导出与列表共用同一套过滤条件——除 format 外,还按筛选栏的 event_typeuser_idstart_time/end_time 一并传参;导出不跟随列表分页 offset,始终导出当前过滤条件下的全量(后端 QueryEvents 仅在 filter.Limit>0 时截断,导出侧未设 Limit,见 platform/repository/crud.go:913)。后端导出另支持的 action/resource_type/result 过滤暂未在 UI 暴露,不随导出携带。

5.3 响应结构

统一成功包裹:{"code": 0, "data": ...};错误:{"code": "<错误码>", "error": "<详情>"},HTTP 状态码取错误类型。

事件列表响应 data

{
  "logs": [
    {
      "id": "audit_log_xxxx",
      "user_id": "alice",
      "timestamp": "2026-08-30T10:15:00+08:00",
      "event_type": "USER_LOGIN",
      "action_details": {"ip": "10.0.0.5", "method": "password"},
      "result": "SUCCESS",
      "ref_type": "user",
      "ref_id": "u_1"
    }
  ]
}

摘要响应 data

{
  "total": 1024,
  "by_category": {"USER_LOGIN": 512, "NLQ_QUERY": 400},
  "by_action": {"USER_LOGIN": 512, "NLQ_QUERY": 400},
  "by_status": {"SUCCESS": 980, "FAILURE": 44}
}

归档结果响应 data

{
  "count": 360,
  "file_path": "data/archives/audit_20260830T...jsonl",
  "size_bytes": 245760,
  "start_time": "2026-06-01T00:00:00+08:00",
  "end_time": "2026-08-30T10:15:00+08:00",
  "archived_at": "2026-08-30T10:20:00+08:00"
}

归档状态响应 data{"archives": [ ...同上 ArchiveResult 数组... ]}

5.4 关联模块表

后端包职责
action/products/foundry/server/audit_handlers.go审计五个 handler:列表/摘要/导出/归档/归档状态
action/platform/audit审计服务:GetAuditLogsSummaryExportCSV/ExportJSONArchive/ArchiveStatus
action/platform/audit/hashchain.go审计哈希链:每条记录按前一条 hash 链式计算,防篡改
action/products/foundry/server/server.goprotected 组路由注册(1039-1043 行)与审计服务组装

5.5 关键机制

6. 核心流程详解

6.1 页面加载主流程

  1. onMounted 触发 handleRefresh()(并行 fetchSummary + fetchEvents)与 fetchArchiveStatus()
  2. fetchSummaryGET /audit/events/summary,成功后 summary.value = data.data;失败 showAlert('加载审计摘要失败:...', 'alert-error')
  3. fetchEventsloading=true,按 filters.limit 与勾选事件类型拼 event_type 逗号串调 GET /audit/events,成功取 data.data.logs;失败弹错;finally 复位 loading。
  4. fetchArchiveStatusGET /audit/archive/status,成功取 data.data.archives

事件类型选项是动态计算的:eventTypeOptions = 摘要 by_category 的键 ∪ 当前日志中出现的事件类型,去重后作为复选框集合。因此首次进入时如果摘要为空,选项集合可能为空,刷新出数据后选项自然增长。

6.2 过滤与查询分支

6.3 导出流程(CSV/JSON)

  1. 点「导出 CSV」或「导出 JSON」→ handleExport(format)exporting=true
  2. 组参:{format} + 与列表一致的过滤条件(event_type/user_id/start_time/end_time),不带 limit/offset——导出为当前过滤条件下的全量,不跟随列表分页(按钮已如实标注「导出全量」)。
  3. GET /audit/events/exportresponseType: 'blob'
  4. 成功后用 Blob 建 URL,创建 <a download="audit_export_<ts>.<format>"> 触发点击下载,随后 revokeObjectURL 释放。
  5. 提示"审计日志已导出为 CSV/JSON";失败提示"导出失败:..."。

6.4 归档流程

  1. 输入保留天数(默认 90),点「手动归档」→ handleArchivearchiving=true
  2. POST /audit/archive,body {retention_days: archiveDays.value}
  3. 成功:提示"归档完成:共 N 条,文件 <path>",随后并行刷新归档状态、摘要、事件列表。
  4. 失败:提示"归档失败:...";无论成败 finally 复位 archiving

6.5 状态与终态语义

审计事件没有前端状态机;其"终态"体现在 result 字段(SUCCESS/FAILURE 等),由各业务模块在写入审计时确定。归档是一次性任务:调 POST /audit/archive 同步返回结果(条数、文件路径),无任务轮询;归档记录长期保留在 audit/archive/status 列表中作为历史留痕。

7. 权限与安全

8. 常见问题与排错

问题 1:页面提示「加载审计事件失败:...」

问题 2:导出 CSV 在 Excel 打开乱码或下载名不对

问题 3:手动归档后热数据没有减少

问题 4:事件类型复选框无法勾选/为空

9. 已知缺陷与边界

说明
无编辑/删除单条审计记录只读,仅支持导出与归档,不提供单条修改(符合审计不可篡改原则)
分页无 total(边界)/audit/events 响应只有 {logs:[...]}、无 totalaudit_handlers.go:95)。已实现真 offset 分页:每页条数 50/100/200/500/1000,offset=(页码-1)×每页条数;因总条数不可知,上一页按 page>1、下一页按「本页行数 == 每页条数」满页启发式判断(最后一页恰满页时会多出一次空翻,前端自动回退一页)
与 AIP 审计页独立与 AIP AuditLogPage 各自独立实现,能力集合不同,勿混用接口前缀

注:时间过滤未暴露已于 2026-09-06 修复(筛选栏新增用户 ID 与开始/结束日期控件,直接对接后端 user_id/start_time/end_time 参数)。

注:归档无二次确认已于 2026-09-06 修复(「手动归档」提交前弹确认框、按钮为 danger 样式,保留天数 0 = 全量归档时提示更强文案)。

注:导出过滤子集已于 2026-09-06 修复(导出与列表共用同一套过滤条件,勾选的 event_type 及填写的用户/时间范围一并带入导出请求)。