Files
SeeyonFileSystem/server/repository/file_repo.go
2026-07-03 15:58:29 +08:00

289 lines
8.4 KiB
Go

package repository
import (
"seeyon-filesystem/config"
"seeyon-filesystem/model"
)
// 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
// 构建权限条件: 自己的 OR 直接授权 OR 角色授权
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...)...).
Order("f.name ASC")
err := query.Find(&files).Error
return files, err
}
// 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
// 权限条件
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
}