Modify: split hooks into small modules
This commit is contained in:
parent
451bdb4ee1
commit
0cb80f69f5
11 changed files with 191 additions and 53 deletions
1
go.mod
1
go.mod
|
@ -14,6 +14,7 @@ require (
|
|||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
|
||||
github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/qiniu/api.v7/v7 v7.4.0
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||
"github.com/gin-gonic/gin"
|
||||
testMock "github.com/stretchr/testify/mock"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -23,17 +22,13 @@ type FileHeader interface {
|
|||
// Handler 存储策略适配器
|
||||
type Handler interface {
|
||||
// 上传文件
|
||||
Put(ctx context.Context, file io.ReadCloser, dst string) error
|
||||
Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error
|
||||
// 删除一个或多个文件
|
||||
Delete(ctx context.Context, files []string) ([]string, error)
|
||||
}
|
||||
|
||||
// FileSystem 管理文件的文件系统
|
||||
type FileSystem struct {
|
||||
/*
|
||||
测试用
|
||||
*/
|
||||
testMock.Mock
|
||||
/*
|
||||
文件系统所有者
|
||||
*/
|
||||
|
@ -43,13 +38,13 @@ type FileSystem struct {
|
|||
钩子函数
|
||||
*/
|
||||
// 上传文件前
|
||||
BeforeUpload func(ctx context.Context, fs *FileSystem) error
|
||||
BeforeUpload []Hook
|
||||
// 上传文件后
|
||||
AfterUpload func(ctx context.Context, fs *FileSystem) error
|
||||
AfterUpload []Hook
|
||||
// 文件保存成功,插入数据库验证失败后
|
||||
AfterValidateFailed func(ctx context.Context, fs *FileSystem) error
|
||||
AfterValidateFailed []Hook
|
||||
// 用户取消上传后
|
||||
AfterUploadCanceled func(ctx context.Context, fs *FileSystem) error
|
||||
AfterUploadCanceled []Hook
|
||||
|
||||
/*
|
||||
文件系统处理适配器
|
||||
|
@ -77,7 +72,6 @@ func NewFileSystem(user *model.User) (*FileSystem, error) {
|
|||
}
|
||||
|
||||
// NewFileSystemFromContext 从gin.Context创建文件系统
|
||||
// TODO:test
|
||||
func NewFileSystemFromContext(c *gin.Context) (*FileSystem, error) {
|
||||
user, exist := c.Get("user")
|
||||
if !exist {
|
||||
|
|
|
@ -7,8 +7,38 @@ import (
|
|||
"path"
|
||||
)
|
||||
|
||||
// GenericBeforeUpload 通用上传前处理钩子,包含数据库操作
|
||||
func GenericBeforeUpload(ctx context.Context, fs *FileSystem) error {
|
||||
// Hook 钩子函数
|
||||
type Hook func(ctx context.Context, fs *FileSystem) error
|
||||
|
||||
// Use 注入钩子
|
||||
func (fs *FileSystem) Use(name string, hook Hook) {
|
||||
switch name {
|
||||
case "BeforeUpload":
|
||||
fs.BeforeUpload = append(fs.BeforeUpload, hook)
|
||||
case "AfterUpload":
|
||||
fs.AfterUpload = append(fs.AfterUpload, hook)
|
||||
case "AfterValidateFailed":
|
||||
fs.AfterValidateFailed = append(fs.AfterValidateFailed, hook)
|
||||
case "AfterUploadCanceled":
|
||||
fs.AfterUploadCanceled = append(fs.AfterUploadCanceled, hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger 触发钩子,遇到第一个错误时
|
||||
// 返回错误,后续钩子不会继续执行
|
||||
func (fs *FileSystem) Trigger(ctx context.Context, hooks []Hook) error {
|
||||
for _, hook := range hooks {
|
||||
err := hook(ctx, fs)
|
||||
if err != nil {
|
||||
util.Log().Warning("钩子执行失败:%s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HookValidateFile 一系列对文件检验的集合
|
||||
func HookValidateFile(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||
|
||||
// 验证单文件尺寸
|
||||
|
@ -26,6 +56,13 @@ func GenericBeforeUpload(ctx context.Context, fs *FileSystem) error {
|
|||
return ErrFileExtensionNotAllowed
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// HookValidateCapacity 验证并扣除用户容量,包含数据库操作
|
||||
func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||
// 验证并扣除容量
|
||||
if !fs.ValidateCapacity(ctx, file.GetSize()) {
|
||||
return ErrInsufficientCapacity
|
||||
|
@ -33,10 +70,8 @@ func GenericBeforeUpload(ctx context.Context, fs *FileSystem) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GenericAfterUploadCanceled 通用上传取消处理钩子,包含数据库操作
|
||||
func GenericAfterUploadCanceled(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||
|
||||
// HookDeleteTempFile 删除已保存的临时文件
|
||||
func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
|
||||
filePath := ctx.Value(SavePathCtx).(string)
|
||||
// 删除临时文件
|
||||
if util.Exists(filePath) {
|
||||
|
@ -46,6 +81,13 @@ func GenericAfterUploadCanceled(ctx context.Context, fs *FileSystem) error {
|
|||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HookGiveBackCapacity 归还用户容量
|
||||
func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||
|
||||
// 归还用户容量
|
||||
if !fs.User.DeductionStorage(file.GetSize()) {
|
||||
return errors.New("无法继续降低用户已用存储")
|
||||
|
|
|
@ -34,20 +34,20 @@ func TestGenericBeforeUpload(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
asserts.Error(GenericBeforeUpload(ctx, &fs))
|
||||
asserts.Error(HookValidateFile(ctx, &fs))
|
||||
|
||||
file.Size = 1
|
||||
file.Name = "1"
|
||||
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
|
||||
asserts.Error(GenericBeforeUpload(ctx, &fs))
|
||||
asserts.Error(HookValidateFile(ctx, &fs))
|
||||
|
||||
file.Name = "1.txt"
|
||||
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
|
||||
asserts.NoError(GenericBeforeUpload(ctx, &fs))
|
||||
asserts.NoError(HookValidateFile(ctx, &fs))
|
||||
|
||||
file.Name = "1.t/xt"
|
||||
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
|
||||
asserts.Error(GenericBeforeUpload(ctx, &fs))
|
||||
asserts.Error(HookValidateFile(ctx, &fs))
|
||||
}
|
||||
|
||||
func TestGenericAfterUploadCanceled(t *testing.T) {
|
||||
|
@ -67,7 +67,9 @@ func TestGenericAfterUploadCanceled(t *testing.T) {
|
|||
}
|
||||
|
||||
// 成功
|
||||
err = GenericAfterUploadCanceled(ctx, &fs)
|
||||
err = HookDeleteTempFile(ctx, &fs)
|
||||
asserts.NoError(err)
|
||||
err = HookGiveBackCapacity(ctx, &fs)
|
||||
asserts.NoError(err)
|
||||
asserts.Equal(uint64(0), fs.User.Storage)
|
||||
|
||||
|
@ -76,12 +78,12 @@ func TestGenericAfterUploadCanceled(t *testing.T) {
|
|||
f.Close()
|
||||
|
||||
// 容量不能再降低
|
||||
err = GenericAfterUploadCanceled(ctx, &fs)
|
||||
err = HookGiveBackCapacity(ctx, &fs)
|
||||
asserts.Error(err)
|
||||
|
||||
//文件不存在
|
||||
fs.User.Storage = 5
|
||||
err = GenericAfterUploadCanceled(ctx, &fs)
|
||||
err = HookDeleteTempFile(ctx, &fs)
|
||||
asserts.NoError(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
type Handler struct{}
|
||||
|
||||
// Put 将文件流保存到指定目录
|
||||
func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string) error {
|
||||
func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error {
|
||||
defer file.Close()
|
||||
|
||||
// 如果目标目录不存在,创建
|
||||
|
|
|
@ -34,7 +34,7 @@ func TestHandler_Put(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := handler.Put(ctx, testCase.file, testCase.dst)
|
||||
err := handler.Put(ctx, testCase.file, testCase.dst, 15)
|
||||
if testCase.err {
|
||||
asserts.Error(err)
|
||||
} else {
|
||||
|
|
38
pkg/filesystem/qiniu/file.go
Normal file
38
pkg/filesystem/qiniu/file.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package qiniu
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// FileStream 用户传来的文件
|
||||
type FileStream struct {
|
||||
File io.ReadCloser
|
||||
Size uint64
|
||||
VirtualPath string
|
||||
Name string
|
||||
MIMEType string
|
||||
}
|
||||
|
||||
func (file FileStream) Read(p []byte) (n int, err error) {
|
||||
return file.File.Read(p)
|
||||
}
|
||||
|
||||
func (file FileStream) GetMIMEType() string {
|
||||
return file.MIMEType
|
||||
}
|
||||
|
||||
func (file FileStream) GetSize() uint64 {
|
||||
return file.Size
|
||||
}
|
||||
|
||||
func (file FileStream) Close() error {
|
||||
return file.File.Close()
|
||||
}
|
||||
|
||||
func (file FileStream) GetFileName() string {
|
||||
return file.Name
|
||||
}
|
||||
|
||||
func (file FileStream) GetVirtualPath() string {
|
||||
return file.VirtualPath
|
||||
}
|
63
pkg/filesystem/qiniu/handller.go
Normal file
63
pkg/filesystem/qiniu/handller.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package qiniu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/v7/auth"
|
||||
"github.com/qiniu/api.v7/v7/storage"
|
||||
)
|
||||
|
||||
// Handler 本地策略适配器
|
||||
type Handler struct{}
|
||||
|
||||
// Put 将文件流保存到指定目录
|
||||
func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error {
|
||||
// 凭证生成
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: "cloudrevetest",
|
||||
}
|
||||
mac := auth.New("YNzTBBpDUq4EEiFV0-vyJCZCJ0LvUEI0_WvxtEXE", "Clm9d9M2CH7pZ8vm049ZlGZStQxrRQVRTjU_T5_0")
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
cfg := storage.Config{}
|
||||
// 空间对应的机房
|
||||
cfg.Zone = &storage.ZoneHuadong
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
ret := storage.PutRet{}
|
||||
putExtra := storage.PutExtra{
|
||||
Params: map[string]string{},
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
err := formUploader.Put(ctx, &ret, upToken, dst, file, int64(size), &putExtra)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fmt.Println(ret.Key, ret.Hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除一个或多个文件,
|
||||
// 返回已删除的文件,及遇到的最后一个错误
|
||||
func (handler Handler) Delete(ctx context.Context, files []string) ([]string, error) {
|
||||
deleted := make([]string, 0, len(files))
|
||||
var retErr error
|
||||
|
||||
for _, value := range files {
|
||||
err := os.Remove(value)
|
||||
if err == nil {
|
||||
deleted = append(deleted, value)
|
||||
} else {
|
||||
util.Log().Warning("无法删除文件,%s", err)
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return deleted, retErr
|
||||
}
|
|
@ -18,11 +18,9 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|||
ctx = context.WithValue(ctx, FileHeaderCtx, file)
|
||||
|
||||
// 上传前的钩子
|
||||
if fs.BeforeUpload != nil {
|
||||
err = fs.BeforeUpload(ctx, fs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fs.Trigger(ctx, fs.BeforeUpload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成文件名和路径
|
||||
|
@ -32,27 +30,24 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|||
go fs.CancelUpload(ctx, savePath, file)
|
||||
|
||||
// 保存文件
|
||||
err = fs.Handler.Put(ctx, file, savePath)
|
||||
err = fs.Handler.Put(ctx, file, savePath, file.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 上传完成后的钩子
|
||||
if fs.AfterUpload != nil {
|
||||
ctx = context.WithValue(ctx, SavePathCtx, savePath)
|
||||
err = fs.AfterUpload(ctx, fs)
|
||||
ctx = context.WithValue(ctx, SavePathCtx, savePath)
|
||||
err = fs.Trigger(ctx, fs.AfterUpload)
|
||||
|
||||
if err != nil {
|
||||
// 上传完成后续处理失败
|
||||
if fs.AfterValidateFailed != nil {
|
||||
followUpErr := fs.AfterValidateFailed(ctx, fs)
|
||||
// 失败后再失败...
|
||||
if followUpErr != nil {
|
||||
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||
}
|
||||
}
|
||||
return err
|
||||
if err != nil {
|
||||
// 上传完成后续处理失败
|
||||
followUpErr := fs.Trigger(ctx, fs.AfterValidateFailed)
|
||||
// 失败后再失败...
|
||||
if followUpErr != nil {
|
||||
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
util.Log().Info("新文件上传:%s , 大小:%d, 上传者:%s", file.GetFileName(), file.GetSize(), fs.User.Nick)
|
||||
|
@ -88,7 +83,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe
|
|||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, SavePathCtx, path)
|
||||
err := fs.AfterUploadCanceled(ctx, fs)
|
||||
err := fs.Trigger(ctx, fs.AfterUploadCanceled)
|
||||
if err != nil {
|
||||
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ type FileHeaderMock struct {
|
|||
testMock.Mock
|
||||
}
|
||||
|
||||
func (m FileHeaderMock) Put(ctx context.Context, file io.ReadCloser, dst string) error {
|
||||
func (m FileHeaderMock) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error {
|
||||
args := m.Called(ctx, file, dst)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
@ -61,9 +61,9 @@ func TestFileSystem_Upload(t *testing.T) {
|
|||
asserts.NoError(err)
|
||||
|
||||
// BeforeUpload 返回错误
|
||||
fs.BeforeUpload = func(ctx context.Context, fs *FileSystem) error {
|
||||
fs.Use("BeforeUpload", func(ctx context.Context, fs *FileSystem) error {
|
||||
return errors.New("error")
|
||||
}
|
||||
})
|
||||
err = fs.Upload(ctx, file)
|
||||
asserts.Error(err)
|
||||
fs.BeforeUpload = nil
|
||||
|
|
|
@ -55,10 +55,13 @@ func FileUploadStream(c *gin.Context) {
|
|||
}
|
||||
|
||||
// 给文件系统分配钩子
|
||||
fs.BeforeUpload = filesystem.GenericBeforeUpload
|
||||
fs.AfterUploadCanceled = filesystem.GenericAfterUploadCanceled
|
||||
fs.AfterUpload = filesystem.GenericAfterUpload
|
||||
fs.AfterValidateFailed = filesystem.GenericAfterUploadCanceled
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateFile)
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateCapacity)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
|
||||
fs.Use("AfterUpload", filesystem.GenericAfterUpload)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
|
||||
|
||||
// 执行上传
|
||||
uploadCtx := context.WithValue(ctx, filesystem.GinCtx, c)
|
||||
|
|
Loading…
Add table
Reference in a new issue