package repository import ( "time" "gorm.io/gorm" "filestoragesystem/internal/model" ) // FileListFilter 文件列表过滤条件 type FileListFilter struct { UserID uint ProjectID uint Filename string // 模糊匹配 Prefix string // 虚拟目录前缀 Trashed bool // 查询回收站 Visibility int8 // 0为不过滤 Page int PageSize int } // FileRepo 文件数据访问 type FileRepo struct{ DB *gorm.DB } func (r *FileRepo) Create(f *model.File) error { return r.DB.Create(f).Error } func (r *FileRepo) FindByID(id uint) (*model.File, error) { var f model.File err := r.DB.First(&f, id).Error return &f, err } // List 分页查询文件 func (r *FileRepo) List(filter FileListFilter) ([]model.File, int64, error) { q := r.DB.Model(&model.File{}) if filter.UserID > 0 { q = q.Where("user_id = ?", filter.UserID) } if filter.ProjectID > 0 { q = q.Where("project_id = ?", filter.ProjectID) } if filter.Filename != "" { q = q.Where("filename LIKE ?", "%"+filter.Filename+"%") } if filter.Prefix != "" { q = q.Where("(filename = ? OR filename LIKE ?)", filter.Prefix, filter.Prefix+"/%") } if filter.Trashed { q = q.Where("status = 0") } else { q = q.Where("status = 1") } if filter.Visibility > 0 { q = q.Where("visibility = ?", filter.Visibility) } var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, err } var files []model.File err := q.Preload("Project").Order("id DESC"). Offset((filter.Page - 1) * filter.PageSize).Limit(filter.PageSize).Find(&files).Error return files, total, err } func (r *FileRepo) Update(f *model.File) error { return r.DB.Save(f).Error } // SoftDelete 软删除(进入回收站) func (r *FileRepo) SoftDelete(id uint) error { now := time.Now() return r.DB.Model(&model.File{}).Where("id = ?", id). Updates(map[string]interface{}{"status": 0, "deleted_at": now}).Error } // Restore 从回收站恢复 func (r *FileRepo) Restore(id uint) error { return r.DB.Model(&model.File{}).Where("id = ?", id). Updates(map[string]interface{}{"status": 1, "deleted_at": nil}).Error } func (r *FileRepo) Delete(id uint) error { return r.DB.Delete(&model.File{}, id).Error } // CountByStoredPath 统计引用同一物理文件的记录数(用于去重删除) func (r *FileRepo) CountByStoredPath(storedPath string) (int64, error) { var n int64 err := r.DB.Model(&model.File{}).Where("stored_path = ?", storedPath).Count(&n).Error return n, err } // FindByMD5AndPath 基于MD5查找可复用的物理文件记录 func (r *FileRepo) FindByMD5AndPath(md5 string) (*model.File, error) { var f model.File err := r.DB.Where("md5 = ? AND stored_path != ''", md5).Order("id ASC").First(&f).Error return &f, err } // ListByProject 项目下所有文件(用于项目删除级联) func (r *FileRepo) ListByProject(projectID uint) ([]model.File, error) { var files []model.File err := r.DB.Where("project_id = ?", projectID).Find(&files).Error return files, err } // ListByUser 用户所有文件(用于用户删除级联) func (r *FileRepo) ListByUser(userID uint) ([]model.File, error) { var files []model.File err := r.DB.Where("user_id = ?", userID).Find(&files).Error return files, err } // StatsByProject 项目文件统计 func (r *FileRepo) StatsByProject(projectID uint) (count int64, size int64, err error) { var res struct { Count int64 `json:"count"` Size int64 `json:"size"` } err = r.DB.Model(&model.File{}). Where("project_id = ? AND status = 1", projectID). Select("COUNT(*) as count, COALESCE(SUM(size),0) as size"). Scan(&res).Error return res.Count, res.Size, err } // StatsByUser 用户文件统计 func (r *FileRepo) StatsByUser(userID uint) (count int64, size int64, err error) { var res struct { Count int64 `json:"count"` Size int64 `json:"size"` } err = r.DB.Model(&model.File{}). Where("user_id = ? AND status = 1", userID). Select("COUNT(*) as count, COALESCE(SUM(size),0) as size"). Scan(&res).Error return res.Count, res.Size, err } // IncrDownloadCount 下载计数 func (r *FileRepo) IncrDownloadCount(id uint) error { return r.DB.Model(&model.File{}).Where("id = ?", id). UpdateColumn("download_count", gorm.Expr("download_count + 1")).Error } // ---- 临时链接 ---- // TempLinkRepo 临时链接数据访问 type TempLinkRepo struct{ DB *gorm.DB } func (r *TempLinkRepo) Create(l *model.TempLink) error { return r.DB.Create(l).Error } func (r *TempLinkRepo) FindByToken(token string) (*model.TempLink, error) { var l model.TempLink err := r.DB.Where("token = ?", token).First(&l).Error return &l, err } func (r *TempLinkRepo) IncrUsed(id uint) error { return r.DB.Model(&model.TempLink{}).Where("id = ?", id). UpdateColumn("used_count", gorm.Expr("used_count + 1")).Error } func (r *TempLinkRepo) DeleteByFile(fileID uint) error { return r.DB.Where("file_id = ?", fileID).Delete(&model.TempLink{}).Error } // ---- 流量日志 ---- // TrafficRepo 流量日志数据访问 type TrafficRepo struct{ DB *gorm.DB } func (r *TrafficRepo) Create(t *model.TrafficLog) error { return r.DB.Create(t).Error } // DailyStats 按天/类型聚合流量 func (r *TrafficRepo) DailyStats(userID uint, days int) ([]map[string]interface{}, error) { var rows []map[string]interface{} err := r.DB.Model(&model.TrafficLog{}). Select("date(created_at) as date, type, COALESCE(SUM(size),0) as size, COUNT(*) as count"). Where("user_id = ? AND created_at >= date('now', ?)", userID, "-"+itoa(days)+" day"). Group("date(created_at), type"). Order("date DESC"). Find(&rows).Error return rows, err } // SumByUser 汇总用户上传/下载流量 func (r *TrafficRepo) SumByUser(userID uint, days int) (upload, download int64, err error) { var res []struct { Type int64 `json:"type"` Size int64 `json:"size"` } q := r.DB.Model(&model.TrafficLog{}). Select("type, COALESCE(SUM(size),0) as size"). Where("user_id = ?", userID) if days > 0 { q = q.Where("created_at >= date('now', ?)", "-"+itoa(days)+" day") } err = q.Group("type").Scan(&res).Error for _, r := range res { if r.Type == 1 { upload = r.Size } else if r.Type == 2 { download = r.Size } } return upload, download, err } func itoa(n int) string { if n == 0 { return "0" } var buf [12]byte i := len(buf) for n > 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } return string(buf[i:]) }