package repository import ( "seeyon-filesystem/config" "seeyon-filesystem/model" "gorm.io/gorm" ) // FileRepository 文件数据访问 type FileRepository struct{} // NewFileRepository 创建文件Repository实例 func NewFileRepository() *FileRepository { return &FileRepository{} } // Create 创建文件记录 func (r *FileRepository) Create(file *model.File) error { return config.DB.Create(file).Error } // FindByID 根据ID查找文件 func (r *FileRepository) FindByID(id uint64) (*model.File, error) { var file model.File err := config.DB.First(&file, id).Error if err != nil { return nil, err } return &file, nil } // FindByMD5 根据MD5查找文件(用于秒传) func (r *FileRepository) FindByMD5(md5 string) (*model.File, error) { var file model.File err := config.DB.Where("md5 = ? AND status = 1", md5).First(&file).Error if err != nil { return nil, err } return &file, nil } // ListByFolder 查询文件夹下的文件列表(基于权限) // 使用 LEFT JOIN 一次性查出, 避免子查询 func (r *FileRepository) ListByFolder(folderID uint64, userID uint64, roleIDs []uint64) ([]model.File, error) { var files []model.File query := r.buildListByFolderQuery(folderID, userID, roleIDs) err := query.Order("f.extension ASC, f.name ASC").Find(&files).Error return files, err } // ListByFolderPaginated 分页查询文件夹下的文件列表(基于权限) func (r *FileRepository) ListByFolderPaginated(folderID uint64, userID uint64, roleIDs []uint64, page, pageSize int) ([]model.File, int64, error) { var files []model.File var total int64 query := r.buildListByFolderQuery(folderID, userID, roleIDs) // 统计总数 if err := query.Count(&total).Error; err != nil { return nil, 0, err } // 分页查询 offset := (page - 1) * pageSize err := query.Order("f.extension ASC, f.name ASC").Offset(offset).Limit(pageSize).Find(&files).Error return files, total, err } // buildListByFolderQuery 构建文件夹文件查询(公共逻辑) // 可见性: 管理员看全部 | 自己的 | 直接授权 | 角色授权 func (r *FileRepository) buildListByFolderQuery(folderID uint64, userID uint64, roleIDs []uint64) *gorm.DB { // 检查是否为管理员 isAdmin := r.isAdmin(roleIDs) // 管理员: 无额外条件, 看所有文件 if isAdmin { query := config.DB.Table("fs_file f"). Where("f.folder_id = ? AND f.status = 1", folderID) return query } // 非管理员: 自己的 + 直接授权 + 角色授权 permCond := "f.owner_id = ?" args := []interface{}{userID} permCond += " OR fp_user.user_id = ?" args = append(args, userID) if len(roleIDs) > 0 { permCond += " OR fp_role.role_id IN ?" args = append(args, roleIDs) } query := config.DB.Table("fs_file f"). Select("DISTINCT f.*"). Joins("LEFT JOIN fs_file_permission fp_user ON fp_user.file_id = f.id AND fp_user.user_id = ?", userID) if len(roleIDs) > 0 { query = query.Joins("LEFT JOIN fs_file_permission fp_role ON fp_role.file_id = f.id AND fp_role.role_id IN ?", roleIDs) } query = query.Where("f.folder_id = ? AND f.status = 1 AND ("+permCond+")", append([]interface{}{folderID}, args...)...) return query } // isAdmin 检查用户角色中是否包含 admin func (r *FileRepository) isAdmin(roleIDs []uint64) bool { if len(roleIDs) == 0 { return false } var count int64 config.DB.Model(&model.Role{}).Where("id IN ? AND name = ?", roleIDs, "admin").Count(&count) return count > 0 } // ListByOwner 查询用户的所有文件(仅自己的) func (r *FileRepository) ListByOwner(ownerID uint64) ([]model.File, error) { var files []model.File err := config.DB.Where("owner_id = ? AND status = 1", ownerID). Order("created_at DESC").Find(&files).Error return files, err } // SearchByName 按文件名搜索(基于权限, 使用JOIN优化) func (r *FileRepository) SearchByName(userID uint64, roleIDs []uint64, keyword string) ([]model.File, error) { var files []model.File isAdmin := r.isAdmin(roleIDs) var query *gorm.DB if isAdmin { // 管理员: 搜所有文件 query = config.DB.Table("fs_file f"). Where("f.status = 1") } else { // 非管理员: 自己的 + 直接授权 + 角色授权 permCond := "f.owner_id = ? OR fp_user.user_id = ?" args := []interface{}{userID, userID} if len(roleIDs) > 0 { permCond += " OR fp_role.role_id IN ?" args = append(args, roleIDs) } query = config.DB.Table("fs_file f"). Select("DISTINCT f.*"). Joins("LEFT JOIN fs_file_permission fp_user ON fp_user.file_id = f.id AND fp_user.user_id = ?", userID) if len(roleIDs) > 0 { query = query.Joins("LEFT JOIN fs_file_permission fp_role ON fp_role.file_id = f.id AND fp_role.role_id IN ?", roleIDs) } query = query.Where("f.status = 1 AND ("+permCond+")", args...) } // 多关键词: 用|分隔, OR匹配 if keyword != "" { keywords := splitKeywords(keyword) if len(keywords) > 1 { conditions := make([]string, 0, len(keywords)) args := make([]interface{}, 0, len(keywords)) for _, kw := range keywords { conditions = append(conditions, "name LIKE ?") args = append(args, "%"+kw+"%") } query = query.Where("("+joinStrings(conditions, " OR ")+")", args...) } else { query = query.Where("name LIKE ?", "%"+keywords[0]+"%") } } err := query.Order("created_at DESC").Limit(200).Find(&files).Error return files, err } // SearchByType 按文件类型搜索 func (r *FileRepository) SearchByType(ownerID uint64, extensions []string) ([]model.File, error) { var files []model.File err := config.DB.Where("owner_id = ? AND status = 1 AND extension IN ?", ownerID, extensions). Order("created_at DESC").Limit(200).Find(&files).Error return files, err } // SearchByPolicy 按存储策略搜索(管理员用) func (r *FileRepository) SearchByPolicy(policyID uint64) ([]model.File, error) { var files []model.File err := config.DB.Where("storage_policy_id = ? AND status = 1", policyID). Order("created_at DESC").Limit(500).Find(&files).Error return files, err } func splitKeywords(s string) []string { var result []string for _, kw := range splitByChar(s, '|') { kw = trimSpace(kw) if kw != "" { result = append(result, kw) } } if len(result) == 0 { result = []string{s} } return result } func splitByChar(s string, sep byte) []string { var result []string start := 0 for i := 0; i < len(s); i++ { if s[i] == sep { result = append(result, s[start:i]) start = i + 1 } } result = append(result, s[start:]) return result } func trimSpace(s string) string { start, end := 0, len(s) for start < end && s[start] == ' ' { start++ } for end > start && s[end-1] == ' ' { end-- } return s[start:end] } func joinStrings(strs []string, sep string) string { if len(strs) == 0 { return "" } result := strs[0] for i := 1; i < len(strs); i++ { result += sep + strs[i] } return result } // ListTrashed 查询回收站文件 func (r *FileRepository) ListTrashed(ownerID uint64) ([]model.File, error) { var files []model.File err := config.DB.Where("owner_id = ? AND status = 0", ownerID). Order("updated_at DESC").Find(&files).Error return files, err } // Update 更新文件信息 func (r *FileRepository) Update(file *model.File) error { return config.DB.Save(file).Error } // SoftDelete 软删除(移入回收站) func (r *FileRepository) SoftDelete(id uint64) error { return config.DB.Model(&model.File{}).Where("id = ?", id).Update("status", 0).Error } // Restore 从回收站恢复 func (r *FileRepository) Restore(id uint64) error { return config.DB.Model(&model.File{}).Where("id = ?", id).Update("status", 1).Error } // HardDelete 永久删除 func (r *FileRepository) HardDelete(id uint64) error { return config.DB.Unscoped().Delete(&model.File{}, id).Error } // IncrementDownloadCount 增加下载次数 func (r *FileRepository) IncrementDownloadCount(id uint64) error { return config.DB.Model(&model.File{}).Where("id = ?", id). Update("download_count", config.DB.Raw("download_count + 1")).Error } // CountByOwner 统计用户文件数 func (r *FileRepository) CountByOwner(ownerID uint64) (int64, error) { var count int64 err := config.DB.Model(&model.File{}).Where("owner_id = ? AND status = 1", ownerID).Count(&count).Error return count, err } // SumSizeByOwner 统计用户文件总大小 func (r *FileRepository) SumSizeByOwner(ownerID uint64) (int64, error) { var total int64 err := config.DB.Model(&model.File{}).Where("owner_id = ? AND status = 1", ownerID). Select("COALESCE(SUM(size), 0)").Scan(&total).Error return total, err } // PolicyStats 存储策略统计 type PolicyStats struct { PolicyID uint64 `json:"policy_id"` PolicyName string `json:"policy_name"` PolicyType string `json:"policy_type"` FileCount int64 `json:"file_count"` TotalSize int64 `json:"total_size"` UserCount int64 `json:"user_count"` } // GetPolicyStats 获取所有存储策略的使用统计 func (r *FileRepository) GetPolicyStats() ([]PolicyStats, error) { var stats []PolicyStats err := config.DB.Raw(` SELECT f.storage_policy_id as policy_id, COALESCE(sp.name, '未知') as policy_name, COALESCE(sp.policy_type, '') as policy_type, COUNT(*) as file_count, COALESCE(SUM(f.size), 0) as total_size, COUNT(DISTINCT f.owner_id) as user_count FROM fs_file f LEFT JOIN fs_storage_policy sp ON sp.id = f.storage_policy_id WHERE f.status = 1 GROUP BY f.storage_policy_id, sp.name, sp.policy_type ORDER BY total_size DESC `).Scan(&stats).Error return stats, err } // GetGlobalStats 获取全局统计 func (r *FileRepository) GetGlobalStats() (map[string]interface{}, error) { var totalFiles int64 var totalSize int64 var totalUsers int64 config.DB.Model(&model.File{}).Where("status = 1").Count(&totalFiles) config.DB.Model(&model.File{}).Where("status = 1").Select("COALESCE(SUM(size), 0)").Scan(&totalSize) config.DB.Model(&model.File{}).Where("status = 1").Distinct("owner_id").Count(&totalUsers) var totalFolders int64 config.DB.Model(&model.Folder{}).Where("status = 1").Count(&totalFolders) return map[string]interface{}{ "total_files": totalFiles, "total_size": totalSize, "total_folders": totalFolders, "total_users": totalUsers, }, nil }