Modify: split hooks into small modules

pull/247/head
HFO4 5 years ago
parent 451bdb4ee1
commit 0cb80f69f5

@ -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 {

@ -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
}

@ -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…
Cancel
Save