Feat: file download in service level
This commit is contained in:
parent
a734493b65
commit
4156a71adf
10 changed files with 99 additions and 30 deletions
|
@ -17,6 +17,9 @@ type File struct {
|
|||
FolderID uint `gorm:"index:folder_id"`
|
||||
PolicyID uint
|
||||
Dir string `gorm:"size:65536"`
|
||||
|
||||
// 关联模型
|
||||
Policy Policy `gorm:"PRELOAD:false,association_autoupdate:false"`
|
||||
}
|
||||
|
||||
// Create 创建文件记录
|
||||
|
@ -41,3 +44,12 @@ func (folder *Folder) GetChildFile() ([]File, error) {
|
|||
result := DB.Where("folder_id = ?", folder.ID).Find(&files)
|
||||
return files, result.Error
|
||||
}
|
||||
|
||||
// GetPolicy 获取文件所属策略
|
||||
// TODO:test
|
||||
func (file *File) GetPolicy() *Policy {
|
||||
if file.Policy.Model.ID == 0 {
|
||||
file.Policy, _ = GetPolicyByID(file.PolicyID)
|
||||
}
|
||||
return &file.Policy
|
||||
}
|
||||
|
|
|
@ -9,4 +9,6 @@ const (
|
|||
SavePathCtx
|
||||
// FileHeaderCtx 上传的文件
|
||||
FileHeaderCtx
|
||||
// PathCtx 文件或目录的虚拟路径
|
||||
PathCtx
|
||||
)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package filesystem
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownPolicyType = errors.New("未知存储策略类型")
|
||||
|
@ -8,7 +11,8 @@ var (
|
|||
ErrFileExtensionNotAllowed = errors.New("不允许上传此类型的文件")
|
||||
ErrInsufficientCapacity = errors.New("容量空间不足")
|
||||
ErrIllegalObjectName = errors.New("目标名称非法")
|
||||
ErrInsertFileRecord = errors.New("无法插入文件记录")
|
||||
ErrInsertFileRecord = serializer.NewError(serializer.CodeDBError, "无法插入文件记录", nil)
|
||||
ErrFileExisted = errors.New("同名文件已存在")
|
||||
ErrPathNotExist = errors.New("路径不存在")
|
||||
ErrPathNotExist = serializer.NewError(404, "路径不存在", nil)
|
||||
ErrObjectNotExist = serializer.NewError(404, "文件不存在", nil)
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -36,7 +37,28 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model
|
|||
return &newFile, nil
|
||||
}
|
||||
|
||||
// Download 处理下载文件请求
|
||||
// Download 处理下载文件请求,path为虚拟路径
|
||||
// TODO:测试
|
||||
func (fs *FileSystem) Download(ctx context.Context, path string) (io.ReadCloser, error) {
|
||||
// 触发`下载前`钩子
|
||||
err := fs.Trigger(ctx, fs.BeforeFileDownload)
|
||||
if err != nil {
|
||||
util.Log().Debug("BeforeFileDownload 钩子执行失败,%s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 找到文件
|
||||
exist, file := fs.IsFileExist(path)
|
||||
if !exist {
|
||||
return nil, ErrObjectNotExist
|
||||
}
|
||||
|
||||
// 将当前存储策略重设为文件使用的
|
||||
fs.Policy = file.GetPolicy()
|
||||
err = fs.dispatchHandler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, serializer.NewError(serializer.CodeEncryptError, "人都的", errors.New("不是人都的"))
|
||||
}
|
||||
|
|
|
@ -29,10 +29,10 @@ type Handler interface {
|
|||
|
||||
// FileSystem 管理文件的文件系统
|
||||
type FileSystem struct {
|
||||
/*
|
||||
文件系统所有者
|
||||
*/
|
||||
// 文件系统所有者
|
||||
User *model.User
|
||||
// 操作文件使用的上传策略
|
||||
Policy *model.Policy
|
||||
|
||||
/*
|
||||
钩子函数
|
||||
|
@ -45,6 +45,8 @@ type FileSystem struct {
|
|||
AfterValidateFailed []Hook
|
||||
// 用户取消上传后
|
||||
AfterUploadCanceled []Hook
|
||||
// 文件下载前
|
||||
BeforeFileDownload []Hook
|
||||
|
||||
/*
|
||||
文件系统处理适配器
|
||||
|
@ -54,21 +56,36 @@ type FileSystem struct {
|
|||
|
||||
// NewFileSystem 初始化一个文件系统
|
||||
func NewFileSystem(user *model.User) (*FileSystem, error) {
|
||||
var handler Handler
|
||||
|
||||
// 根据存储策略类型分配适配器
|
||||
switch user.Policy.Type {
|
||||
case "local":
|
||||
handler = local.Handler{}
|
||||
default:
|
||||
return nil, ErrUnknownPolicyType
|
||||
fs := &FileSystem{
|
||||
User: user,
|
||||
}
|
||||
|
||||
// 分配存储策略适配器
|
||||
err := fs.dispatchHandler()
|
||||
|
||||
// TODO 分配默认钩子
|
||||
return &FileSystem{
|
||||
User: user,
|
||||
Handler: handler,
|
||||
}, nil
|
||||
return fs, err
|
||||
}
|
||||
|
||||
// dispatchHandler 根据存储策略分配文件适配器
|
||||
// TODO: 测试
|
||||
func (fs *FileSystem) dispatchHandler() error {
|
||||
var policyType string
|
||||
if fs.Policy == nil {
|
||||
// 如果没有具体指定,就是用用户当前存储策略
|
||||
policyType = fs.User.Policy.Type
|
||||
} else {
|
||||
policyType = fs.Policy.Type
|
||||
}
|
||||
|
||||
// 根据存储策略类型分配适配器
|
||||
switch policyType {
|
||||
case "local":
|
||||
fs.Handler = local.Handler{}
|
||||
return nil
|
||||
default:
|
||||
return ErrUnknownPolicyType
|
||||
}
|
||||
}
|
||||
|
||||
// NewFileSystemFromContext 从gin.Context创建文件系统
|
||||
|
|
|
@ -21,6 +21,8 @@ func (fs *FileSystem) Use(name string, hook Hook) {
|
|||
fs.AfterValidateFailed = append(fs.AfterValidateFailed, hook)
|
||||
case "AfterUploadCanceled":
|
||||
fs.AfterUploadCanceled = append(fs.AfterUploadCanceled, hook)
|
||||
case "BeforeFileDownload":
|
||||
fs.BeforeFileDownload = append(fs.BeforeFileDownload, hook)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +39,15 @@ func (fs *FileSystem) Trigger(ctx context.Context, hooks []Hook) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// HookIsFileExist 检查虚拟路径文件是否存在
|
||||
func HookIsFileExist(ctx context.Context, fs *FileSystem) error {
|
||||
filePath := ctx.Value(PathCtx).(string)
|
||||
if ok, _ := fs.IsFileExist(filePath); ok {
|
||||
return nil
|
||||
}
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
|
||||
// HookValidateFile 一系列对文件检验的集合
|
||||
func HookValidateFile(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||
|
@ -107,10 +118,10 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
|||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if fs.IsFileExist(path.Join(
|
||||
if ok, _ := fs.IsFileExist(path.Join(
|
||||
virtualPath,
|
||||
ctx.Value(FileHeaderCtx).(FileHeader).GetFileName(),
|
||||
)) {
|
||||
)); ok {
|
||||
return ErrFileExisted
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) erro
|
|||
}
|
||||
|
||||
// 是否有同名文件
|
||||
if fs.IsFileExist(path.Join(base, dir)) {
|
||||
if ok, _ := fs.IsFileExist(path.Join(base, dir)); ok {
|
||||
return ErrFileExisted
|
||||
}
|
||||
|
||||
|
@ -133,11 +133,11 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) {
|
|||
}
|
||||
|
||||
// IsFileExist 返回给定路径的文件是否存在
|
||||
func (fs *FileSystem) IsFileExist(fullPath string) bool {
|
||||
func (fs *FileSystem) IsFileExist(fullPath string) (bool, model.File) {
|
||||
basePath := path.Dir(fullPath)
|
||||
fileName := path.Base(fullPath)
|
||||
|
||||
_, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
|
||||
file, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
|
||||
|
||||
return err == nil
|
||||
return err == nil, file
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestFileSystem_IsFileExist(t *testing.T) {
|
|||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/s", "1.txt").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"Name"}).AddRow("s"),
|
||||
)
|
||||
testResult := fs.IsFileExist("/s/1.txt")
|
||||
testResult, _ := fs.IsFileExist("/s/1.txt")
|
||||
asserts.True(testResult)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
|
@ -29,7 +29,7 @@ func TestFileSystem_IsFileExist(t *testing.T) {
|
|||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/ss/dfsd", "1.txt").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"Name"}),
|
||||
)
|
||||
testResult = fs.IsFileExist("/ss/dfsd/1.txt")
|
||||
testResult, _ = fs.IsFileExist("/ss/dfsd/1.txt")
|
||||
asserts.False(testResult)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|||
followUpErr := fs.Trigger(ctx, fs.AfterValidateFailed)
|
||||
// 失败后再失败...
|
||||
if followUpErr != nil {
|
||||
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||
util.Log().Debug("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -85,7 +85,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe
|
|||
ctx = context.WithValue(ctx, SavePathCtx, path)
|
||||
err := fs.Trigger(ctx, fs.AfterUploadCanceled)
|
||||
if err != nil {
|
||||
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||
util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@ func (service *FileDownloadService) Download(ctx context.Context, c *gin.Context
|
|||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
|
||||
}
|
||||
|
||||
// 开始处理下载
|
||||
ctx = context.WithValue(ctx, filesystem.GinCtx, c)
|
||||
_, err = fs.Download(ctx, service.Path)
|
||||
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue