CI 测试 / go vet + go test (push) Successful in 49s
- 全项目版本号统一:v3.x 迭代号(26.9/26.9/26.9/26.9 及裸 v2/v3)→ 26.9, 覆盖 Go 注释 / 文档 / openapi.yaml / README×4 / 前端源码(80+ 处) - v31_test.go 更名 custom_code_test.go;TestV2AccessorDefaults → TestKVAccessorDefaults - docs/api/00-overview.md 更新日志合并为单条 26.9 条目(修复错位拼接) - .goreleaser.yaml 头部注释与实际一致(Pro 2.18.1 / GITEA_TOKEN / semver tag 要求) - CI:release-image.yml → ci.yml,仅保留 vet+test 门禁; 镜像发布移交 GoReleaser Pro(原 build-push 的 tag 校验与 26.9 版本方案冲突,历史 9 次失败) - 前端重建:server/web/dist 与 web-embed 同步(docs 文案嵌入更新)
153 lines
7.4 KiB
Go
153 lines
7.4 KiB
Go
// 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"` // 归属存储引擎(26.9: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"` // 归属存储引擎(26.9:分片与会话记录当时引擎,合并走同一引擎)
|
||
}
|
||
|
||
// 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"` // 归属存储引擎(26.9:直传/代理完成走同一引擎取回)
|
||
}
|
||
|
||
// 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()...)
|
||
}
|