13 KiB
13 KiB
灵犀 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_appNOLOGIN 角色并仅授予审计表SELECT/INSERT。 audit_export_jobs:任务状态 / 进度 /truncated/ 文件信息 / 过期 / 下载计数 /download_count。- 种子:
system_configs27→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 关键口径与实现说明
- append-only 双保险:DB 触发器阻止任何角色 UPDATE/DELETE/TRUNCATE;生产应用账号应使用
lingxi_app(仅审计表 SELECT/INSERT)。本地仍用 postgres,由触发器兜底。 - 分区无 default 分区:迁移建 14 个月,启动 / 每日
ensure继续创建;写入范围由 ensure 保证,避免 default 分区与后续 ATTACH 冲突。 - 审计查看留痕:列表 / 详情访问写
audit.log.view(LOG-14);因此频繁轮询列表会产生较多浏览记录,是否采样 / 仅详情留痕待产品确认。 - 导出自身留痕:创建写
audit.export,下载写audit.export.download;创建成功前端失效列表,保证新记录可见。 - 权限口径:
audit:read/audit:export为特殊权限,默认关闭;无权限不渲染导出入口,接口仍校验(40303)。 - 运行日志与审计分离:运行日志只提供日志平台入口与 trace 检索指引,不写入业务库;服务状态 / 错误列表无后端接口,页面用空态说明,不伪造数据。
- 时间筛选包含当天:预设「今天 / 近 7 天 / 近 30 天」
to取当天 23:59:59.999,保证列表刷新后能看到当天新产生的审计记录。 - 不伪造统计:后端未提供 summary 接口,列表页不展示 KPI 数字,只显示真实「已加载 N 条 / 还有更多 / 已全部加载」。
- 导出预计条数:无服务端 count 接口,抽屉显示「当前筛选已加载 N 条,实际以服务端筛选为准」,不编造条数。
- 详情 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 已知遗留
- 资产 / 任务 / 成本 / 客户资产包接口就绪后接入
asset.*/task.*/sensitive.*动作(action code 已登记)。 - 导出文件物理清理、并发限流、对象存储签名 URL(B3 / A6)。
- OTel SDK 与日志平台真实查询接口(待依赖评审 / 运维提供)。
- 列表「查看」动作产生
audit.log.view记录,后续可按需调整采样策略。 - UI 原型中 A/B/C 三套为设计产物(
design/目录不入库);实现按用户选定 A + A + C 落地。
7 下一步
- 按计划进入 A6 任务中心与队列骨架:审计导出将迁移到统一任务协议(进度 / 重试 / 取消 / 幂等)。
- A7 AI 配置可直接复用审计采集与
@Audit装饰器;资产 / 任务接入后补齐七类留痕。 - QT-10 / Q-08 保留期、OTel 依赖、审计查看采样策略确认后回写本报告。