package service import ( "errors" "fmt" "seeyon-filesystem/config" "seeyon-filesystem/dto" "seeyon-filesystem/model" "seeyon-filesystem/repository" "seeyon-filesystem/utils" ) // FolderService 文件夹业务服务 type FolderService struct { folderRepo *repository.FolderRepository fileRepo *repository.FileRepository } // NewFolderService 创建文件夹服务实例 func NewFolderService() *FolderService { return &FolderService{ folderRepo: repository.NewFolderRepository(), fileRepo: repository.NewFileRepository(), } } // Create 创建文件夹 func (s *FolderService) Create(req *dto.CreateFolderRequest, ownerID uint64) (*dto.FolderVO, error) { parentID := uint64(req.ParentID) // 构建物化路径 parentPath := "/" if parentID > 0 { parent, err := s.folderRepo.FindByID(parentID) if err != nil { return nil, errors.New("父文件夹不存在") } if !s.canAccessFolder(parent.OwnerID, ownerID) { return nil, errors.New("无权访问父文件夹") } parentPath = parent.Path } // 检查同名文件夹 existing, _ := s.folderRepo.FindByNameAndParent(req.Name, parentID, ownerID) if existing != nil { return nil, errors.New("同名文件夹已存在") } folderID := utils.GenID() folder := &model.Folder{ ID: folderID, BizID: req.BizID, Name: req.Name, ParentID: parentID, OwnerID: ownerID, Path: fmt.Sprintf("%s%d/", parentPath, folderID), Status: 1, } if err := s.folderRepo.Create(folder); err != nil { return nil, fmt.Errorf("创建文件夹失败: %w", err) } return s.toFolderVO(folder), nil } // GetByID 根据ID获取文件夹 func (s *FolderService) GetByID(id uint64, ownerID uint64) (*dto.FolderVO, error) { folder, err := s.folderRepo.FindByID(id) if err != nil { return nil, errors.New("文件夹不存在") } if !s.canAccessFolder(folder.OwnerID, ownerID) { return nil, errors.New("无权访问此文件夹") } return s.toFolderVO(folder), nil } // GetByBizID 根据业务ID获取文件夹 func (s *FolderService) GetByBizID(bizID string, ownerID uint64) (*dto.FolderVO, error) { folder, err := s.folderRepo.FindByBizID(bizID, ownerID) if err != nil { return nil, errors.New("文件夹不存在") } return s.toFolderVO(folder), nil } // ListChildren 获取文件夹的子项(文件+文件夹, 基于权限, 支持分页) // 管理员看全部, 非管理员只看自己创建的 + 被授权的 func (s *FolderService) ListChildren(folderID uint64, userID uint64, page, pageSize int) (*dto.FileListVO, error) { roleIDs := s.getUserRoleIDs(userID) isAdmin := s.isAdmin(roleIDs) // 如果指定了文件夹ID, 验证访问权限 if folderID > 0 { folder, err := s.folderRepo.FindByID(folderID) if err != nil { return nil, errors.New("文件夹不存在") } if !isAdmin && folder.OwnerID != userID { return nil, errors.New("无权访问此文件夹") } } // 查询子文件夹 var folders []model.Folder var totalFolders int64 if isAdmin { folders, _ = s.folderRepo.ListByParentAll(folderID) totalFolders, _ = s.folderRepo.CountByParentAll(folderID) } else { folders, _ = s.folderRepo.ListByParent(folderID, userID) totalFolders, _ = s.folderRepo.CountByParent(folderID, userID) } // 分页查询子文件 var files []model.File var totalFiles int64 var err error if pageSize > 0 { files, totalFiles, err = s.fileRepo.ListByFolderPaginated(folderID, userID, roleIDs, page, pageSize) } else { files, err = s.fileRepo.ListByFolder(folderID, userID, roleIDs) totalFiles = int64(len(files)) } if err != nil { return nil, err } // 组装响应 folderVOs := make([]dto.FolderVO, 0, len(folders)) for _, f := range folders { folderVOs = append(folderVOs, *s.toFolderVO(&f)) } fileVOs := make([]dto.FileVO, 0, len(files)) for _, f := range files { fileVOs = append(fileVOs, *s.toFileVO(&f)) } return &dto.FileListVO{ Folders: folderVOs, Files: fileVOs, TotalFiles: totalFiles, TotalFolders: totalFolders, Page: page, PageSize: pageSize, }, nil } // canAccessFolder 检查用户是否有权操作该文件夹(owner / admin) func (s *FolderService) canAccessFolder(folderOwnerID, userID uint64) bool { if folderOwnerID == userID { return true } return s.isAdmin(s.getUserRoleIDs(userID)) } // isAdmin 检查用户是否为管理员 func (s *FolderService) 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 } // Rename 重命名文件夹(乐观锁) func (s *FolderService) Rename(id uint64, newName string, ownerID uint64, version int) error { folder, err := s.folderRepo.FindByID(id) if err != nil { return errors.New("文件夹不存在") } if !s.canAccessFolder(folder.OwnerID, ownerID) { return errors.New("无权操作此文件夹") } // 乐观锁检查 if version > 0 && folder.Version != version { return fmt.Errorf("文件夹已被其他人修改, 当前版本: %d, 请刷新后重试", folder.Version) } newVersion := folder.Version + 1 result := config.DB.Model(&model.Folder{}). Where("id = ? AND version = ?", id, folder.Version). Updates(map[string]interface{}{ "name": newName, "version": newVersion, }) if result.RowsAffected == 0 { return fmt.Errorf("文件夹已被其他人修改, 请刷新后重试") } return result.Error } // Delete 删除文件夹(移入回收站) func (s *FolderService) Delete(id uint64, ownerID uint64) error { folder, err := s.folderRepo.FindByID(id) if err != nil { return errors.New("文件夹不存在") } if !s.canAccessFolder(folder.OwnerID, ownerID) { return errors.New("无权操作此文件夹") } return s.folderRepo.SoftDelete(id) } // toFolderVO 转换为视图对象 func (s *FolderService) toFolderVO(folder *model.Folder) *dto.FolderVO { return &dto.FolderVO{ ID: dto.StringID(folder.ID), BizID: folder.BizID, Name: folder.Name, ParentID: dto.StringID(folder.ParentID), OwnerID: dto.StringID(folder.OwnerID), OwnerName: s.getUserName(folder.OwnerID), Path: folder.Path, Version: folder.Version, CreatedAt: folder.CreatedAt, UpdatedAt: folder.UpdatedAt, } } // getUserName 根据用户ID获取显示名称 func (s *FolderService) getUserName(userID uint64) string { var user model.User if config.DB.Select("nickname", "username").First(&user, userID).Error != nil { return "" } if user.Nickname != "" { return user.Nickname } return user.Username } // getUserRoleIDs 获取用户的角色ID列表 func (s *FolderService) getUserRoleIDs(userID uint64) []uint64 { var userRoles []model.UserRole config.DB.Where("user_id = ?", userID).Find(&userRoles) roleIDs := make([]uint64, 0, len(userRoles)) for _, ur := range userRoles { roleIDs = append(roleIDs, ur.RoleID) } return roleIDs } // toFileVO 转换为视图对象 func (s *FolderService) toFileVO(file *model.File) *dto.FileVO { vo := &dto.FileVO{ ID: dto.StringID(file.ID), Name: file.Name, Extension: file.Extension, FolderID: dto.StringID(file.FolderID), OwnerID: dto.StringID(file.OwnerID), OwnerName: s.getUserName(file.OwnerID), Size: file.Size, StorageKey: file.StorageKey, StoragePolicyID: file.StoragePolicyID, MimeType: file.MimeType, IsFavorite: file.IsFavorite, DownloadCount: file.DownloadCount, Version: file.Version, CreatedAt: file.CreatedAt, UpdatedAt: file.UpdatedAt, } // 生成下载/预览链接 if file.StorageKey != "" { downloadURL, _ := GenerateFileURL(file, "download") previewURL, _ := GenerateFileURL(file, "preview") vo.DownloadURL = downloadURL vo.PreviewURL = previewURL } return vo }