# 审计系统 数据模型

> **版本**: v1.0  
> **状态**: ✅ Approved  
> **最后更新**: 2026-05-08  
> **Schema**: `platform_audit`  
> **Prisma 文件**: [`backend/prisma/schema/platform_audit.prisma`](../../../backend/prisma/schema/platform_audit.prisma)

---

## 1. 模型总览

`platform_audit` Schema 包含 4 张主表 + 7 个枚举类型：

| 表 | Prisma 模型 | 用途 |
|---|---|---|
| `audit_log` | `AuditLog` | 主审计日志表（28 字段，5W1H + 合规 + 哈希链） |
| `audit_database_change_log` | `AuditDatabaseChangeLog` | 数据库行级变更明细（关联 audit_log） |
| `audit_sensitive_operation_log` | `AuditSensitiveOperationLog` | 敏感操作扩展明细（MFA/审批） |
| `audit_integrity_check_log` | `AuditIntegrityCheckLog` | 完整性校验任务执行记录 |

**FK 关系**:

```
audit_log.user_id          → platform_iam.users.id           (NULL 允许：anonymous/system)
audit_database_change_log  → audit_log.id  (Cascade)
audit_sensitive_operation_log → audit_log.id  (Cascade)
audit_integrity_check_log  无 FK（独立审计任务记录）
```

**触发器**: 当前 schema **无触发器**。README 提及"PostgreSQL 触发器禁止修改/删除"为目标态，实际防篡改通过 SHA-256 哈希链 + 应用层 + DB 约束（`current_hash` 唯一）实现。

---

## 2. AuditLog（主表）

### 字段分组

| 分组 | 字段 | 说明 |
|---|---|---|
| **主键** | `id` (uuid) | |
| **多租户** | `region` (string, 默认 `cn`) | 写入时**强制小写归一**（拦截器近因修复，2026-05）；查询走大小写不敏感（`mode: 'insensitive'`）兼容历史 `'CN'` 大写记录；**但哈希链按精确 `(region, tenantId)` 隔离串链**——`getLatestLog` 与 seed 取 `latest` 必须按精确值过滤，不可用 ILIKE，否则跨域记录会污染同链 |
| | `tenantId` (string, 默认 `default`) | |
| **5W1H** | `who` (string) | 操作人用户名/system/anonymous |
| | `what` (string) | 操作描述（HTTP method + path 或业务动作） |
| | `when` (timestamptz, 默认 now) | |
| | `where` (string) | 来源（IP / API 路径 / 系统） |
| | `why` (string?) | 业务原因（可空） |
| | `how` (string) | 来源方式（HTTP_API / CI/CD pipeline / Schedule 等） |
| **操作详情** | `module` (string) | 业务模块名（注意：当前混用大小写，如 `Auth` vs `iam`，建议规范化） |
| | `action` (`AuditAction` enum, 35 值) | |
| | `entityType` (string) | |
| | `entityId` (uuid) | |
| **变更内容** | `oldValue` (Json?) | |
| | `newValue` (Json?) | 敏感字段在写入前 redact（如 `password: '***REDACTED***'`） |
| | `changes` (Json?) | |
| **上下文** | `userId` (uuid?) | NULL = 未认证流程（如 LOGIN_FAILED） |
| | `sessionId` (uuid) | |
| | `traceId` (uuid) | |
| | `requestId` (uuid) | |
| **环境** | `ipAddress` (string) | |
| | `userAgent` (string) | |
| | `deviceId` (uuid?) | |
| | `geoLocation` (string?) | |
| **业务扩展** | `businessType` (string?) | 业务类型扩展点（当前响应未返回） |
| | `businessKey` (string?) | 业务主键扩展点（当前响应未返回） |
| **状态** | `status` (`AuditStatus`, 默认 SUCCESS) | SUCCESS / FAILED / PARTIAL / PENDING |
| | `errorMessage` (string?) | |
| | `duration` (int?) | 操作耗时 ms |
| **SOX 合规** | `isFinancial` (bool, 默认 false) | |
| | `isSensitive` (bool, 默认 false) | |
| | `riskLevel` (`RiskLevel`, 默认 MEDIUM) | HIGH / MEDIUM / LOW |
| | `complianceLevel` (`ComplianceLevel`, 默认 MEDIUM) | HIGH=7年 / MEDIUM=5年 / LOW=3年 |
| | `retentionYears` (int, 默认 5) | 由 complianceLevel 派生 |
| **完整性** | `previousHash` (string?) | 创世记录为 NULL，后续指向上一条 currentHash |
| | `currentHash` (string, **UNIQUE**) | SHA-256（排序 JSON 序列化后哈希） |
| | `signature` (string?) | HMAC 签名（可选） |
| **元数据** | `createdAt` (timestamptz) | |
| | `archivedAt` (timestamptz?) | 归档时间 |

### 哈希链规则

1. 创世记录：`previousHash = NULL`，`currentHash = SHA256(sortedJSON(record without {currentHash, signature, createdAt, archivedAt}))`
2. 后续记录：`previousHash = 上一条 currentHash`（按 `createdAt, id` 排序），`currentHash = SHA256(...)`
3. 校验：扫描时按 `(createdAt, id)` 排序，相邻 `previousHash` 必须等于上一条 `currentHash`，且每条 `currentHash` 必须能由当前字段重算得出
4. 失败类型（`failures[].type`）：
   - `HASH_CHAIN_BROKEN` —— previousHash 与上一条 currentHash 不匹配
   - `HASH_MISMATCH` —— currentHash 与重算结果不匹配
   - `INVALID_GENESIS` —— 非首条但 previousHash 为 NULL
   - `SIGNATURE_INVALID` —— HMAC 签名验证失败

### 索引（多租户隔离查询）

```
(region, tenantId, when)
(tenantId, userId, when)
(tenantId, entityType, entityId, when)
(tenantId, module, action, when)
(tenantId, isFinancial, when)
(tenantId, isSensitive, when)
(traceId)
(currentHash)
(sessionId)
```

---

## 3. AuditDatabaseChangeLog（DB 行级变更）

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | uuid | |
| `tableName` | string | 被改动的表名 |
| `operation` | `DbOperation` enum | INSERT / UPDATE / DELETE |
| `recordId` | uuid | 被改动的行 ID |
| `oldData` | Json? | |
| `newData` | Json? | |
| `changedFields` | string[] | UPDATE 命中的字段名集合 |
| `userId` | uuid | |
| `auditLogId` | uuid → AuditLog (Cascade) | |
| `createdAt` | timestamptz | |

索引：`(tableName, recordId)`、`(userId, createdAt)`、`(auditLogId)`

---

## 4. AuditSensitiveOperationLog（敏感操作扩展）

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | uuid | |
| `operationType` | string | 操作类型描述 |
| `description` | string | |
| `requiresApproval` | bool, 默认 false | |
| `approvedBy` | string? | |
| `approvalTime` | timestamptz? | |
| `riskLevel` | `RiskLevel` | |
| `mfaVerified` | bool, 默认 false | |
| `userId` | uuid | |
| `auditLogId` | uuid → AuditLog (Cascade) | |
| `createdAt` | timestamptz | |

索引：`(userId, createdAt)`、`(riskLevel, createdAt)`、`(auditLogId)`

---

## 5. AuditIntegrityCheckLog（完整性检查任务）

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | uuid | |
| `region` | string, 默认 `cn` | |
| `tenantId` | string, 默认 `default` | |
| `checkType` | `IntegrityCheckType` | HASH_CHAIN / SIGNATURE / COUNT / FULL |
| `startTime` | timestamptz | |
| `endTime` | timestamptz | |
| `recordCount` | int | 校验总记录数 |
| `passCount` | int | 通过数 |
| `failCount` | int | 失败数 |
| `failures` | Json? | failures 数组结构（**近因修复 2026-05**：补齐 `type` 收敛枚举与 `expectedHash`/`actualHash` 字段） |
| `status` | `CheckStatus` | SUCCESS / FAILED / PARTIAL |
| `errorMessage` | string? | |
| `createdAt` | timestamptz | |

索引：`(region, tenantId, createdAt)`、`(checkType, createdAt)`、`(status, createdAt)`

---

## 6. 枚举完整列表

### AuditAction（35 值）

CRUD: `CREATE` / `READ` / `UPDATE` / `DELETE` / `BULK_UPDATE` / `BULK_DELETE`  
审批: `APPROVE` / `REJECT` / `RETURN` / `FORWARD` / `WITHDRAW`  
认证: `LOGIN` / `LOGOUT` / `LOGIN_FAILED` / `PASSWORD_CHANGE` / `PASSWORD_RESET` / `EMAIL_CHANGE`  
权限: `PERMISSION_CHANGE` / `ROLE_CHANGE`  
系统: `CONFIG_CHANGE` / `BACKUP` / `RESTORE` / `EXPORT` / `IMPORT` / `DEPLOY` / `ROLLBACK`  
财务: `PAYMENT` / `REFUND` / `INVOICE_GENERATE` / `FINANCIAL_CLOSE` / `BUDGET_APPROVE`  
文件: `DOWNLOAD` / `UPLOAD` / `SHARE` / `ARCHIVE`

### AuditStatus

`SUCCESS` / `FAILED` / `PARTIAL` / `PENDING`

### DbOperation

`INSERT` / `UPDATE` / `DELETE`

### ComplianceLevel / RiskLevel

`HIGH` / `MEDIUM` / `LOW`

> 保留期限映射：HIGH → 7 年，MEDIUM → 5 年，LOW → 3 年。

### IntegrityCheckType

`HASH_CHAIN` / `SIGNATURE` / `COUNT` / `FULL`

### CheckStatus

`SUCCESS` / `FAILED` / `PARTIAL`

---

## 7. 数据约束与不变量

1. **`currentHash` 全局唯一** —— 数据库唯一约束 + 业务层重算校验。
2. **不可变（写入后只读）** —— 由应用层 + interceptor 不暴露 PUT/PATCH/DELETE 端点保证；DB 层暂无触发器（已于 2026-05-08 同步把 README「PostgreSQL 触发器禁止修改/删除」改为「应用层 + 哈希链 tamper-evident，DB 触发器属规划项」，避免误导）。
3. **多租户字段强制非空** —— `region` / `tenantId` 默认值在拦截器中显式设置；`region` **写入前小写归一**。
4. **保留期由 complianceLevel 决定** —— 不允许直接覆盖 `retentionYears`。
5. **创世记录 previousHash 必须为 NULL** —— 校验时通过 `INVALID_GENESIS` 失败类型识别异常。

---

## 8. 跨模块约束

- `audit_log.user_id` → `platform_iam.users.id`：删除用户时不允许级联删除审计日志（应用层禁止），通常通过停用账号实现。
- 业务模块通过 `@Auditable` 装饰器 + interceptor 写入，不直接写表。

---

## 9. 与文档/代码的差距记录（2026-05-08 体检发现）

| 项 | 现状 | 差距 |
|---|---|---|
| README 触发器声明 | DB 层无触发器 | 与 README "PostgreSQL 触发器禁止修改/删除" 描述不一致 |
| `AuditLogItem` 接口 | 列表响应缺 `userId` / `ipAddress` / `userAgent` | 前端 interface 与实际 shape 不符（前端 shape 仅列出可在列表展示的字段） |
| `AuditLogDetail` 接口 | 详情响应缺 `userId` / `businessType` / `businessKey` / `signature` | businessType/Key 字段未实现；userId 通过 `user.id` 间接可得 |
| `IntegrityCheckScope` | 后端 DTO 仅 `ALL` / `FINANCIAL_ONLY` | 前端枚举有 4 值，传 `SENSITIVE_ONLY`/`HIGH_RISK_ONLY` 会 400 |
| `module` 字段大小写 | 数据库内混用 `Auth` 与 `iam` 等不同 case | 建议拦截器统一首字母大小写策略，与 region 同步处理 |
| `format` 字段 | 后端 DTO 接受 `csv/excel/json`，前端类型仅 `csv/json` | 前端缺 `excel` 选项（次要） |

修复优先级：scope 枚举 > 接口字段对齐 > module 大小写 > 文档触发器表述。