From ee8f90df1c9ed5e623fa47695b244077f8543068 Mon Sep 17 00:00:00 2001 From: WintBit Date: Sat, 28 Oct 2023 05:01:30 +0800 Subject: [PATCH] feat(backend): support group folder ***Important: model field modified*** 1. `model.File.UserID`: `uint` => `int` 2. `model.Folder.OwnerID`: `uint` => `int` > Why? negative id means its a `group id`. > Positive means it belongs to a user. --- models/file.go | 19 ++++++++++----- models/folder.go | 10 ++++++-- models/group.go | 7 ++++++ models/user.go | 11 +++++++-- pkg/crontab/collect.go | 2 +- pkg/filesystem/file.go | 2 +- pkg/filesystem/manage.go | 47 ++++++++++++++++++++++---------------- pkg/filesystem/path.go | 11 +++++++-- pkg/hashid/hash.go | 6 ++++- pkg/mocks/wopimock/mock.go | 2 +- pkg/wopi/types.go | 2 +- pkg/wopi/wopi.go | 4 ++-- service/admin/file.go | 10 ++++---- service/explorer/wopi.go | 2 +- 14 files changed, 90 insertions(+), 45 deletions(-) diff --git a/models/file.go b/models/file.go index bfe49cba..b7b3707f 100644 --- a/models/file.go +++ b/models/file.go @@ -20,7 +20,7 @@ type File struct { gorm.Model Name string `gorm:"unique_index:idx_only_one"` SourceName string `gorm:"type:text"` - UserID uint `gorm:"index:user_id;unique_index:idx_only_one"` + UserID int `gorm:"index:user_id;unique_index:idx_only_one"` Size uint64 PicInfo string FolderID uint `gorm:"index:folder_id;unique_index:idx_only_one"` @@ -63,8 +63,12 @@ func (file *File) Create() error { return err } + if file.UserID < 0 { + return tx.Commit().Error + } + user := &User{} - user.ID = file.UserID + user.ID = uint(file.UserID) if err := user.ChangeStorage(tx, "+", file.Size); err != nil { tx.Rollback() return err @@ -254,7 +258,7 @@ func DeleteFiles(files []*File, uid uint) error { user.ID = uid var size uint64 for _, file := range files { - if uid > 0 && file.UserID != uid { + if uid > 0 && file.UserID > 0 && uint(file.UserID) != uid { tx.Rollback() return errors.New("user id not consistent") } @@ -342,8 +346,6 @@ func (file *File) UpdateSize(value uint64) error { tx := DB.Begin() var sizeDelta uint64 operator := "+" - user := User{} - user.ID = file.UserID if value > file.Size { sizeDelta = value - file.Size } else { @@ -367,12 +369,17 @@ func (file *File) UpdateSize(value uint64) error { return res.Error } + file.Size = value + if file.UserID < 0 { + return tx.Commit().Error + } + user := User{} + user.ID = uint(file.UserID) if err := user.ChangeStorage(tx, operator, sizeDelta); err != nil { tx.Rollback() return err } - file.Size = value return tx.Commit().Error } diff --git a/models/folder.go b/models/folder.go index 80f712c4..7d95a499 100644 --- a/models/folder.go +++ b/models/folder.go @@ -15,7 +15,7 @@ type Folder struct { gorm.Model Name string `gorm:"unique_index:idx_only_one_name"` ParentID *uint `gorm:"index:parent_id;unique_index:idx_only_one_name"` - OwnerID uint `gorm:"index:owner_id"` + OwnerID int `gorm:"index:owner_id"` // 数据库忽略字段 Position string `gorm:"-"` @@ -213,8 +213,14 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b // CopyFolderTo 将此目录及其子目录及文件递归复制至dstFolder // 返回此操作新增的容量 func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint64, err error) { + if folder.OwnerID < 0 { + return 0, errors.New("cannot copy group folder") + } + + ownerId := uint(folder.OwnerID) + // 列出所有子目录 - subFolders, err := GetRecursiveChildFolder([]uint{folderID}, folder.OwnerID, true) + subFolders, err := GetRecursiveChildFolder([]uint{folderID}, ownerId, true) if err != nil { return 0, err } diff --git a/models/group.go b/models/group.go index 0abf21db..756fcb09 100644 --- a/models/group.go +++ b/models/group.go @@ -19,6 +19,7 @@ type Group struct { // 数据库忽略字段 PolicyList []uint `gorm:"-"` OptionsSerialized GroupOption `gorm:"-"` + GroupFolder *Folder `gorm:"-"` } // GroupOption 用户组其他配置 @@ -82,3 +83,9 @@ func (group *Group) SerializePolicyList() (err error) { group.Options = string(optionsValue) return err } + +func (group *Group) getGroupFolder() (*Folder, bool) { + var folder Folder + result := DB.Where("owner_id = ?", -int(group.ID)).First(&folder) + return &folder, !result.RecordNotFound() +} diff --git a/models/user.go b/models/user.go index ff1d6dd6..7f6913b2 100644 --- a/models/user.go +++ b/models/user.go @@ -137,6 +137,13 @@ func GetUserByID(ID interface{}) (User, error) { func GetActiveUserByID(ID interface{}) (User, error) { var user User result := DB.Set("gorm:auto_preload", true).Where("status = ?", Active).First(&user, ID) + + if user.Group.GroupFolder == nil { + folder, ok := user.Group.getGroupFolder() + if ok { + user.Group.GroupFolder = folder + } + } return user, result.Error } @@ -180,7 +187,7 @@ func (user *User) AfterCreate(tx *gorm.DB) (err error) { // 创建用户的默认根目录 defaultFolder := &Folder{ Name: "/", - OwnerID: user.ID, + OwnerID: int(user.ID), } tx.Create(defaultFolder) return err @@ -198,7 +205,7 @@ func (user *User) AfterFind() (err error) { return err } -//SerializeOptions 将序列后的Option写入到数据库字段 +// SerializeOptions 将序列后的Option写入到数据库字段 func (user *User) SerializeOptions() (err error) { optionsValue, err := json.Marshal(&user.OptionsSerialized) user.Options = string(optionsValue) diff --git a/pkg/crontab/collect.go b/pkg/crontab/collect.go index a5678f62..ce35404f 100644 --- a/pkg/crontab/collect.go +++ b/pkg/crontab/collect.go @@ -60,7 +60,7 @@ func uploadSessionCollect() { placeholders := model.GetUploadPlaceholderFiles(0) // 将过期的上传会话按照用户分组 - userToFiles := make(map[uint][]uint) + userToFiles := make(map[int][]uint) for _, file := range placeholders { _, sessionExist := cache.Get(filesystem.UploadSessionCachePrefix + *file.UploadSessionID) if sessionExist { diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index a2ddbb1b..21ad8b09 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -56,7 +56,7 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder, file fs newFile := model.File{ Name: uploadInfo.FileName, SourceName: uploadInfo.SavePath, - UserID: fs.User.ID, + UserID: int(fs.User.ID), Size: uploadInfo.Size, FolderID: parent.ID, PolicyID: fs.Policy.ID, diff --git a/pkg/filesystem/manage.go b/pkg/filesystem/manage.go index c77c9d92..2534dcc1 100644 --- a/pkg/filesystem/manage.go +++ b/pkg/filesystem/manage.go @@ -3,14 +3,13 @@ package filesystem import ( "context" "fmt" - "path" - "strings" - model "github.com/cloudreve/Cloudreve/v3/models" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" "github.com/cloudreve/Cloudreve/v3/pkg/hashid" "github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/util" + "path" + "strings" ) /* ================= @@ -288,6 +287,12 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu // 获取子文件 childFiles, _ = folder.GetChildFiles() + if dirPath == "/" { + if fs.User.Group.GroupFolder != nil { + childFolders = append(childFolders, *fs.User.Group.GroupFolder) + } + } + return fs.listObjects(ctx, parentPath, childFiles, childFolders, pathProcessor), nil } @@ -336,15 +341,18 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo // 所有对象的父目录 var processedPath string + // 路径处理钩子, + // 所有对象父目录都是一样的,所以只处理一次 + if pathProcessor != nil { + processedPath = pathProcessor(parent) + } else { + processedPath = parent + } + for _, subFolder := range folders { - // 路径处理钩子, - // 所有对象父目录都是一样的,所以只处理一次 - if processedPath == "" { - if pathProcessor != nil { - processedPath = pathProcessor(parent) - } else { - processedPath = parent - } + var key string + if subFolder.OwnerID < 0 { + key = "group" } objects = append(objects, serializer.Object{ @@ -353,20 +361,13 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo Path: processedPath, Size: 0, Type: "dir", + Key: key, Date: subFolder.UpdatedAt, CreateDate: subFolder.CreatedAt, }) } for _, file := range files { - if processedPath == "" { - if pathProcessor != nil { - processedPath = pathProcessor(parent) - } else { - processedPath = parent - } - } - if file.UploadSessionID == nil { newFile := serializer.Object{ ID: hashid.HashID(file.ID, hashid.FileID), @@ -431,11 +432,17 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*mo return nil, ErrFileExisted } + if base == "/" && + fs.User.Group.GroupFolder != nil && + fs.User.Group.GroupFolder.Name == dir { + return fs.User.Group.GroupFolder, nil + } + // 创建目录 newFolder := model.Folder{ Name: dir, ParentID: &parent.ID, - OwnerID: fs.User.ID, + OwnerID: parent.OwnerID, } _, err := newFolder.Create() diff --git a/pkg/filesystem/path.go b/pkg/filesystem/path.go index b0637aa7..6e348452 100644 --- a/pkg/filesystem/path.go +++ b/pkg/filesystem/path.go @@ -29,11 +29,11 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) { currentFolder = fs.Root } - for _, folderName := range pathList { + for i, folderName := range pathList { var err error // 根目录 - if folderName == "/" { + if i == 0 && folderName == "/" { if currentFolder != nil { continue } @@ -42,6 +42,13 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) { return false, nil } } else { + if i == 1 { + groupFolder := fs.User.Group.GroupFolder + if groupFolder != nil && folderName == groupFolder.Name { + currentFolder = groupFolder + continue + } + } currentFolder, err = currentFolder.GetChild(folderName) if err != nil { return false, nil diff --git a/pkg/hashid/hash.go b/pkg/hashid/hash.go index ffe59441..4fe15836 100644 --- a/pkg/hashid/hash.go +++ b/pkg/hashid/hash.go @@ -56,7 +56,11 @@ func HashDecode(raw string) ([]int, error) { // HashID 计算数据库内主键对应的HashID func HashID(id uint, t int) string { - v, _ := HashEncode([]int{int(id), t}) + return HashIDInt(int(id), t) +} + +func HashIDInt(id int, t int) string { + v, _ := HashEncode([]int{id, t}) return v } diff --git a/pkg/mocks/wopimock/mock.go b/pkg/mocks/wopimock/mock.go index 0573c047..1449c202 100644 --- a/pkg/mocks/wopimock/mock.go +++ b/pkg/mocks/wopimock/mock.go @@ -10,7 +10,7 @@ type WopiClientMock struct { mock.Mock } -func (w *WopiClientMock) NewSession(user uint, file *model.File, action wopi.ActonType) (*wopi.Session, error) { +func (w *WopiClientMock) NewSession(user int, file *model.File, action wopi.ActonType) (*wopi.Session, error) { args := w.Called(user, file, action) return args.Get(0).(*wopi.Session), args.Error(1) } diff --git a/pkg/wopi/types.go b/pkg/wopi/types.go index a9425f4a..d0cfaa15 100644 --- a/pkg/wopi/types.go +++ b/pkg/wopi/types.go @@ -59,7 +59,7 @@ type Session struct { type SessionCache struct { AccessToken string FileID uint - UserID uint + UserID int Action ActonType } diff --git a/pkg/wopi/wopi.go b/pkg/wopi/wopi.go index 2938de04..3281905d 100644 --- a/pkg/wopi/wopi.go +++ b/pkg/wopi/wopi.go @@ -18,7 +18,7 @@ import ( type Client interface { // NewSession creates a new document session with access token. - NewSession(uid uint, file *model.File, action ActonType) (*Session, error) + NewSession(uid int, file *model.File, action ActonType) (*Session, error) // AvailableExts returns a list of file extensions that are supported by WOPI. AvailableExts() []string } @@ -116,7 +116,7 @@ func NewClient(endpoint string, cache cache.Driver, http request.Client) (Client }, nil } -func (c *client) NewSession(uid uint, file *model.File, action ActonType) (*Session, error) { +func (c *client) NewSession(uid int, file *model.File, action ActonType) (*Session, error) { if err := c.checkDiscovery(); err != nil { return nil, err } diff --git a/service/admin/file.go b/service/admin/file.go index a029989d..3c0cc773 100644 --- a/service/admin/file.go +++ b/service/admin/file.go @@ -93,7 +93,7 @@ func (service *FileBatchService) Delete(c *gin.Context) serializer.Response { } // 根据用户分组 - userFile := make(map[uint][]model.File) + userFile := make(map[int][]model.File) for i := 0; i < len(files); i++ { if _, ok := userFile[files[i].UserID]; !ok { userFile[files[i].UserID] = []model.File{} @@ -102,7 +102,7 @@ func (service *FileBatchService) Delete(c *gin.Context) serializer.Response { } // 异步执行删除 - go func(files map[uint][]model.File) { + go func(files map[int][]model.File) { for uid, file := range files { var ( fs *filesystem.FileSystem @@ -183,12 +183,12 @@ func (service *AdminListService) Files() serializer.Response { tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res) // 查询对应用户 - users := make(map[uint]model.User) + users := make(map[int]model.User) for _, file := range res { users[file.UserID] = model.User{} } - userIDs := make([]uint, 0, len(users)) + userIDs := make([]int, 0, len(users)) for k := range users { userIDs = append(userIDs, k) } @@ -197,7 +197,7 @@ func (service *AdminListService) Files() serializer.Response { model.DB.Where("id in (?)", userIDs).Find(&userList) for _, v := range userList { - users[v.ID] = v + users[int(v.ID)] = v } return serializer.Response{Data: map[string]interface{}{ diff --git a/service/explorer/wopi.go b/service/explorer/wopi.go index 9ee7c30e..b05a4f86 100644 --- a/service/explorer/wopi.go +++ b/service/explorer/wopi.go @@ -94,7 +94,7 @@ func (service *WopiService) FileInfo(c *gin.Context) (*serializer.WopiFileInfo, ReadOnly: true, ClosePostMessage: true, Size: int64(fs.FileTarget[0].Size), - OwnerId: hashid.HashID(fs.FileTarget[0].UserID, hashid.UserID), + OwnerId: hashid.HashIDInt(fs.FileTarget[0].UserID, hashid.UserID), } if session.Action == wopi.ActionEdit {