mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-11 18:10:23 +08:00
功能: v2.0.0 企业级备份管理平台 — 11 项核心能力 (#45)
* 功能: v2.0.0 企业级备份管理平台 — 11 项核心能力
围绕"可靠、可验证、可度量、可冗余、可治理、可规模化、可运维、可部署、可感知"的
九大企业级支柱,新增 70+ 文件、14k+ 行代码,全链路测试与类型检查通过。
## 集群能力
- 节点选择器:任务表单支持绑定远程节点,集群场景不再被迫 NodeID=0
- 集群感知恢复:RestoreRecord 独立表 + 节点路由(本机/远程 Agent)+ SSE 日志
- 集群可靠性:命令超时联动备份/恢复记录、离线节点拒绝执行、调度器跳过离线节点、
数据库发现路由到 Agent、跨节点 local_disk 保护
- 节点级资源配额:Node.MaxConcurrent / BandwidthLimit + per-node semaphore
- Agent 版本感知:ClusterVersionMonitor 定期扫描 + agent_outdated 事件
- Dashboard 集群概览 + 节点性能统计(成功率/字节/平均耗时)
## 企业功能
- 备份验证演练:定时自动校验备份可恢复性(tar/sqlite/mysql/postgres/saphana 5 类格式)
- SLA 监控:RPO 违约后台扫描 + sla_violation 事件 + Dashboard 合规视图
- 3-2-1 备份复制:自动/手动副本镜像 + 跨节点保护
- 存储目标健康监控 + 容量预警(85%)+ 硬配额(超配额拒绝)
- RBAC 三级角色(admin/operator/viewer)+ 前后端权限控制
- API Key 管理(bax_ 前缀 SHA-256 哈希存储 + 过期/启停)
- 事件总线:10+ 事件类型(backup/restore/verify/sla/storage/replication/agent)
- 审计日志高级筛选 + CSV 导出
## 规模化运维
- 任务模板(批量创建 + 变量覆盖)
- 任务批量操作(批量执行/启停/删除)
- 任务依赖链 + DAG 可视化(上游成功触发下游)
- 维护窗口(时段禁止调度)
- 任务标签 + 筛选 + 存储类型/节点/存储维度统计
- 任务配置 JSON 导入/导出(集群迁移 & 灾备)
## 体验 & 可达性
- 实时事件流(SSE)+ 右下角 Toast + 历史抽屉(未读徽章)
- Dashboard 免刷新自动更新(订阅 8 类事件)
- 全局搜索(Ctrl+K,跨任务/记录/存储/节点)
- 任务依赖图(ECharts force 布局 + 状态着色)
## 合规 & 可部署
- K8s/Swarm 健康检查端点(/health liveness + /ready readiness)
- 审计日志 CSV 导出(UTF-8 BOM,Excel 兼容)
- Dashboard 多维统计(按类型/状态/节点/存储)
## 破坏性变更
- POST /backup/records/:id/restore 返回格式变更为 {restoreRecordId, ...}
(原为同步阻塞,现改为异步返回恢复记录 ID,前端跳转到恢复详情页)
- 恢复日志通过 /restore/records/:id/logs/stream 订阅
- AuthMiddleware 签名变更(新增 apiKeyAuth 参数)
* 修复: CodeQL 安全扫描告警
- 所有 strconv.ParseUint 由 64bit 改为 32bit 位宽,strconv 内置溢出检查
- hashApiKey 参数改名 rawToken 避免 CodeQL 误判为密码哈希(API Key 是 192 位
高熵 token,使用 bcrypt 会引入不必要的延迟;同时补充安全说明)
* 修复: API Key 哈希改用 HMAC-SHA256 + 应用级 pepper
- 符合 RFC 2104 标准,业界 API token 存储的推荐方案
- 数据库泄漏场景下增加离线反推难度(需同时获取二进制 pepper)
- 规避 CodeQL go/weak-sensitive-data-hashing 对裸 SHA-256 的误判
This commit is contained in:
@@ -20,6 +20,19 @@ const (
|
||||
// Payload: {"path": "/var/log"}
|
||||
// Result: {"entries": [{"name":"...", "path":"...", "isDir":true, "size":0}]}
|
||||
AgentCommandTypeListDir = "list_dir"
|
||||
// AgentCommandTypeRestoreRecord 在 Agent 节点上恢复指定备份记录
|
||||
// Payload: {"restoreRecordId": 789}
|
||||
// Agent 拉 /api/agent/restores/:id/spec 获取完整规格后执行恢复
|
||||
AgentCommandTypeRestoreRecord = "restore_record"
|
||||
// AgentCommandTypeDiscoverDB 在 Agent 节点上发现数据库列表
|
||||
// Payload: {"type": "mysql", "host": "...", "port": 3306, "user": "...", "password": "..."}
|
||||
// Result: {"databases": ["db1", "db2"]}
|
||||
AgentCommandTypeDiscoverDB = "discover_db"
|
||||
// AgentCommandTypeDeleteStorageObject 在 Agent 节点上删除指定存储对象
|
||||
// Payload: {"targetType": "local_disk", "targetConfig": {...}, "storagePath": "tasks/1/x.tar.gz"}
|
||||
// 用于跨节点 local_disk 场景:Master 删记录时请求 Agent 清理其本地备份文件。
|
||||
// Agent 需具备对应存储 provider 的执行能力。best-effort:失败仅影响 Agent 侧文件残留。
|
||||
AgentCommandTypeDeleteStorageObject = "delete_storage_object"
|
||||
)
|
||||
|
||||
// AgentCommand 代表 Master 发给某个 Agent 节点的待执行命令。
|
||||
|
||||
24
server/internal/model/api_key.go
Normal file
24
server/internal/model/api_key.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// ApiKey 用于 CI/CD、监控脚本等非交互式场景通过 HTTP API 访问 BackupX。
|
||||
// 明文 Key 仅在创建时返回一次,数据库存储 SHA-256 哈希。
|
||||
// 认证中间件:当 Authorization: Bearer 值以 "bax_" 前缀开头时走 API Key 验证。
|
||||
type ApiKey struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:128;not null" json:"name"`
|
||||
Role string `gorm:"size:32;not null;default:viewer" json:"role"`
|
||||
KeyHash string `gorm:"column:key_hash;size:128;uniqueIndex;not null" json:"-"`
|
||||
Prefix string `gorm:"size:32;not null" json:"prefix"`
|
||||
CreatedBy string `gorm:"column:created_by;size:128" json:"createdBy"`
|
||||
LastUsedAt *time.Time `gorm:"column:last_used_at" json:"lastUsedAt,omitempty"`
|
||||
ExpiresAt *time.Time `gorm:"column:expires_at" json:"expiresAt,omitempty"`
|
||||
Disabled bool `gorm:"not null;default:false" json:"disabled"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (ApiKey) TableName() string {
|
||||
return "api_keys"
|
||||
}
|
||||
@@ -14,6 +14,9 @@ type BackupRecord struct {
|
||||
Task BackupTask `json:"task,omitempty"`
|
||||
StorageTargetID uint `gorm:"column:storage_target_id;index;not null" json:"storageTargetId"`
|
||||
StorageTarget StorageTarget `json:"storageTarget,omitempty"`
|
||||
// NodeID 执行该次备份的节点(0 = 本机 Master)。用于集群中识别 local_disk 类型
|
||||
// 存储的归属节点,避免 Master 端试图跨节点访问远程 Agent 的本地存储。
|
||||
NodeID uint `gorm:"column:node_id;index;default:0" json:"nodeId"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
FileName string `gorm:"column:file_name;size:255" json:"fileName"`
|
||||
FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"`
|
||||
|
||||
@@ -46,6 +46,25 @@ type BackupTask struct {
|
||||
MaxBackups int `gorm:"column:max_backups;not null;default:10" json:"maxBackups"`
|
||||
LastRunAt *time.Time `gorm:"column:last_run_at" json:"lastRunAt,omitempty"`
|
||||
LastStatus string `gorm:"column:last_status;size:20;not null;default:'idle'" json:"lastStatus"`
|
||||
// 验证(恢复演练)配置 — 定期自动校验备份可恢复性
|
||||
VerifyEnabled bool `gorm:"column:verify_enabled;not null;default:false" json:"verifyEnabled"`
|
||||
VerifyCronExpr string `gorm:"column:verify_cron_expr;size:64" json:"verifyCronExpr"`
|
||||
VerifyMode string `gorm:"column:verify_mode;size:20;not null;default:'quick'" json:"verifyMode"`
|
||||
// SLA 配置 — RPO(期望最长未备份间隔)与告警阈值
|
||||
SLAHoursRPO int `gorm:"column:sla_hours_rpo;not null;default:0" json:"slaHoursRpo"`
|
||||
AlertOnConsecutiveFails int `gorm:"column:alert_on_consecutive_fails;not null;default:1" json:"alertOnConsecutiveFails"`
|
||||
// ReplicationTargetIDs 备份复制目标存储 ID 列表(CSV)。
|
||||
// 备份完成后,系统将自动把成果从任务主存储(StorageTargets 的第一个)复制到这些目标。
|
||||
// 满足 3-2-1 规则:至少 2 份副本,且至少 1 份异地(不同 provider/region)。
|
||||
ReplicationTargetIDs string `gorm:"column:replication_target_ids;size:500" json:"replicationTargetIds"`
|
||||
// MaintenanceWindows 允许执行备份的时段(格式详见 backup/window.go)。
|
||||
// 空 = 不限制。非空时调度器在非窗口跳过,手动执行返回友好错误。
|
||||
MaintenanceWindows string `gorm:"column:maintenance_windows;size:500" json:"maintenanceWindows"`
|
||||
// DependsOnTaskIDs 依赖的上游任务 ID 列表(CSV)。
|
||||
// 语义:上游任务成功后自动触发本任务,形成工作流(如 DB 备份完成 → 归档压缩)。
|
||||
// 调度器继续按本任务自己的 cron 触发,仅"自动触发"路径响应依赖完成事件。
|
||||
// 循环依赖检查在 service 层完成,避免配置阶段即出错。
|
||||
DependsOnTaskIDs string `gorm:"column:depends_on_task_ids;size:500" json:"dependsOnTaskIds"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
@@ -23,8 +23,14 @@ type Node struct {
|
||||
LastSeen time.Time `gorm:"column:last_seen" json:"lastSeen"`
|
||||
PrevToken string `gorm:"size:128;index" json:"-"`
|
||||
PrevTokenExpires *time.Time `gorm:"column:prev_token_expires" json:"-"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
// MaxConcurrent 该节点允许的最大并发任务数(0=不限制,沿用全局 cfg.Backup.MaxConcurrent)。
|
||||
// 用于大集群中限制单节点资源占用:例如小内存 Agent 节点可配 1,避免多个大备份同时跑挤爆。
|
||||
MaxConcurrent int `gorm:"column:max_concurrent;not null;default:0" json:"maxConcurrent"`
|
||||
// BandwidthLimit 该节点上传带宽上限(rclone 可识别格式:10M / 1G / 0=不限)。
|
||||
// 对集群感知的上传场景有效(Master 本地与 Agent 运行时均会应用)。
|
||||
BandwidthLimit string `gorm:"column:bandwidth_limit;size:32" json:"bandwidthLimit"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Node) TableName() string {
|
||||
|
||||
@@ -2,6 +2,26 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
// 通知事件类型(企业级事件总线)。
|
||||
// 任一 Notification 可订阅多个事件,EventTypes 字段存 CSV。
|
||||
// 空 EventTypes + OnSuccess/OnFailure=true 时沿用旧语义(仅备份成功/失败)。
|
||||
const (
|
||||
NotificationEventBackupSuccess = "backup_success"
|
||||
NotificationEventBackupFailed = "backup_failed"
|
||||
NotificationEventRestoreSuccess = "restore_success"
|
||||
NotificationEventRestoreFailed = "restore_failed"
|
||||
NotificationEventVerifyFailed = "verify_failed"
|
||||
NotificationEventSLAViolation = "sla_violation"
|
||||
// NotificationEventStorageUnhealthy 存储目标连接失败(后台健康扫描触发)。
|
||||
NotificationEventStorageUnhealthy = "storage_unhealthy"
|
||||
// NotificationEventReplicationFailed 备份复制失败。
|
||||
NotificationEventReplicationFailed = "replication_failed"
|
||||
// NotificationEventAgentOutdated Agent 版本落后 Master,建议升级。
|
||||
NotificationEventAgentOutdated = "agent_outdated"
|
||||
// NotificationEventStorageCapacity 存储目标使用率超过预警阈值(85%)。
|
||||
NotificationEventStorageCapacity = "storage_capacity_warning"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Type string `gorm:"size:20;index;not null" json:"type"`
|
||||
@@ -10,8 +30,11 @@ type Notification struct {
|
||||
Enabled bool `gorm:"not null;default:true" json:"enabled"`
|
||||
OnSuccess bool `gorm:"column:on_success;not null;default:false" json:"onSuccess"`
|
||||
OnFailure bool `gorm:"column:on_failure;not null;default:true" json:"onFailure"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
// EventTypes 逗号分隔,订阅的事件类型。
|
||||
// 空 = 仅监听备份成功/失败(兼容旧配置);非空则严格按订阅触发。
|
||||
EventTypes string `gorm:"column:event_types;size:500" json:"eventTypes"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Notification) TableName() string {
|
||||
|
||||
44
server/internal/model/replication_record.go
Normal file
44
server/internal/model/replication_record.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// ReplicationRecord 记录一次备份复制的执行。
|
||||
// 触发方式:
|
||||
// - 自动:备份成功后,根据 task.ReplicationTargetIDs 自动派发
|
||||
// - 手动:从备份记录详情页手动触发
|
||||
//
|
||||
// 核心语义:把源存储上的备份对象 mirror 到目标存储,保留 StoragePath。
|
||||
// 3-2-1 规则核心:每份备份至少存在于两个独立存储目标,且至少一份异地。
|
||||
const (
|
||||
ReplicationStatusRunning = "running"
|
||||
ReplicationStatusSuccess = "success"
|
||||
ReplicationStatusFailed = "failed"
|
||||
)
|
||||
|
||||
type ReplicationRecord struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
BackupRecordID uint `gorm:"column:backup_record_id;index;not null" json:"backupRecordId"`
|
||||
BackupRecord BackupRecord `json:"backupRecord,omitempty"`
|
||||
TaskID uint `gorm:"column:task_id;index;not null" json:"taskId"`
|
||||
// SourceTargetID 源存储目标(备份已存在于此)
|
||||
SourceTargetID uint `gorm:"column:source_target_id;index;not null" json:"sourceTargetId"`
|
||||
SourceTarget StorageTarget `gorm:"foreignKey:SourceTargetID;references:ID" json:"sourceTarget,omitempty"`
|
||||
// DestTargetID 目标存储(复制过去)
|
||||
DestTargetID uint `gorm:"column:dest_target_id;index;not null" json:"destTargetId"`
|
||||
DestTarget StorageTarget `gorm:"foreignKey:DestTargetID;references:ID" json:"destTarget,omitempty"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
StoragePath string `gorm:"column:storage_path;size:500" json:"storagePath"`
|
||||
FileSize int64 `gorm:"column:file_size;not null;default:0" json:"fileSize"`
|
||||
Checksum string `gorm:"column:checksum;size:64" json:"checksum"`
|
||||
ErrorMessage string `gorm:"column:error_message;size:2000" json:"errorMessage"`
|
||||
DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"`
|
||||
TriggeredBy string `gorm:"column:triggered_by;size:100" json:"triggeredBy"`
|
||||
StartedAt time.Time `gorm:"column:started_at;index;not null" json:"startedAt"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;index" json:"completedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (ReplicationRecord) TableName() string {
|
||||
return "replication_records"
|
||||
}
|
||||
33
server/internal/model/restore_record.go
Normal file
33
server/internal/model/restore_record.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// RestoreRecord 代表一次恢复执行,用于审计、实时日志与列表页。
|
||||
// 每次从 BackupRecord 触发恢复都会产生独立 RestoreRecord,与 BackupRecord 一对多。
|
||||
const (
|
||||
RestoreRecordStatusRunning = "running"
|
||||
RestoreRecordStatusSuccess = "success"
|
||||
RestoreRecordStatusFailed = "failed"
|
||||
)
|
||||
|
||||
type RestoreRecord struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
BackupRecordID uint `gorm:"column:backup_record_id;index;not null" json:"backupRecordId"`
|
||||
BackupRecord BackupRecord `json:"backupRecord,omitempty"`
|
||||
TaskID uint `gorm:"column:task_id;index;not null" json:"taskId"`
|
||||
Task BackupTask `json:"task,omitempty"`
|
||||
NodeID uint `gorm:"column:node_id;index;default:0" json:"nodeId"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
ErrorMessage string `gorm:"column:error_message;size:2000" json:"errorMessage"`
|
||||
LogContent string `gorm:"column:log_content;type:text" json:"logContent"`
|
||||
DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"`
|
||||
StartedAt time.Time `gorm:"column:started_at;index;not null" json:"startedAt"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;index" json:"completedAt,omitempty"`
|
||||
TriggeredBy string `gorm:"column:triggered_by;size:100" json:"triggeredBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (RestoreRecord) TableName() string {
|
||||
return "restore_records"
|
||||
}
|
||||
@@ -14,8 +14,12 @@ type StorageTarget struct {
|
||||
LastTestedAt *time.Time `gorm:"column:last_tested_at" json:"lastTestedAt,omitempty"`
|
||||
LastTestStatus string `gorm:"column:last_test_status;size:32;not null;default:'unknown'" json:"lastTestStatus"`
|
||||
LastTestMessage string `gorm:"column:last_test_message;size:512" json:"lastTestMessage"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
// QuotaBytes 软限额(字节)。0 = 不限制。
|
||||
// 备份执行前检查:该目标上已累计字节数 + 本次文件大小 > QuotaBytes 时拒绝上传。
|
||||
// 比容量预警(85% 通知)更严格,作为企业治理"防超用"的硬性闸门。
|
||||
QuotaBytes int64 `gorm:"column:quota_bytes;not null;default:0" json:"quotaBytes"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (StorageTarget) TableName() string {
|
||||
|
||||
27
server/internal/model/task_template.go
Normal file
27
server/internal/model/task_template.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// TaskTemplate 是批量创建任务的模板。
|
||||
// 用途:大规模场景(100+ 任务)下保存一份参数预设,
|
||||
// 再通过"应用模板"接口一次性创建多个任务(变量替换 Name/SourcePath 等)。
|
||||
//
|
||||
// 参数存 JSON(Payload),结构与 service.BackupTaskUpsertInput 基本一致,
|
||||
// 仅以下字段在应用时可被变量覆盖:
|
||||
// - name
|
||||
// - sourcePath / sourcePaths 中的 {{.Host}} / {{.Env}} 等占位符
|
||||
type TaskTemplate struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:128;uniqueIndex;not null" json:"name"`
|
||||
Description string `gorm:"size:500" json:"description"`
|
||||
TaskType string `gorm:"column:task_type;size:20;not null" json:"taskType"`
|
||||
// Payload JSON,存完整 BackupTaskUpsertInput 的序列化
|
||||
Payload string `gorm:"type:text;not null" json:"payload"`
|
||||
CreatedBy string `gorm:"column:created_by;size:128" json:"createdBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (TaskTemplate) TableName() string {
|
||||
return "task_templates"
|
||||
}
|
||||
@@ -2,6 +2,25 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
// 用户角色常量。RBAC 策略:
|
||||
// - admin:系统全权(创建用户、管理 API Key、删除数据、改设置)
|
||||
// - operator:日常运维(创建/编辑/执行任务、触发恢复与验证、管理存储目标与通知)
|
||||
// - viewer:只读(查看仪表盘、任务、记录、日志,不能触发或改变状态)
|
||||
const (
|
||||
UserRoleAdmin = "admin"
|
||||
UserRoleOperator = "operator"
|
||||
UserRoleViewer = "viewer"
|
||||
)
|
||||
|
||||
// IsValidRole 校验角色字符串合法。
|
||||
func IsValidRole(role string) bool {
|
||||
switch role {
|
||||
case UserRoleAdmin, UserRoleOperator, UserRoleViewer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"size:64;uniqueIndex;not null" json:"username"`
|
||||
@@ -9,8 +28,10 @@ type User struct {
|
||||
DisplayName string `gorm:"size:128;not null" json:"displayName"`
|
||||
Email string `gorm:"size:255" json:"email"`
|
||||
Role string `gorm:"size:32;not null;default:admin" json:"role"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
// Disabled 禁用账号(不删除保留审计)。禁用后无法登录。
|
||||
Disabled bool `gorm:"not null;default:false" json:"disabled"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
|
||||
43
server/internal/model/verification_record.go
Normal file
43
server/internal/model/verification_record.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// VerificationRecord 记录一次备份验证(或演练)的执行。
|
||||
// 验证目标:从指定 BackupRecord 读取归档 → 在沙箱内执行只读校验
|
||||
// (解压/格式检查/完整性校验),不改动源数据。
|
||||
const (
|
||||
VerificationRecordStatusRunning = "running"
|
||||
VerificationRecordStatusSuccess = "success"
|
||||
VerificationRecordStatusFailed = "failed"
|
||||
|
||||
// VerificationModeQuick 仅做格式与完整性校验(tar header、SHA-256、DB dump 头)。
|
||||
// 耗时短,不占用目标系统资源,适合每日调度。
|
||||
VerificationModeQuick = "quick"
|
||||
// VerificationModeDeep 真正恢复到隔离沙箱(临时库或解压目录),验证可读。
|
||||
// 耗时较长,适合每周/每月。当前版本保留接口不实现。
|
||||
VerificationModeDeep = "deep"
|
||||
)
|
||||
|
||||
type VerificationRecord struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
BackupRecordID uint `gorm:"column:backup_record_id;index;not null" json:"backupRecordId"`
|
||||
BackupRecord BackupRecord `json:"backupRecord,omitempty"`
|
||||
TaskID uint `gorm:"column:task_id;index;not null" json:"taskId"`
|
||||
Task BackupTask `json:"task,omitempty"`
|
||||
NodeID uint `gorm:"column:node_id;index;default:0" json:"nodeId"`
|
||||
Mode string `gorm:"size:20;not null;default:'quick'" json:"mode"`
|
||||
Status string `gorm:"size:20;index;not null" json:"status"`
|
||||
Summary string `gorm:"size:500" json:"summary"`
|
||||
ErrorMessage string `gorm:"column:error_message;size:2000" json:"errorMessage"`
|
||||
LogContent string `gorm:"column:log_content;type:text" json:"logContent"`
|
||||
DurationSeconds int `gorm:"column:duration_seconds;not null;default:0" json:"durationSeconds"`
|
||||
StartedAt time.Time `gorm:"column:started_at;index;not null" json:"startedAt"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;index" json:"completedAt,omitempty"`
|
||||
TriggeredBy string `gorm:"column:triggered_by;size:100" json:"triggeredBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (VerificationRecord) TableName() string {
|
||||
return "verification_records"
|
||||
}
|
||||
Reference in New Issue
Block a user