1. 文件的上传
文件的上传是以分块的形式上传,文件的哈希值为缓存目录,存储在缓存路径下。当分块上传完毕后,则会合并文件夹,将缓存文件夹删除。
2. 分块上传
文件的分块已经由前端切割好,发送的请求中req.Action字段会赋值为chunk,网盘只用负责接收请求并根据请求中的动作做出相对应的处理。
为保证文件的完整性,会使用文件的哈希值作为缓存路径的文件夹名,并在该路径下生成缓存文件。
cachePath := req.getCachePath(user.UserID)
if err = fs.Mkdir(cachePath); err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
if req.isFileExist(filepath.Join(cachePath, req.chunkNumber)) {
return
}
tmpFile, err := ioutil.TempFile(filepath.Join(fs.GetRoot(), cachePath), "temp-")
if err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
defer tmpFile.Close()
赋值上传文件的内容,重新命名缓存文件。
3.分块合并
当上传最后一块分块时,req.Action会赋值为merge,插件会根据该字段进行分块合并的操作。
打开缓存路径,统计分块总数是否与请求中的分块总数一致:
fileInfos, err := file.Readdir(-1)
cachePath := req.getCachePath(user.UserID)
file, err := fs.Open(cachePath)
totalChunks, _ := strconv.Atoi(req.TotalChunks)
if len(fileInfos) != totalChunks {
err = errors.New(status.ChunkFileNotExistErr)
return
}
创建临时文件,将缓存目录下的所有的缓存文件写入到临时文件中,并检验临时文件的哈希值。
tempFile, err := ioutil.TempFile(filepath.Join(fs.GetRoot(), cachePath), "temp-")
if err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
for i := range fileInfos {
var chunkFile filebrowser.File
chunkFile, err = fs.Open(filepath.Join(cachePath, strconv.Itoa(i+1)))
if err != nil {
return resp, err
}
var b []byte
b, err = ioutil.ReadAll(chunkFile)
if err != nil {
return resp, err
}
tempFile.Write(b)
chunkFile.Close()
}
tempFile.Close()
rootPath := strings.TrimPrefix(tempFile.Name(), fs.GetRoot())
if err = req.checkFileHash(rootPath); err != nil {
return
}
移动并重命名临时文件,并对其目标目录判断是否需要密钥,如果需要密钥则要对复制后的文件进行加密处理,如果没有就直接将文件复制到目标目录下,删除临时文件:
fileName := strings.TrimPrefix(tempFile.Name(), fs.GetRoot())
// 获取目录的密钥且校验密码,如果密钥为空,则不需要加密,
secret, err := utils.GetFolderSecret(req.path, c.GetHeader("pwd"))
if secret != "" {
err = fs.CopyFileToTarget(fileName, newPath + types.FolderEncryptExt) // 如果需要的话,需要加上.env文件
if err != nil {
……
return
}
_, err = utils2.EncryptFile(secret, newPath + types.FolderEncryptExt, newPath)
if err != nil {
……
return
}
// 把源文件删除
_ = fs.Remove(newPath + types.FolderEncryptExt)
} else {
err = fs.CopyFileToTarget(fileName, newPath)
if err != nil {
……
return
}
}
// 成功后删除原来的文件
_ = fs.RemoveAll(fileName)
if err = req.createFolder(newPath, types.FolderTypeFile, user.UserID); err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
resp, err = req.wrapResp(newPath, fs)
if err == nil {
// 如果合并成功,把分片的文件夹删除
_ = fs.RemoveAll(cachePath)
}
4. 文件的下载
文件下载需要先判断是否当前用户是有否写权限:
write, _ := c.Get("write")
如果文件有密码,则需要先解密:
if secret != "" {
ext := strconv.FormatInt(time.Now().UnixNano(), 10)
downloadPath, err = utils2.DecryptFile(pwd, downloadPath, fmt.Sprint(downloadPath, ".", ext))
if err != nil {
return
}
}
但没有密码就直接打开原文件:
open, err := fb.Open(downloadPath)
最后通过http包中的ServeContent方法传输文件
http.ServeContent(c.Writer, c.Request, fileName, fileInfo.ModTime(), open)
5. 文件的删除
删除文件时,需要判断是否是目录,如果是目录,则需要通过FB将其下的所有文件都删除,否则就根据路径删除文件:
fileInfo, err = fs.Stat(path)
if err != nil {
return
}
if fileInfo.IsDir() {
if err = fs.RemoveAll(path); err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
} else {
if err = fs.Remove(path); err != nil {
err = errors.Wrap(err, errors.InternalServerErr)
return
}
}
最后,删除folder表中的关联信息
if err = entity.DelFolderByAbsPaths(entity.GetDB(), req.Paths); err != nil{}
6.文件的复制和移动
文件进行复制或移动时,需要检查参数,包括req.Action请求是否合法、路径是否合法、以及本次操作的可行性,并获取文件夹密钥和目标文件夹。
if err = req.validateRequest(user.UserID); err != nil{}
secret, err := utils.GetFolderSecret(req.Destination, req.DestinationPwd)
req.Destination, err = utils.GetNewPath(req.Destination)
复制需要FB判断该目标是文件夹还是文件再做复制:
isDir, err := fs.IsDir(path)
if err != nil {
return
}
if !isDir {
if err = fs.CopyFile(path, req.Destination); err != nil {
return
}
} else {
if err = fs.CopyDir(path, req.Destination); err != nil {
return
}
}
移动文件/文件夹同样是需要FB判断目标是文件夹还是文件,之后先将文件/文件夹复制到目标目录后再将原路径下的删除:
isDir, _ := fs.IsDir(path)
if isDir {
// 如果是目录, 先复制
if err = fs.CopyDir(path, req.Destination); err != nil {
config.Logger.Errorf("resource_operate CopyDir err %v", err)
return
}
} else {
// 如果是文件, 先复制
if err = fs.CopyFile(path, req.Destination); err != nil {
config.Logger.Errorf("resource_operate MoveFile err %v", err)
return
}
}
if err = filebrowser.GetFB().RemoveAll(path); err != nil {
config.Logger.Errorf("resource_operate RemoveAll err %v", err)
return
}
// 更新原文件的数据,调整路径
newPath := filepath.Join(req.Destination, filepath.Base(path))
if err = utils.UpdateFolderPath(entity.GetDB(), path, newPath); err != nil {
config.Logger.Errorf("resource_operate UpdateFolderPath err %v", err)
return
}
最后需要保存数据,并且判断是否需要做加密解密处理:
destPath := filepath.Join(req.Destination, filepath.Base(path))
if err = req.saveFolder(key, uid, secret, destPath); err != nil {
config.Logger.Errorf("resource_operate save folder err %v", err)
return
}
7.文件的重命名
文件的重命名比较简单,只需要通过FB重命名,在判断错误信息即可:
if err = fs.Rename(req.Path, newPath); err != nil {
……
}