// Package model 定义 GORM 数据模型与 Postgres 自动迁移。 // 字段对齐参考实现 apps/base/models.py,并新增审计日志表。 package model import ( "time" "gorm.io/gorm" ) // FileCodes 文件/文本分享记录(对齐参考 FileCodes)。 type FileCodes struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` Code string `gorm:"column:code;size:255;uniqueIndex;not null" json:"code"` // 取件码 Prefix string `gorm:"size:255;default:''" json:"prefix"` // 文件名前缀/文本分享标记 Suffix string `gorm:"size:255;default:''" json:"suffix"` // 文件名后缀(含扩展名) UUIDFileName *string `gorm:"size:255" json:"uuid_file_name"` // 存储侧 UUID 文件名 FilePath *string `gorm:"size:255" json:"file_path"` // 存储侧相对路径 Size int64 `gorm:"default:0" json:"size"` // 字节数;文本为字符数 Text *string `gorm:"type:text" json:"text"` // 文本分享内容 ExpiredAt *time.Time `json:"expired_at"` // 过期时间;永久分享为 NULL ExpiredCount int `gorm:"default:0" json:"expired_count"` // 剩余可取次数;<0 表示按时间过期 UsedCount int `gorm:"default:0" json:"used_count"` // 已取次数 CreatedAt time.Time `json:"created_at"` FileHash *string `gorm:"size:64" json:"file_hash"` // SHA256 IsChunked bool `gorm:"default:false" json:"is_chunked"` UploadID *string `gorm:"size:36" json:"upload_id"` // 分片上传会话 ID Engine string `gorm:"size:16;default:''" json:"engine"` // 归属存储引擎(v3:local|s3|webdav;空=历史数据按当前引擎取) } // TableName 表名。 func (FileCodes) TableName() string { return "file_codes" } // Expired 判断是否已过期(对齐参考语义:expired_count<0 按时间,否则按次数)。 func (f *FileCodes) Expired(now time.Time) bool { if f.ExpiredAt == nil { return false } if f.ExpiredCount < 0 { return f.ExpiredAt.Before(now) } return f.ExpiredCount <= 0 } // UploadChunk 分片上传记录(对齐参考 UploadChunk)。 type UploadChunk struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` UploadID string `gorm:"size:36;index:idx_upload_chunk,unique,priority:1;not null" json:"upload_id"` ChunkIndex int `gorm:"index:idx_upload_chunk,unique,priority:2;not null" json:"chunk_index"` ChunkHash string `gorm:"size:64;not null" json:"chunk_hash"` // 分片 SHA256 TotalChunks int `json:"total_chunks"` FileSize int64 `json:"file_size"` ChunkSize int `json:"chunk_size"` FileName string `gorm:"size:255" json:"file_name"` SavePath string `gorm:"size:512" json:"save_path"` CreatedAt time.Time `json:"created_at"` Completed bool `gorm:"default:false" json:"completed"` Engine string `gorm:"size:16;default:''" json:"engine"` // 归属存储引擎(v3:分片与会话记录当时引擎,合并走同一引擎) } // TableName 表名。 func (UploadChunk) TableName() string { return "upload_chunks" } // KeyValue 运行时配置键值(对齐参考 KeyValue)。value 存 JSON。 type KeyValue struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` Key string `gorm:"size:255;uniqueIndex;not null" json:"key"` Value *string `gorm:"type:text" json:"value"` // JSON 字符串 CreatedAt time.Time `json:"created_at"` } // TableName 表名。 func (KeyValue) TableName() string { return "key_values" } // PresignUploadSession 预签名直传会话(对齐参考 PresignUploadSession)。 type PresignUploadSession struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` UploadID string `gorm:"size:36;uniqueIndex;not null" json:"upload_id"` FileName string `gorm:"size:255" json:"file_name"` FileSize int64 `json:"file_size"` SavePath string `gorm:"size:512" json:"save_path"` Mode string `gorm:"size:10" json:"mode"` // direct=客户端直传 | proxy=服务器代理 ExpireValue int `json:"expire_value"` ExpireStyle string `gorm:"size:20;default:day" json:"expire_style"` CreatedAt time.Time `json:"created_at"` ExpiresAt time.Time `json:"expires_at"` Engine string `gorm:"size:16;default:''" json:"engine"` // 归属存储引擎(v3:直传/代理完成走同一引擎取回) } // TableName 表名。 func (PresignUploadSession) TableName() string { return "presign_upload_sessions" } // IsExpired 会话是否已过期。 func (p *PresignUploadSession) IsExpired(now time.Time) bool { return p.ExpiresAt.Before(now) } // StorageReservation 上传容量预留(尚未写入 file_codes 的占位)。 type StorageReservation struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` Token string `gorm:"size:64;uniqueIndex;not null" json:"token"` Size int64 `json:"size"` ExpiresAt time.Time `gorm:"index" json:"expires_at"` } // TableName 表名。 func (StorageReservation) TableName() string { return "storage_reservations" } // 审计结果常量。 const ( AuditResultSuccess = "success" // 操作成功 AuditResultDenied = "denied" // 被拒绝(限流/鉴权/策略) AuditResultFailed = "failed" // 执行失败(服务端/客户端错误) ) // AuditLog 上传/下载审计日志(需求 ③)。 type AuditLog struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` Action string `gorm:"size:32;index" json:"action"` // upload | download FileCode string `gorm:"size:64;index" json:"file_code"` // 取件码(上传时为生成的码) FileName string `gorm:"size:255" json:"file_name"` // 原始文件名/文本标记 SizeBytes int64 `json:"size_bytes"` // 文件总字节数 TransferredBytes int64 `json:"transferred_bytes"` // 本次实际传输字节数 IP string `gorm:"size:64;index" json:"ip"` UserAgent string `gorm:"size:512" json:"user_agent"` DeviceOS string `gorm:"size:64" json:"device_os"` // Windows/macOS/Android/iOS/Linux/Unknown DeviceBrowser string `gorm:"size:64" json:"device_browser"` // Chrome/Firefox/Safari/Edge/... DeviceType string `gorm:"size:32" json:"device_type"` // desktop/mobile/tablet/bot/other Actor string `gorm:"size:64" json:"actor"` // admin | guest Result string `gorm:"size:16;index" json:"result"` // success | denied | failed ErrorMsg string `gorm:"size:512" json:"error_msg"` DurationMs int64 `json:"duration_ms"` CreatedAt time.Time `gorm:"index" json:"created_at"` // 操作时间 } // TableName 表名。 func (AuditLog) TableName() string { return "audit_logs" } // AllModels 全部需要迁移的模型。 func AllModels() []any { return []any{ &FileCodes{}, &UploadChunk{}, &KeyValue{}, &PresignUploadSession{}, &StorageReservation{}, &AuditLog{}, } } // AutoMigrate 在 Postgres 上建表/补列;服务启动时调用。 func AutoMigrate(db *gorm.DB) error { return db.AutoMigrate(AllModels()...) }