158 lines
13 KiB
Markdown
158 lines
13 KiB
Markdown
# 灵犀 A5 审计与日志 · 实施报告 V0.1
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 实施日期 | 2026-11-04 |
|
||
| 批次 | A5(审计与日志) |
|
||
| 状态 | **已完成**(开发 + 全部门禁 + 浏览器 e2e 通过;未提交 git) |
|
||
| 依据 | 开发计划 V0.1 §6.6 / §7.5;PRD V1.6 §5.4.6 / R-05;技术架构 V0.4 §5.4.5 / §9.7;工程规范 08 |
|
||
| 视觉基线 | A0「方向 A · 云白蓝」;用户选定 **操作日志列表 A / 日志详情 A / 运行日志 C** |
|
||
| 迁移 | `prisma/migrations/20261104054622_a5_audit_logs/` |
|
||
| 前端路由 | `/system/audit-logs`、`/system/audit-logs/:id`、`/system/logs` |
|
||
|
||
---
|
||
|
||
## 1 范围与完成度
|
||
|
||
| 任务 | 内容 | 状态 |
|
||
| --- | --- | --- |
|
||
| A5-BE-01 | 审计表与 append-only(月分区 / 复合主键 / DB 触发器 / `lingxi_app` 最小权限) | ✅ |
|
||
| A5-BE-02 | 审计采集(`@Audit` + 拦截器落库、成功 / 失败双写、前后值、脱敏) | ✅ |
|
||
| A5-BE-03 | 审计检索与导出(多条件游标检索、详情 diff、异步 CSV、签名下载、导出自身留痕) | ✅ |
|
||
| A5-BE-04 | 敏感访问与保留归档(查看审计留痕、保留期配置、归档占位) | ✅ |
|
||
| A5-INF-01 | 运行日志与观测入口(Pino / trace_id / 日志平台配置与外链;OTel 待接) | ✅(OTel 待确认) |
|
||
| A5-FE-01 | 操作日志列表与详情(方案 A:筛选卡 + 全宽表格 + 游标分页 / 独立详情 + diff + 原始 JSON) | ✅ |
|
||
| A5-FE-02 | 导出与只读(560 抽屉、进度轮询、历史、签名下载、权限控制) | ✅ |
|
||
| A5-QA-01 | 不可篡改与留痕用例(7 类采样 + append-only + 浏览器 e2e) | ✅ |
|
||
|
||
> 侧栏同步更名:`审计日志` → **`操作日志`**、`客户字段` → **`字段管理`**,新增 **`运行日志`**;A1–A4 既有页面与功能未改动。
|
||
|
||
---
|
||
|
||
## 2 交付物
|
||
|
||
### 2.1 文档与契约
|
||
|
||
- 技术架构 §5.4.5 / §9.7 对应实现已回写本报告;开发计划 §6.6 / §7.5 / §13 状态与 WBS 已回写。
|
||
- `e2e/README.md` 增加 A5 用例与运行命令。
|
||
- UI 原型与实现截图:`design/a5-ui-v1/`(A/B/C 三套原型、对比页、评审记录、实现截图)。
|
||
|
||
### 2.2 共享契约(`packages/shared`)
|
||
|
||
- `audit/audit-actions.ts`:10 个审计分类(含 `other` 兜底)、54 个动作(现有 36 个 `@Audit` + A5 新增 + asset/task/sensitive 预留),`auditActionMeta` 未知 code 兜底。
|
||
- `audit/audit-diff.ts`:`buildAuditDiff` 纯函数(对象一层展开、数组 index、权限点数组增删语义);前后端共用。
|
||
- `schemas/audit.ts`:列表 / 详情 / 动作目录 / 导出创建 / 导出任务(含 `download_token`)/ 导出历史的 Zod 契约。
|
||
- `config/system-configs.ts`:新增 `audit` 分组与 3 项配置:`audit.retention_months`(默认 12,min 12)、`audit.export_max_rows`(默认 50,000)、`audit.logs_platform_url`(public,供运行日志页读取)。
|
||
- 单测:契约、动作目录、diff、配置校验(shared 合计 163)。
|
||
|
||
### 2.3 数据库与种子
|
||
|
||
迁移 `20261104054622_a5_audit_logs`(expand-only,新增表,不改历史迁移):
|
||
|
||
- `audit_logs`:`RANGE (occurred_at)` 按月分区,复合主键 `(id, occurred_at)`;字段覆盖主体 / 动作 / 分类 / 对象 / 客户 / 结果 / 请求 / trace / IP / UA / 前后值 / 原因 / 错误 / 耗时;6 组索引。
|
||
- `audit_logs_ensure_partitions(months_ahead int)`:幂等创建当月 ~ +N 月分区;迁移时创建 14 个月;应用启动 + 每日定时调用。
|
||
- append-only:`BEFORE UPDATE/DELETE` 行触发器 + `BEFORE TRUNCATE` 语句触发器(`insufficient_privilege`);`REVOKE UPDATE/DELETE/TRUNCATE FROM PUBLIC`;创建 `lingxi_app` NOLOGIN 角色并仅授予审计表 `SELECT/INSERT`。
|
||
- `audit_export_jobs`:任务状态 / 进度 / `truncated` / 文件信息 / 过期 / 下载计数 / `download_count`。
|
||
- 种子:`system_configs` 27→29 项(A5 新增 3);6 条纯追加演示审计(固定 ULID,重复执行跳过);`SEED.md` 更新。
|
||
|
||
### 2.4 后端(`apps/api`)
|
||
|
||
| 模块 | 接口 |
|
||
| --- | --- |
|
||
| platform/audit | `GET /audit-logs`、`GET /audit-logs/actions`、`GET /audit-logs/{id}` |
|
||
| platform/audit | `POST /audit-logs/export`、`GET /audit-logs/exports`、`GET /audit-logs/exports/{id}` |
|
||
| platform/audit | `GET /audit-logs/exports/{id}/download?token=`(`@Public` + HMAC 短时效) |
|
||
|
||
- `AuditInterceptor`:成功 / 失败都落库;响应前 `await`,写入失败只记 Pino、不影响主请求;`AUDIT_SINK` 可选注入,未装配退化为仅 Pino;修复原 `defer(ALS.run())` 导致 handler 内 `patchAudit` 丢失上下文的隐患。
|
||
- 采集覆盖:登录 / 登出 / 登录失败、角色与权限、用户 / 部门、客户 / 行业 / 客户组 / 客户字段、字典类型 / 条目、系统配置;前后值与 targetName / customerId / customerName。
|
||
- 敏感访问:审计列表与详情本身写 `audit.log.view`;导出与下载分别写 `audit.export` / `audit.export.download`。
|
||
- 导出:异步 `setImmediate`(A6 任务协议就绪后替换)、分批 CSV(UTF-8 BOM、转义)、`audit.export_max_rows` 截断标记、本地 `apps/api/.data/audit-exports/`、启动恢复卡死任务、HMAC 签名下载并计数。
|
||
- 保留 / 归档:`AuditRetentionService` 仅返回计划(`supported:false`),不 UPDATE/DELETE 审计数据(QT-10 待确认)。
|
||
- 查询:所有过滤在 SQL `where` 完成;稳定排序 `occurred_at DESC, id DESC`;关键字覆盖对象名 / 对象 ID / 请求路径 / 原因 / 操作人姓名 / 账号 / trace_id / IP。
|
||
|
||
### 2.5 前端(`apps/web`)
|
||
|
||
| 页面 | 路由 | 实现 |
|
||
| --- | --- | --- |
|
||
| 操作日志 | `/system/audit-logs` | 方案 A:页头只读标识 + 查看留痕提示 + 两行筛选(关键字 / 分类 / 操作人 / 客户 / 结果 / 时间预设 + 自定义日期)+ 全宽表格 + 真实游标「加载更多」 |
|
||
| 日志详情 | `/system/audit-logs/:id` | 方案 A:左 diff / 完整原始 JSON,右上下文 / 只读说明 / 相关记录;loading / error / 404 三态;失败请求可打开 |
|
||
| 运行日志 | `/system/logs` | 方案 C:平台入口卡 / trace 检索卡 / 服务状态卡 / 最近错误卡;真实数据仅 `audit.logs_platform_url`,其余为诚实空态 |
|
||
|
||
- 导出:560 SideSheet,创建 → 1.5s 轮询 → 成功 / 失败 / 过期 / 历史 / 刷新 / 签名下载;创建成功失效审计列表,使 `audit.export` 记录进入列表。
|
||
- 权限:`audit:read` 守卫列表 / 详情 / 运行日志;`audit:export` 控制导出入口(无权限不渲染)。
|
||
- 侧栏:`操作日志`(紧随数据字典)、`运行日志`、`字段管理`;面包屑详情补「管理中心 / 操作日志 / 日志详情」。
|
||
- 适配:1024 宽隐藏 IP / trace 并收窄列宽,保留 时间 / 操作人 / 操作 / 对象 / 客户 / 结果 / 查看;1440 九列完整。
|
||
- 数据层:`api/audit.ts`(shared Zod parse)、`hooks/queries/use-audit.ts`(InfiniteQuery / 详情 / 动作目录 / 导出轮询 / 创建 mutation)、`api/query-keys.ts`、`endpoints.ts`、`apiBaseUrl()`。
|
||
- 样式:`styles/audit.css`、`styles/run-logs.css`,只用 `--lx-*` token。
|
||
|
||
---
|
||
|
||
## 3 验证结果
|
||
|
||
| 门禁 | 命令 | 结果 |
|
||
| --- | --- | --- |
|
||
| 格式 | `pnpm format:check` | ✅ |
|
||
| Lint | `pnpm lint` | ✅ 0 error / 0 warning |
|
||
| 类型 | `pnpm typecheck` | ✅ 10/10 tasks |
|
||
| 单测(shared) | `pnpm --filter @lingxi/shared test` | ✅ 16 files / 163 tests |
|
||
| 单测(api) | `pnpm --filter @lingxi/api test` | ✅ 24 files / 164 tests(lines 65.75%,阈值 60) |
|
||
| 单测(web) | `pnpm --filter @lingxi/web test` | ✅ 32 files / 187 tests(lines 92.72%) |
|
||
| 集成 | `pnpm --filter @lingxi/api test:int` | ✅ 8 files / 70 tests(含 audit 12) |
|
||
| 构建 | `pnpm build` | ✅ 8/8 tasks |
|
||
| OpenAPI | `pnpm openapi:diff` | ✅ 无破坏性变更 |
|
||
| 浏览器 e2e | `A5_E2E=1 pnpm exec playwright test e2e/a5-audit-logs.spec.ts` | ✅ 5/5(约 57s) |
|
||
|
||
**e2e 覆盖**:列表 / 查看留痕提示 / 分类筛选 / 重置;详情 diff / 完整原始 JSON / trace / IP / 请求 / 耗时;导出创建 → 轮询完成 → 导出历史 → API 断言 `download_token` + CSV 200 → 列表新增 `audit.export`;无 `audit:read` → 403;有 `audit:read` 无 `audit:export` 不渲染导出;运行日志未配置空态 → 配置后外链出现。
|
||
|
||
**数据库实测**:`UPDATE / DELETE / TRUNCATE audit_logs` 均被 PG 拒绝;`SET LOCAL ROLE lingxi_app` 下 INSERT / SELECT 成功、UPDATE 被权限拒绝;分区函数重复执行 `created=0`;`prisma migrate status` up to date;seed 二次执行配置不覆盖、演示审计不重复。
|
||
|
||
**浏览器人工验收**(真实 API + PG + seed):1440 列表 / 详情 / 导出抽屉 / 运行日志(已配置 / 未配置)、1024 列表;无控制台错误、无横向溢出,body 不出现纵向滚动。
|
||
|
||
---
|
||
|
||
## 4 关键口径与实现说明
|
||
|
||
1. **append-only 双保险**:DB 触发器阻止任何角色 UPDATE/DELETE/TRUNCATE;生产应用账号应使用 `lingxi_app`(仅审计表 SELECT/INSERT)。本地仍用 postgres,由触发器兜底。
|
||
2. **分区无 default 分区**:迁移建 14 个月,启动 / 每日 `ensure` 继续创建;写入范围由 ensure 保证,避免 default 分区与后续 ATTACH 冲突。
|
||
3. **审计查看留痕**:列表 / 详情访问写 `audit.log.view`(LOG-14);因此频繁轮询列表会产生较多浏览记录,是否采样 / 仅详情留痕待产品确认。
|
||
4. **导出自身留痕**:创建写 `audit.export`,下载写 `audit.export.download`;创建成功前端失效列表,保证新记录可见。
|
||
5. **权限口径**:`audit:read` / `audit:export` 为特殊权限,默认关闭;无权限不渲染导出入口,接口仍校验(40303)。
|
||
6. **运行日志与审计分离**:运行日志只提供日志平台入口与 trace 检索指引,不写入业务库;服务状态 / 错误列表无后端接口,页面用空态说明,不伪造数据。
|
||
7. **时间筛选包含当天**:预设「今天 / 近 7 天 / 近 30 天」`to` 取当天 23:59:59.999,保证列表刷新后能看到当天新产生的审计记录。
|
||
8. **不伪造统计**:后端未提供 summary 接口,列表页不展示 KPI 数字,只显示真实「已加载 N 条 / 还有更多 / 已全部加载」。
|
||
9. **导出预计条数**:无服务端 count 接口,抽屉显示「当前筛选已加载 N 条,实际以服务端筛选为准」,不编造条数。
|
||
10. **详情 diff label**:后端只返回路径(label 为 null),前端用本地路径中文名映射兜底。
|
||
|
||
---
|
||
|
||
## 5 与计划的差异与待确认
|
||
|
||
| 编号 | 项 | 现状 |
|
||
| --- | --- | --- |
|
||
| A5-D-01 | 七类留痕 | 登录 / 权限 / 客户与字典等配置 / 导出 / 敏感访问已实测;资产(B1)与任务(A6)接口未建设,已登记 action code 与预留,未伪造数据 |
|
||
| A5-D-02 | OTel 接入 | SDK 属新增运行时依赖,本轮未引入;Pino 字段、trace_id 贯通、审计与运行日志分离已就绪,运行日志页标注「OTel 待接入」 |
|
||
| A5-D-03 | 运行日志服务指标 | 无指标 / trace / 错误查询接口,页面按方案 C 布局但服务状态与最近错误为空态,不伪造 |
|
||
| A5-D-04 | 保留期 | 默认 / 下限 12 个月(min 12 / max 60),QT-10 最终值确认后改 shared 定义即可 |
|
||
| A5-D-05 | 审计查看留痕频率 | 列表每次请求都留痕,符合 LOG-14;如需采样 / 去重需产品确认 |
|
||
| A5-D-06 | 导出存储 | A5 使用本地 `apps/api/.data/audit-exports/`,B3 / A6 接入对象存储与统一任务协议后替换;过期文件未定时物理清理 |
|
||
| A5-D-07 | 导出并发 | 未做限流,待 A6 / 运维策略 |
|
||
| A5-D-08 | e2e 审计数据 | 审计表 append-only,e2e 产生的审计记录不清理(仅清理临时账号 / 角色并还原配置),属预期留痕 |
|
||
|
||
---
|
||
|
||
## 6 已知遗留
|
||
|
||
1. 资产 / 任务 / 成本 / 客户资产包接口就绪后接入 `asset.*` / `task.*` / `sensitive.*` 动作(action code 已登记)。
|
||
2. 导出文件物理清理、并发限流、对象存储签名 URL(B3 / A6)。
|
||
3. OTel SDK 与日志平台真实查询接口(待依赖评审 / 运维提供)。
|
||
4. 列表「查看」动作产生 `audit.log.view` 记录,后续可按需调整采样策略。
|
||
5. UI 原型中 A/B/C 三套为设计产物(`design/` 目录不入库);实现按用户选定 A + A + C 落地。
|
||
|
||
---
|
||
|
||
## 7 下一步
|
||
|
||
- 按计划进入 **A6 任务中心与队列骨架**:审计导出将迁移到统一任务协议(进度 / 重试 / 取消 / 幂等)。
|
||
- A7 AI 配置可直接复用审计采集与 `@Audit` 装饰器;资产 / 任务接入后补齐七类留痕。
|
||
- QT-10 / Q-08 保留期、OTel 依赖、审计查看采样策略确认后回写本报告。
|