1、文件夹管理

用户对文件夹进行添加、删除、重命名等基础操作的同时,还可以对文件夹是否进行加密,是否共享,其他用户是否有权限访问私人文件夹等进行操作。

文件夹的操作均通过调用封装好的GetFB()方法实现,以下用FB简称代替。

func GetFB() *FileBrowser {
    once.Do(func() {
        fb = &FileBrowser{
            fs:       nil,
            dirMode:  0777,
            fileMode: 0666,
        }
        rootPath := config.AppSetting.UploadSavePath
        if !path.IsAbs(rootPath) {
            wd, err := os.Getwd()
            if err != nil {
                log.Fatalf("can not read current dir, error: %v", err.Error())
            }
            rootPath = filepath.Join(wd, rootPath)
        }
        log.Printf("use %v as file root path", rootPath)

        if err := os.MkdirAll(rootPath, fb.dirMode); err != nil {
            log.Fatalf("can not create root data dir, error: %v", err.Error())
        }
        fb.root = rootPath
        fb.fs = afero.NewBasePathFs(afero.NewOsFs(), rootPath)

    })
    return fb
}

文件夹在数据库中分为两个部分:记录文件夹信息的folder表,以及记录文件夹的用户权限信息的folder_auth表。

2、 文件夹的添加

获取到请求时,将请求中的数据分别写入用户权限信息结构体和文件夹信息结构体:

for key, auth := range req.Auth {
    auths[key] = entity.FolderAuth{
        Uid:      auth.Uid,
        Nickname: auth.Nickname,
        Face:     auth.Face,
        Read:     auth.Read,
        Write:    auth.Write,
        Deleted:  auth.Deleted,
    }
    persons[key] = auth.Nickname
}

通过FB创建文件夹,并将数据填充结构体,写入folder表:

err = filebrowser.GetFB().Mkdir(fmt.Sprintf("/%s/%s/%s", req.PoolName, req.PartitionName, req.Name))
folderInfo, err := entity.CreateFolder(tx, &entity.FolderInfo{
            Name:          req.Name,
            Uid:           user.UserID,
            Mode:          req.Mode,
            PoolName:      req.PoolName,
            PartitionName: req.PartitionName,
            IsEncrypt:     req.IsEncrypt,
            Cipher:        req.Cipher,
            Type:          types.FolderTypeDir,
            CreatedAt:     time.Now().Unix(),
            Persons:       strings.Join(persons, "、"),
            AbsPath:       fmt.Sprintf("/%s/%s/%s", req.PoolName, req.PartitionName, req.Name),
        })

判断文件夹是否是共享文件夹,并将用户权限信息填写完整,写入folder_auth表:

isShare := 0
if req.Mode == types.FolderShareDir {
    isShare = 1
}
for key := range req.Auth {
    auths[key].FolderId = folderInfo.ID
    auths[key].IsShare = isShare
}
if err = entity.BatchInsertAuth(tx, auths); err != nil {}

3、文件夹的更新

文件夹的更新也分为两个部分,更新文件夹的名字、类型(私人or共享),有权限的用户名、以及更新用户权限信息。

更新文件夹信息:

values := map[string]interface{}{
    "name":           req.Name,
    "mode":           req.Mode,
    "Persons":        strings.Join(persons, "、"), // 可访问成员
}
if err = entity.UpdateFolderInfo(tx, req.ID, values); err != nil {}

更新用户权限信息,需要先删除权限,在添加权限信息:

if err = entity.DelFolderAuth(tx, req.ID); err != nil{}
isShare := 0
if req.Mode == types.FolderShareDir {
    isShare = 1
}
for key := range auths {
    auths[key].FolderId = req.ID
    auths[key].IsShare = isShare
}
if err = entity.BatchInsertAuth(tx, auths); err != nil {}

4、 文件夹的删除

删除文件夹时,需要删除folder表、folder_auth表中的相关信息,最后通过FB删除:

if err := entity.DelFolder(tx, oldInfo.AbsPath); err != nil {
    return errors.Wrap(err, status.FolderDelFailErr)
}
if err = entity.DelFolderAuth(tx, req.Id); err != nil {
    return errors.Wrap(err, status.FolderDelFailErr)
}
if err = filebrowser.GetFB().RemoveAll(oldInfo.AbsPath); err != nil {
    return errors.Wrap(err, status.FolderDelFailErr)
}

5、 文件夹的解密和修改密码

文件夹的解密和重新加密都用到了golang的crypro标准库的aes包和cipher包。

查询数据库获取密钥:

folderInfo, err := entity.GetFolderInfo(req.Id)

将旧密码还原:

secret, err := utils.DecryptString(req.OldPwd, folderInfo.Cipher)

加密新密码,并更新数据库:

cipher, err := utils.EncryptString(req.NewPwd, secret)
if err = entity.UpdateFolderInfo(entity.GetDB(), req.Id, entity.FolderInfo{Cipher: cipher});

文件夹解除密码时只需要调用utils内函数即可:

_, err = utils.GetFolderSecret(req.Path, req.Password)

6、 移除用户时,需要删除那些文件夹

在网盘里移除用户时,我们需要查找其建立的私人文件夹:

folderInfos, err := entity.GetPrivateFolders(req.UserIDs)
for _, folderInfo := range folderInfos {
    err = removeFolderAndRecode(fs, folderInfo.AbsPath)
    if err != nil {
        return
    }
}

并且要删除其初始化的文件夹:

for _, v := range req.UserIDs {
    folderRow, err := entity.GetRelateFolderInfoByUid(types.FolderSelfDirUid, v)
    if err != nil {
        return
    }
    err = removeFolderAndRecode(fs, folderRow.AbsPath)
    if err != nil {
        return
    }
}

最后要删除folder_auth表中关于该用户UID的所有记录:

if err = entity.DelFolderAuthByUid(req.UserIDs); err != nil {
    return
}

7、 获取文件夹列表

通过查询数据库获取所有的文件夹信息并获取文件夹的异步任务信息:

pageOffset := utils.GetPageOffset(req.Page, req.PageSize)
folderInfos, err := entity.GetFolderList(user.UserID, pageOffset, req.PageSize)
if err != nil {
    return
}

for _, folderInfo := range folderInfos {
    taskId, status := getFolderTaskInfo(folderInfo.ID)
    list = append(list, &Info{
        ID:        folderInfo.ID,
        Name:      folderInfo.Name,
        IsEncrypt: folderInfo.IsEncrypt,
        Mode:      folderInfo.Mode,
        Path:      folderInfo.AbsPath,
        Type:      folderInfo.Type,
        Persons:   folderInfo.Persons,
        PoolName:  fmt.Sprintf("%s-%s", folderInfo.PoolName, folderInfo.PartitionName),
        Status:    status,
        TaskId:    taskId,
    })
}

8、 获取文件夹的信息

文件夹的信息分为两个部分,一个部分是文件夹的本体信息,另一个部分是该文件夹的用户权限信息:

info, err := entity.GetFolderInfo(req.Id)
folderAuthList, err := entity.GetFolderAuthByFolderId(req.Id)

resp.ID = info.ID
resp.Name = info.Name
resp.IsEncrypt = info.IsEncrypt
resp.Mode = info.Mode
resp.Type = info.Type
resp.PoolName = info.PoolName
resp.PartitionName = info.PartitionName

for _, auth := range folderAuthList {
    resp.Auth = append(resp.Auth, AddAuthResp{
        Uid:      auth.Uid,
        Nickname: auth.Nickname,
        Face:     auth.Face,
        Read:     auth.Read,
        Write:    auth.Write,
        Deleted:  auth.Deleted,
    })
}