Files
NextEdu/docs/architecture/audit/archive/audit-module-audit-report-v2.md

437 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 审计模块审计报告 v2
> 审计范围:`src/modules/audit/**`、`src/app/(dashboard)/admin/audit-logs/**`、`src/app/api/export/route.ts`(审计导出分支)、`src/app/api/cron/audit-cleanup/route.ts`、`src/shared/lib/audit-logger.ts`、`src/shared/lib/change-logger.ts`、`src/shared/i18n/messages/{zh-CN,en}/audit.json`
> 审计日期2026-06-25
> 审计依据:`docs/architecture/004_architecture_impact_map.md`2.15 节)、`docs/architecture/005_architecture_data.json`audit 节点)、`docs/standards/coding-standards.md`、项目 `project_rules.md`
> 前置报告:`docs/architecture/audit/audit-module-audit-report.md`v12026-06-24
---
## 一、现有实现概要
### 1.1 文件分布
v1 审计后已完成两轮重构P0-P2当前文件分布如下
| 层 | 文件 | 行数 | 职责 |
|------|------|------|------|
| 路由 | `app/(dashboard)/admin/audit-logs/page.tsx` | 79 | 操作日志列表页RSC调 data-access |
| 路由 | `app/(dashboard)/admin/audit-logs/login-logs/page.tsx` | 79 | 登录日志列表页 |
| 路由 | `app/(dashboard)/admin/audit-logs/data-changes/page.tsx` | 83 | 数据变更日志列表页 |
| 路由 | `app/(dashboard)/admin/audit-logs/overview/page.tsx` | 34 | 审计概览仪表盘(✅ v1 P2-4 新增) |
| 路由 | 4 个 `loading.tsx` + 4 个 `error.tsx` | — | 骨架屏 + 错误边界(✅ v1 P0-2 已修复) |
| 路由 | `app/api/export/route.ts` | 202 | 统一导出 API含 audit/login/dataChange 三分支) |
| 路由 | `app/api/cron/audit-cleanup/route.ts` | 75 | 定时清理 cron✅ v1 P2-5 新增) |
| actions | `modules/audit/actions.ts` | 195 | 7 个 Server Action1 查询 + 3 导出 + 3 保留策略) |
| data-access | `modules/audit/data-access.ts` | 387 | 12 个查询函数(分页 + 导出 + 选项 + 统计 + 趋势) |
| export | `modules/audit/export.ts` | 133 | Excel 列定义 + 行映射 + buildExport✅ v1 P1-3 已抽取) |
| retention | `modules/audit/retention.ts` | 123 | 保留策略配置读写 + 清理 + 纯函数(✅ v1 P2-5 新增) |
| types | `modules/audit/types.ts` | 145 | 类型定义 + 状态映射常量 |
| services | `modules/audit/services/audit-service.tsx` | 124 | AuditService 接口 + Context + hook✅ v1 P2-8 新增) |
| services | `modules/audit/services/admin-audit-service.ts` | 40 | 管理员默认实现 |
| services | `modules/audit/services/mock-audit-service.ts` | 57 | 测试用 Mock 实现 |
| hooks | `modules/audit/hooks/use-log-pagination.ts` | 24 | 分页 URL 状态 hook✅ v1 P1-2 已抽取) |
| 组件 | 15 个组件文件 | 19-191 | 表格/筛选器/视图/详情对话框/概览/图表/保留配置/骨架屏/错误边界 |
| 测试 | `export.test.ts` / `retention.test.ts` | 191/81 | 纯函数单测(✅ v1 P2-6 已补充) |
| i18n | `shared/i18n/messages/{zh-CN,en}/audit.json` | 130 | 完整翻译键table/filter/empty/export/error/detail/overview/retention |
| shared | `shared/lib/audit-logger.ts` / `change-logger.ts` | — | `logAudit()` / `logDataChange()` 写入日志 |
### 1.2 数据流
```
page.tsx (RSC)
├─ requirePermission(AUDIT_LOG_READ)
├─ getAuditLogs/getLoginLogs/getDataChangeLogs (data-access 直调)
└─ <AuditErrorBoundary>
<AuditLogView items={...} /> (Client Component)
├─ <AuditLogFilters> (nuqs URL 状态)
└─ <AuditLogTable> (分页 + 空状态 + 详情对话框)
overview/page.tsx (RSC)
├─ requirePermission(AUDIT_LOG_READ)
├─ getAuditOverviewStats / getAuditTrend / getDataChangeActionStats
└─ <AuditOverviewView> (RSC)
├─ <AuditOverviewStatsBar>
├─ <AuditActivityTrendChart>
├─ <DataChangeDistributionChart>
└─ <AuditRetentionSettings> (Client Component直调 Server Actions)
```
导出流:`<AuditLogExportButton>``fetch /api/export``exportAuditLogsAction``getAuditLogsForExport``buildAuditLogExport` → Excel buffer。
定时清理流:`/api/cron/audit-cleanup`CRON_SECRET 鉴权)→ `getAuditRetentionConfig``purgeExpiredAuditLogs`
### 1.3 架构图记录情况
- **0042.15 节)**:记录较完整,含 services/hooks/export/retention 节点,行数标注准确。
- **005audit 节点)**data-access/actions/types 记录完整,含 services/hooks/export/retention 节点。
- **已知不一致**services 层的 `AuditServiceProvider` 在架构图中标注为"已接入",但实际**未被任何页面使用**(详见 P0-1
---
## 二、现存问题与原因分析
### P0 — 严重违规
#### P0-1 Service 抽象层定义但从未使用(虚假完成)
- **位置**`services/audit-service.tsx``AuditServiceProvider``useAuditService``useAuditAnalytics`)、`services/admin-audit-service.ts``services/mock-audit-service.ts`
- **违反规则**:任务要求 → "完全解耦:通过定义 TypeScript 接口抽象数据依赖,使用 React Context或组合 Provider注入数据服务模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access"`project_rules.md` → "架构图优先规则:改码必同步图"
- **问题**
1. 全局搜索 `AuditServiceProvider` 的实际使用:**仅在 `audit-service.tsx``mock-audit-service.ts` 的 JSDoc `@example` 中出现**,没有任何页面或组件实际注入
2. 4 个 `page.tsx` 均直接 `import { getAuditLogs, ... } from "@/modules/audit/data-access"`
3. `audit-retention-settings.tsx` 直接 `import { getAuditRetentionConfigAction, ... } from "@/modules/audit/actions"`
4. `audit-log-export-button.tsx` 直接 `fetch("/api/export")` 绕过 Service 层
5. 架构图 004 第 1468 行声称"✅ P2-8 已修复:~~无 Service 接口抽象~~",实际为"定义但未接线"
- **原因**P2-8 仅创建了接口文件,未在页面层注入 Provider、未将组件改造为从 Context 获取 service
- **后果**
- DI 架构形同虚设,组件仍硬编码依赖 data-access无法在测试中注入 mock
- 架构图声称已修复,误导后续审计与维护决策
- `mockAuditService` 完全无引用(死代码)
#### P0-2 mock-audit-service.ts 使用 9 处 `as` 类型断言
- **位置**`services/mock-audit-service.ts` 第 29、31、33、36、45、46、47、53、60 行
- **违反规则**`project_rules.md` → "禁止 `as` 断言(除类型收窄外)"`project_memory.md` → "TypeScript strict mode: no `any`, no `as` assertions (except for type narrowing)"
- **示例**
```typescript
getAuditLogs: async () =>
({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 0 }) as PaginatedResult<AuditLog>,
```
- **原因**:对象字面量缺少 `items: [] as AuditLog[]` 类型标注,导致需要 `as` 断言整个返回值
- **后果**:违反 TS 严格规则lint 通过但 code review 应拦截
#### P0-3 全部 7 个 Server Action 缺少 revalidatePath
- **位置**`actions.ts` 所有 7 个 Action`getDataChangeLogsAction`、`exportAuditLogsAction`、`exportLoginLogsAction`、`exportDataChangeLogsAction`、`getAuditRetentionConfigAction`、`saveAuditRetentionConfigAction`、`purgeAuditLogsAction`
- **违反规则**`project_rules.md` → "Server Action 规范:使用 `revalidatePath` 精确刷新缓存"
- **问题**`saveAuditRetentionConfigAction` 和 `purgeAuditLogsAction` 修改数据后未刷新 overview 页面缓存,用户保存保留策略后看到的仍是旧配置
- **原因**:遗漏
- **后果**:保留策略变更后概览页缓存不刷新,显示过期数据
### P1 — 重要缺陷
#### P1-1 trackEvent event 名称误用(保留策略操作误标为导出)
- **位置**`actions.ts` 第 170、199 行(`saveAuditRetentionConfigAction`、`purgeAuditLogsAction`)、`app/api/cron/audit-cleanup/route.ts` 第 54 行
- **违反规则**:任务要求 → "监控:方案中预留关键操作埋点接口"
- **问题**:保留策略的保存/清理操作使用 `event: "audit.exported"`,语义错误
```typescript
// saveAuditRetentionConfigAction
await trackEvent({ event: "audit.exported", ... properties: { action: "save_config" } })
// purgeAuditLogsAction
void trackEvent({ event: "audit.exported", ... properties: { action: "purge" } })
// cron route
void trackEvent({ event: "audit.exported", ... properties: { action: "cron_purge" } })
```
- **后果**:分析平台中所有保留策略操作被归类为"导出",无法区分导出与清理行为
#### P1-2 AuditErrorBoundary 使用错误的 i18n namespace
- **位置**`components/audit-error-boundary.tsx` 第 19 行 `namespace="common"`
- **违反规则**`project_rules.md` → "所有用户可见文本必须适配 i18n使用 next-intl提取翻译键"
- **问题**`SectionErrorBoundary` 读取 `t("error.boundaryTitle")` / `t("error.boundaryDescription")` / `t("error.retry")`。`audit.json` 定义了 `error.title` / `error.description` / `error.retry`(键名不匹配),而 `common.json` 有 `error.boundaryTitle` / `error.boundaryDescription`。当前传 `"common"` 可工作但使用的是通用错误文案,未利用 audit 专属的 `error.description`"数据加载时发生错误,请稍后重试。"
- **后果**:区块级错误边界显示通用错误文案而非审计模块专属文案
#### P1-3 AuditAnalytics 接口定义为死代码
- **位置**`services/audit-service.tsx` 第 64-84 行(`AuditAnalytics` 接口 + `noopAnalytics` + `AuditAnalyticsContext` + `useAuditAnalytics` hook
- **违反规则**:任务要求 → "监控:方案中预留关键操作埋点接口"
- **问题**`useAuditAnalytics` hook 全局搜索仅出现在定义处,**无任何组件调用**。`trackLogView` / `trackExport` / `trackOverviewView` / `trackRetentionConfigChange` / `trackPurge` 五个埋点方法均为死代码
- **后果**:客户端层面无日志查看/导出/概览查看埋点,无法统计用户行为
#### P1-4 data-access 错误处理不一致
- **位置**`data-access.ts`
- **吞没错误**(返回空数组):`getAuditModuleOptions`(第 152 行)、`getDataChangeTableOptions`(第 237 行)
- **抛出错误**`getAuditLogs`、`getLoginLogs`、`getDataChangeLogs`、`getDataChangeStats`、`getAuditOverviewStats`、`getAuditTrend`、`getDataChangeActionStats`
- **违反规则**`project_rules.md` → "错误与边界处理:明确处理空数据、无权限、网络异常等边界状态"
- **后果**DB 故障时筛选器选项静默显示为空,用户误认为"无数据"而非"查询失败"
#### P1-5 日期筛选器 aria-label 不区分起止
- **位置**`audit-log-filters.tsx` 第 94、101 行;`login-log-filters.tsx` 第 78、85 行;`data-change-log-filters.tsx` 第 117、124 行
- **违反规则**`project_rules.md` → "可访问性a11y语义化标签、ARIA 属性、键盘导航"
- **问题**:两个日期 Input 均使用 `aria-label={t("table.time")}`"时间"),屏幕阅读器无法区分"开始日期"与"结束日期"
- **后果**:视障用户无法区分两个日期输入框
#### P1-6 AuditLogDetailDialog DialogDescription 重复标题
- **位置**`components/audit-log-detail-dialog.tsx` 第 135-137 行
- **问题**
```tsx
<DialogTitle>{t("detail.title")}</DialogTitle>
<DialogDescription className="sr-only">{t("detail.title")}</DialogDescription>
```
Description 与 Title 完全相同,无信息增量
- **后果**:无障碍辅助技术读出重复信息
#### P1-7 骨架屏缺少 aria-hidden
- **位置**`components/audit-log-table-skeleton.tsx`全文件、4 个 `loading.tsx`
- **违反规则**`project_rules.md` → "可访问性a11y"
- **问题**Skeleton 元素无 `aria-hidden="true"`,屏幕阅读器会逐个朗读骨架占位块
- **后果**:加载期间屏幕阅读器体验差
#### P1-8 window.confirm 阻塞式确认
- **位置**`components/audit-retention-settings.tsx` 第 80 行
- **问题**`window.confirm(t("purgeConfirm"))` 使用浏览器原生确认框不可自定义样式、阻塞主线程、a11y 差
- **后果**:与其他模块(使用 AlertDialog 组件)交互不一致
### P2 — 改进项
#### P2-1 数据变更 diff 无可视化
- **位置**`data-change-log-table.tsx` 第 118-129 行使用 `<pre>` 纯文本展示 oldValue / newValue
- **差距**PowerSchool / Google Workspace Audit / Microsoft Purview 均提供 JSON diff 高亮(增行绿、删行红、左右对比)
#### P2-2 无失败登录异常告警
- **位置**:全模块
- **差距**:行业产品支持阈值告警(如 5 分钟内同一 IP 失败登录 > 10 次触发告警)
#### P2-3 无 IP 地理位置
- **位置**:表格仅展示 IP 字符串
- **差距**:行业产品将 IP 解析为地理位置(城市/国家),辅助判断异地登录
#### P2-4 概览趋势仅 7 天不可配置
- **位置**`overview/page.tsx` 第 29 行 `getAuditTrend(7)` 硬编码 7 天
- **差距**:行业产品支持 7/30/90 天切换
#### P2-5 多学校数据隔离缺失
- **位置**`data-access.ts` 所有查询无 `schoolId` 过滤
- **说明**`audit_logs` / `login_logs` / `data_change_logs` 表无 `school_id` 字段schema 确认audit 模块设计为系统级跨校审计。当前 `AUDIT_LOG_READ` 权限若仅授予超级管理员则可接受,但需在文档中明确标注此设计决策
- **风险**:若未来授予校级管理员 `AUDIT_LOG_READ`,将导致跨校数据泄露
#### P2-6 useLogPagination hook 无单测
- **位置**`hooks/use-log-pagination.ts`
- **违反规则**`project_rules.md` → "可测试性:数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks"
- **说明**v1 已补充 export.test.ts 和 retention.test.ts但 hook 层无单测
#### P2-7 导出按钮无进度反馈
- **位置**`audit-log-export-button.tsx`
- **问题**:大范围导出仅显示 spinner无进度条/计数
- **差距**:行业产品显示"已导出 X / Y 条"
---
## 三、行业差距对比
| 能力 | PowerSchool / Veracross | Google Workspace Audit | Microsoft Purview | 本系统现状 | 影响 |
|------|------------------------|----------------------|-------------------|-----------|------|
| 统一审计概览仪表盘 | ✅ | ✅ | ✅ | ✅ 已有概览页 | — |
| 按用户/模块/动作/状态筛选 | ✅ | ✅ | ✅ | ✅ 已有完整筛选器 | — |
| 日志详情视图 | ✅ 点击展开完整 JSON | ✅ 详情面板 | ✅ 活动详情 | ✅ 已有详情对话框 | — |
| 数据变更 diff 可视化 | ✅ 左右对比 + 高亮 | ✅ | ✅ 差异高亮 | ❌ 纯文本 pre 展示 | 变更审查效率低 |
| 失败登录监控/告警 | ✅ 异常登录告警 | ✅ 可疑活动检测 | ✅ 实时告警 | ❌ 仅展示无告警 | 无法及时发现暴力破解 |
| 导出调度/定时 | ✅ 计划报告 | ✅ 导出 + 邮件 | ✅ 合规报告 | ❌ 仅手动导出 | 合规审计需人工操作 |
| 数据保留策略 | ✅ 可配置保留期 | ✅ | ✅ | ✅ 已有保留策略配置 | — |
| IP 地理位置 | ✅ | ✅ | ✅ | ❌ 仅显示 IP | 无法判断异地登录 |
| 多角色审计视图 | ✅ 管理员/合规官分级 | ✅ | ✅ | ⚠️ 仅 admin 单角色 + Service 接口预留 | 合规官角色需独立视图 |
| i18n | ✅ 多语言 | ✅ | ✅ | ✅ 已完整 i18n | — |
| 概览趋势可配置时间范围 | ✅ 7/30/90 天 | ✅ | ✅ | ❌ 硬编码 7 天 | 无法查看长期趋势 |
| DI 架构(可测试) | — | — | — | ⚠️ 定义未接线 | 组件无法 mock 数据源 |
---
## 四、改进优先级建议
### P0 — 立即修复(合规与架构正确性)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P0-1 | Service 抽象层未接线 | overview 页面接入 `AuditServiceProvider``AuditOverviewView` 内组件改用 `useAuditService()` 获取数据;或若评估后认为 RSC + props 模式已满足需求则**删除未使用的 services 层**避免误导 |
| P0-2 | mock-audit-service.ts 9 处 as 断言 | 为对象字面量添加元素类型标注(如 `items: [] as AuditLog[]` → 改用 `items: new Array<AuditLog>()` 或显式标注返回类型) |
| P0-3 | 7 个 Action 缺 revalidatePath | 写操作saveAuditRetentionConfigAction、purgeAuditLogsAction添加 `revalidatePath("/admin/audit-logs/overview")` |
### P1 — 本轮实施(规范与体验)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P1-1 | trackEvent event 误标 | 保留策略操作改用 `event: "audit.retention"` |
| P1-2 | ErrorBoundary namespace 错误 | `audit.json` 新增 `error.boundaryTitle` / `error.boundaryDescription``AuditErrorBoundary` 传 `namespace="audit"` |
| P1-3 | AuditAnalytics 死代码 | 组件中调用 `useAuditAnalytics()` 接入客户端埋点(日志查看、概览查看、导出、保留策略变更) |
| P1-4 | data-access 错误处理不一致 | `getAuditModuleOptions` / `getDataChangeTableOptions` 改为抛出错误 |
| P1-5 | 日期 aria-label 不区分 | 新增 i18n `filter.startDate` / `filter.endDate`,替换 `t("table.time")` |
| P1-6 | DialogDescription 重复 | 新增 `detail.description` 翻译键 |
| P1-7 | 骨架屏缺 aria-hidden | Skeleton 容器添加 `aria-hidden="true"` |
| P1-8 | window.confirm | 替换为 AlertDialog 组件(与其他模块一致) |
### P2 — 中长期迭代(功能增强)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P2-1 | diff 无可视化 | 引入 JSON diff 组件(左右对比 + 增删高亮) |
| P2-2 | 无失败登录告警 | 新增异常检测规则 + 告警通知 |
| P2-3 | 无 IP 地理位置 | 接入 IP 反查服务(如 MaxMind GeoLite2 |
| P2-4 | 趋势不可配置 | 概览页新增 7/30/90 天切换按钮 |
| P2-5 | 多校数据隔离 | 文档标注"系统级审计"设计决策;若需校级审计则新增 `school_id` 字段 |
| P2-6 | hook 无单测 | 为 `useLogPagination` 补充单测 |
| P2-7 | 导出无进度 | 大范围导出显示进度条 |
---
## 五、架构图同步说明
本次审计发现架构图以下遗漏/不一致,需在实现后同步更新:
### 004_architecture_impact_map.md2.15 节)
1. **修正 services 层状态**:第 1468 行"✅ P2-8 已修复"改为"P2-8 已定义接口P0-1 待接线"(实施后改回"已接线"
2. **修正 trackEvent 埋点描述**:保留策略操作的 event 名称从 `audit.exported` 改为 `audit.retention`
3. **新增 revalidatePath 说明**actions 节标注 saveAuditRetentionConfigAction / purgeAuditLogsAction 已添加 revalidatePath
### 005_architecture_data.jsonaudit 节点)
1. **services 节点**:标注 `AuditServiceProvider` 的 `usedBy`(实施后从空改为 overview page
2. **actions 节点**:更新 saveAuditRetentionConfigAction / purgeAuditLogsAction 的 trackEvent event 名称
3. **components 节点**:更新 `audit-error-boundary.tsx` 的 namespace 配置
---
## 六、重构方案设计
### a. P0-1 Service 抽象层接线方案
**决策**:保留 Service 接口(满足任务要求的 DI 架构),在 overview 页面接线。
由于 `AuditOverviewView` 是 RSCasync server component而 `AuditServiceProvider` 是 Client Component"use client"),无法直接在 RSC 中使用 Context Provider。因此采用**混合模式**
1. **RSC 页面**:仍由 page.tsx 调用 data-access 获取初始数据(保留 SSR 性能优势)
2. **客户端交互组件**`AuditRetentionSettings`):通过 `AuditServiceProvider` 注入 service组件内部用 `useAuditService()` 获取数据
3. **`adminAuditService`** 改为可被 Client Component 引用的轻量包装(仅委托给 Server Action不直接 import server-only 的 data-access
```typescript
// services/admin-audit-service.ts —— 改造为 Client-safe 实现
// 不 import "server-only",改为委托 Server Actions
import { getAuditRetentionConfigAction, saveAuditRetentionConfigAction, purgeAuditLogsAction } from "../actions"
export const adminAuditService: AuditService = {
// 保留策略相关:通过 Server Action 调用
getAuditRetentionConfig: async () => {
const res = await getAuditRetentionConfigAction()
if (!res.success || !res.data) throw new Error(res.message ?? "Failed")
return res.data
},
saveAuditRetentionConfig: async (config) => {
const res = await saveAuditRetentionConfigAction(config)
if (!res.success || !res.data) throw new Error(res.message ?? "Failed")
},
purgeExpiredAuditLogs: async (retentionDays) => {
const res = await purgeAuditLogsAction(retentionDays)
if (!res.success || !res.data) throw new Error(res.message ?? "Failed")
return res.data
},
// 查询类RSC 页面已通过 props 传入,客户端不需要
// ...其余方法可暂不实现或抛错
}
```
`AuditRetentionSettings` 改造:
```tsx
// 在组件内部使用 useAuditService() 而非直接调 Server Action
const service = useAuditService()
const res = await service.getAuditRetentionConfig()
```
页面注入:
```tsx
// overview/page.tsx
import { AuditServiceProvider } from "@/modules/audit/services/audit-service"
import { adminAuditService } from "@/modules/audit/services/admin-audit-service"
return (
<AuditServiceProvider service={adminAuditService}>
<AuditOverviewView stats={stats} trend={trend} distribution={distribution} />
</AuditServiceProvider>
)
```
### b. P0-2 mock-audit-service 类型标注修复
```typescript
// 修复前
getAuditLogs: async () =>
({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 0 }) as PaginatedResult<AuditLog>,
// 修复后 —— 为返回值添加显式类型标注,无需 as
getAuditLogs: async (): Promise<PaginatedResult<AuditLog>> => ({
items: [],
total: 0,
page: 1,
pageSize: 20,
totalPages: 0,
}),
```
### c. P0-3 revalidatePath
```typescript
import { revalidatePath } from "next/cache"
// saveAuditRetentionConfigAction 末尾
revalidatePath("/admin/audit-logs/overview")
return { success: true, data: config }
// purgeAuditLogsAction 末尾
revalidatePath("/admin/audit-logs/overview")
revalidatePath("/admin/audit-logs")
return { success: true, data: result }
```
### d. P1-1 trackEvent event 修正
```typescript
// 保留策略操作
await trackEvent({
event: "audit.retention", // 原: "audit.exported"
userId: session?.user?.id,
targetType: "audit_retention",
properties: { action: "save_config", ... },
})
```
### e. P1-2 ErrorBoundary namespace 修复
`audit.json` 新增:
```json
"error": {
"title": "加载失败",
"description": "数据加载时发生错误,请稍后重试。",
"retry": "重试",
"boundaryTitle": "审计数据加载失败",
"boundaryDescription": "审计数据区块加载时发生错误。"
}
```
`audit-error-boundary.tsx`
```tsx
<SectionErrorBoundary namespace="audit">
```
### f. i18n 翻译键补充
```json
"filter": {
"startDate": "开始日期",
"endDate": "结束日期"
},
"detail": {
"description": "查看日志的完整字段信息。"
}
```
### g. 最终检查
- [x] 该模块不存在对其他业务模块的直接 import仅依赖 shared/* 和 settings data-access for retention config
- [x] 没有使用 `any` 或硬编码角色字符串
- [x] 所有 actions 包含 `requirePermission` 调用7 个 Action 均有)
- [x] 文件行数未超过建议上限(最大 data-access.ts 387 行 < 800
- [x] 架构影响地图需同步更新(见第五节)