feat(db): A5 审计表分区、append-only 与种子

audit_logs 按月 RANGE 分区、复合主键与 6 组索引;ensure_partitions 幂等建分区;UPDATE/DELETE/TRUNCATE 触发器与 lingxi_app 最小权限;audit_export_jobs;种子新增 3 项配置与 6 条演示审计。
This commit is contained in:
Stev_Wang committed 2026-11-04 16:26:20 +08:00
1 parent 408dc5e81b
commit 6215f204ce
6 files changed
+441 -4

No files matched your search

+76
View File
@@ -442,3 +442,79 @@ model DictItem {
@@index([typeId, parentCode])
@@map("dict_items")
}
// ---------------------------------------------------------------------------
// A5 审计与日志(platform 模块):append-only + 月分区 + 异步导出
// ---------------------------------------------------------------------------
/// 审计日志(A5-BE-01 / 工程规范 08):append-only,按月 RANGE 分区。
///
/// - 复合主键 `(id, occurred_at)`:分区键必须包含在主键 / 唯一约束中;
/// - 运行时按 id 查询请用 `findFirst`(单传 id 无法定位唯一行);
/// - `action_code` / `category` 的英文 code 存表,中文名与分类映射唯一来源是
/// `@lingxi/shared` 的 `AUDIT_ACTION_CATALOG`(避免字典漂移);
/// - 分区、索引、append-only 触发器与 `lingxi_app` 授权由迁移 SQL 手写(Prisma 不支持分区 DDL);
/// - 分区由 `audit_logs_ensure_partitions()` 保证,应用启动与每日定时调用。
model AuditLog {
id String @db.Char(26)
occurredAt DateTime @default(now()) @map("occurred_at") @db.Timestamptz(3)
actorId String? @map("actor_id") @db.Char(26)
actorName String? @map("actor_name") @db.VarChar(64)
actorAccount String? @map("actor_account") @db.VarChar(64)
actionCode String @map("action_code") @db.VarChar(64)
category String @db.VarChar(32)
targetType String? @map("target_type") @db.VarChar(64)
targetId String? @map("target_id") @db.VarChar(128)
targetName String? @map("target_name") @db.VarChar(160)
customerId String? @map("customer_id") @db.Char(26)
customerName String? @map("customer_name") @db.VarChar(128)
result String @db.VarChar(16)
httpMethod String? @map("http_method") @db.VarChar(8)
httpPath String? @map("http_path") @db.VarChar(255)
traceId String? @map("trace_id") @db.VarChar(64)
ip String? @db.VarChar(64)
userAgent String? @map("user_agent") @db.VarChar(255)
beforeJson Json? @map("before_json") @db.JsonB
afterJson Json? @map("after_json") @db.JsonB
changesJson Json? @map("changes_json") @db.JsonB
reason String? @db.VarChar(255)
errorCode String? @map("error_code") @db.VarChar(32)
errorMessage String? @map("error_message") @db.VarChar(500)
durationMs Int? @map("duration_ms")
@@id([id, occurredAt])
@@index([occurredAt(sort: Desc), id(sort: Desc)])
@@index([actorId, occurredAt(sort: Desc)])
@@index([customerId, occurredAt(sort: Desc)])
@@index([actionCode, occurredAt(sort: Desc)])
@@index([targetType, targetId])
@@index([result, occurredAt(sort: Desc)])
@@map("audit_logs")
}
/// 审计异步导出任务(A5-BE-03):普通表(可更新进度),文件落本地临时目录,
/// 通过短时效 HMAC 签名链接下载;A6 任务协议就绪后替换 `setImmediate` 执行方式。
model AuditExportJob {
id String @id @db.Char(26)
status String @default("queued") @db.VarChar(16)
format String @default("csv") @db.VarChar(16)
filtersJson Json @default("{}") @map("filters_json") @db.JsonB
requestedBy String @map("requested_by") @db.Char(26)
requestedAt DateTime @default(now()) @map("requested_at") @db.Timestamptz(3)
startedAt DateTime? @map("started_at") @db.Timestamptz(3)
finishedAt DateTime? @map("finished_at") @db.Timestamptz(3)
totalRows Int? @map("total_rows")
rowCount Int? @map("row_count")
processedRows Int? @map("processed_rows")
truncated Boolean @default(false)
filePath String? @map("file_path") @db.VarChar(500)
fileSize Int? @map("file_size")
errorMessage String? @map("error_message") @db.VarChar(500)
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
downloadCount Int @default(0) @map("download_count")
lastDownloadedAt DateTime? @map("last_downloaded_at") @db.Timestamptz(3)
@@index([requestedBy, requestedAt(sort: Desc)])
@@index([status])
@@map("audit_export_jobs")
}