26.9(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务: - 安全审计全部修复(docs/security-audit-2026-09-05.md): bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、 全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、 管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、 会话默认 7 天、限流缓存故障降级、robots.txt 端点等 - 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固 - Redis 支持库号(FCB_REDIS_DB / redis://…/db URL) - 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、 提码 5 位起、chunk 32MiB 上限、admin 审计动作等) 验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
// 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()...)
|
||||
}
|
||||
Reference in New Issue
Block a user