2026-07-03 15:58:29 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"seeyon-filesystem/config"
|
|
|
|
|
"seeyon-filesystem/dto"
|
|
|
|
|
"seeyon-filesystem/model"
|
|
|
|
|
"seeyon-filesystem/repository"
|
2026-07-10 17:33:33 +08:00
|
|
|
"seeyon-filesystem/utils"
|
2026-07-03 15:58:29 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2026-07-10 17:33:33 +08:00
|
|
|
parentID := uint64(req.ParentID)
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
// 构建物化路径
|
|
|
|
|
parentPath := "/"
|
2026-07-10 17:33:33 +08:00
|
|
|
if parentID > 0 {
|
|
|
|
|
parent, err := s.folderRepo.FindByID(parentID)
|
2026-07-03 15:58:29 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("父文件夹不存在")
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
if !s.canAccessFolder(parent.OwnerID, ownerID) {
|
2026-07-03 15:58:29 +08:00
|
|
|
return nil, errors.New("无权访问父文件夹")
|
|
|
|
|
}
|
|
|
|
|
parentPath = parent.Path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查同名文件夹
|
2026-07-10 17:33:33 +08:00
|
|
|
existing, _ := s.folderRepo.FindByNameAndParent(req.Name, parentID, ownerID)
|
2026-07-03 15:58:29 +08:00
|
|
|
if existing != nil {
|
|
|
|
|
return nil, errors.New("同名文件夹已存在")
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
folderID := utils.GenID()
|
2026-07-03 15:58:29 +08:00
|
|
|
folder := &model.Folder{
|
2026-07-10 17:33:33 +08:00
|
|
|
ID: folderID,
|
|
|
|
|
BizID: req.BizID,
|
2026-07-03 15:58:29 +08:00
|
|
|
Name: req.Name,
|
2026-07-10 17:33:33 +08:00
|
|
|
ParentID: parentID,
|
2026-07-03 15:58:29 +08:00
|
|
|
OwnerID: ownerID,
|
2026-07-10 17:33:33 +08:00
|
|
|
Path: fmt.Sprintf("%s%d/", parentPath, folderID),
|
2026-07-03 15:58:29 +08:00
|
|
|
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("文件夹不存在")
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
if !s.canAccessFolder(folder.OwnerID, ownerID) {
|
2026-07-03 15:58:29 +08:00
|
|
|
return nil, errors.New("无权访问此文件夹")
|
|
|
|
|
}
|
|
|
|
|
return s.toFolderVO(folder), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 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) {
|
2026-07-03 15:58:29 +08:00
|
|
|
roleIDs := s.getUserRoleIDs(userID)
|
2026-07-10 17:33:33 +08:00
|
|
|
isAdmin := s.isAdmin(roleIDs)
|
2026-07-03 15:58:29 +08:00
|
|
|
|
|
|
|
|
// 如果指定了文件夹ID, 验证访问权限
|
|
|
|
|
if folderID > 0 {
|
|
|
|
|
folder, err := s.folderRepo.FindByID(folderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("文件夹不存在")
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
if !isAdmin && folder.OwnerID != userID {
|
2026-07-03 15:58:29 +08:00
|
|
|
return nil, errors.New("无权访问此文件夹")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 查询子文件夹
|
|
|
|
|
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)
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 分页查询子文件
|
|
|
|
|
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))
|
|
|
|
|
}
|
2026-07-03 15:58:29 +08:00
|
|
|
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{
|
2026-07-10 17:33:33 +08:00
|
|
|
Folders: folderVOs,
|
|
|
|
|
Files: fileVOs,
|
|
|
|
|
TotalFiles: totalFiles,
|
|
|
|
|
TotalFolders: totalFolders,
|
|
|
|
|
Page: page,
|
|
|
|
|
PageSize: pageSize,
|
2026-07-03 15:58:29 +08:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
// 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("文件夹不存在")
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
if !s.canAccessFolder(folder.OwnerID, ownerID) {
|
2026-07-03 15:58:29 +08:00
|
|
|
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("文件夹不存在")
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
if !s.canAccessFolder(folder.OwnerID, ownerID) {
|
2026-07-03 15:58:29 +08:00
|
|
|
return errors.New("无权操作此文件夹")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.folderRepo.SoftDelete(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// toFolderVO 转换为视图对象
|
|
|
|
|
func (s *FolderService) toFolderVO(folder *model.Folder) *dto.FolderVO {
|
|
|
|
|
return &dto.FolderVO{
|
2026-07-10 17:33:33 +08:00
|
|
|
ID: dto.StringID(folder.ID),
|
|
|
|
|
BizID: folder.BizID,
|
2026-07-03 15:58:29 +08:00
|
|
|
Name: folder.Name,
|
2026-07-10 17:33:33 +08:00
|
|
|
ParentID: dto.StringID(folder.ParentID),
|
|
|
|
|
OwnerID: dto.StringID(folder.OwnerID),
|
|
|
|
|
OwnerName: s.getUserName(folder.OwnerID),
|
2026-07-03 15:58:29 +08:00
|
|
|
Path: folder.Path,
|
|
|
|
|
Version: folder.Version,
|
|
|
|
|
CreatedAt: folder.CreatedAt,
|
|
|
|
|
UpdatedAt: folder.UpdatedAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
// 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 {
|
2026-07-10 17:33:33 +08:00
|
|
|
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,
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
|
|
|
|
|
// 生成下载/预览链接
|
|
|
|
|
if file.StorageKey != "" {
|
|
|
|
|
downloadURL, _ := GenerateFileURL(file, "download")
|
|
|
|
|
previewURL, _ := GenerateFileURL(file, "preview")
|
|
|
|
|
vo.DownloadURL = downloadURL
|
|
|
|
|
vo.PreviewURL = previewURL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return vo
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|