mirror of https://github.com/cloudreve/Cloudreve
Feat: file uploading in slave mode
parent
6470340104
commit
132c7a8fcb
|
@ -129,7 +129,7 @@ func (policy *Policy) GenerateFileName(uid uint, origin string) string {
|
||||||
case "qiniu":
|
case "qiniu":
|
||||||
// 七牛会将$(fname)自动替换为原始文件名
|
// 七牛会将$(fname)自动替换为原始文件名
|
||||||
replaceTable["{originname}"] = "$(fname)"
|
replaceTable["{originname}"] = "$(fname)"
|
||||||
case "local":
|
case "local", "remote":
|
||||||
replaceTable["{originname}"] = origin
|
replaceTable["{originname}"] = origin
|
||||||
case "oss":
|
case "oss":
|
||||||
// OSS会将${filename}自动替换为原始文件名
|
// OSS会将${filename}自动替换为原始文件名
|
||||||
|
|
|
@ -54,7 +54,6 @@ func HookIsFileExist(ctx context.Context, fs *FileSystem) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookSlaveUploadValidate Slave模式下对文件上传的一系列验证
|
// HookSlaveUploadValidate Slave模式下对文件上传的一系列验证
|
||||||
// TODO 测试
|
|
||||||
func HookSlaveUploadValidate(ctx context.Context, fs *FileSystem) error {
|
func HookSlaveUploadValidate(ctx context.Context, fs *FileSystem) error {
|
||||||
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||||
policy := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy)
|
policy := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy)
|
||||||
|
@ -209,6 +208,22 @@ func GenericAfterUpdate(ctx context.Context, fs *FileSystem) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SlaveAfterUpload Slave模式下上传完成钩子
|
||||||
|
// TODO 测试
|
||||||
|
func SlaveAfterUpload(ctx context.Context, fs *FileSystem) error {
|
||||||
|
fileHeader := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||||
|
// 构造一个model.File,用于生成缩略图
|
||||||
|
file := model.File{
|
||||||
|
Name: fileHeader.GetFileName(),
|
||||||
|
SourceName: ctx.Value(fsctx.SavePathCtx).(string),
|
||||||
|
}
|
||||||
|
fs.GenerateThumbnail(ctx, &file)
|
||||||
|
|
||||||
|
// TODO 发送回调请求
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GenericAfterUpload 文件上传完成后,包含数据库操作
|
// GenericAfterUpload 文件上传完成后,包含数据库操作
|
||||||
func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
||||||
// 文件存放的虚拟路径
|
// 文件存放的虚拟路径
|
||||||
|
|
|
@ -72,7 +72,11 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新文件的图像信息
|
// 更新文件的图像信息
|
||||||
err = file.UpdatePicInfo(fmt.Sprintf("%d,%d", w, h))
|
if file.Model.ID > 0 {
|
||||||
|
err = file.UpdatePicInfo(fmt.Sprintf("%d,%d", w, h))
|
||||||
|
} else {
|
||||||
|
file.PicInfo = fmt.Sprintf("%d,%d", w, h)
|
||||||
|
}
|
||||||
|
|
||||||
// 失败时删除缩略图文件
|
// 失败时删除缩略图文件
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -94,6 +94,26 @@ func TestFileSystem_GenerateThumbnail(t *testing.T) {
|
||||||
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||||
mock.ExpectCommit()
|
mock.ExpectCommit()
|
||||||
|
|
||||||
|
file := &model.File{
|
||||||
|
Model: gorm.Model{ID: 1},
|
||||||
|
Name: "123.jpg",
|
||||||
|
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.GenerateThumbnail(ctx, file)
|
||||||
|
asserts.NoError(mock.ExpectationsWereMet())
|
||||||
|
testHandler.AssertExpectations(t)
|
||||||
|
asserts.True(util.Exists("TestFileSystem_GenerateThumbnail.jpeg" + conf.ThumbConfig.FileSuffix))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功,不进行数据库更新
|
||||||
|
{
|
||||||
|
src := CreateTestImage()
|
||||||
|
testHandler := new(FileHeaderMock)
|
||||||
|
testHandler.On("Get", testMock.Anything, "TestFileSystem_GenerateThumbnail.jpeg").Return(src, nil)
|
||||||
|
fs.Handler = testHandler
|
||||||
|
|
||||||
file := &model.File{
|
file := &model.File{
|
||||||
Name: "123.jpg",
|
Name: "123.jpg",
|
||||||
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
||||||
|
@ -119,6 +139,7 @@ func TestFileSystem_GenerateThumbnail(t *testing.T) {
|
||||||
mock.ExpectRollback()
|
mock.ExpectRollback()
|
||||||
|
|
||||||
file := &model.File{
|
file := &model.File{
|
||||||
|
Model: gorm.Model{ID: 1},
|
||||||
Name: "123.jpg",
|
Name: "123.jpg",
|
||||||
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
||||||
}
|
}
|
||||||
|
@ -132,6 +153,7 @@ func TestFileSystem_GenerateThumbnail(t *testing.T) {
|
||||||
// 不能生成缩略图
|
// 不能生成缩略图
|
||||||
{
|
{
|
||||||
file := &model.File{
|
file := &model.File{
|
||||||
|
Model: gorm.Model{ID: 1},
|
||||||
Name: "123.123",
|
Name: "123.123",
|
||||||
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -83,16 +84,24 @@ func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) str
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 匿名文件系统使用空上传策略生成路径
|
// 匿名文件系统尝试根据上下文中的上传策略生成路径
|
||||||
nilPolicy := model.Policy{}
|
var anonymousPolicy model.Policy
|
||||||
|
if policy, ok := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy); ok {
|
||||||
|
anonymousPolicy = model.Policy{
|
||||||
|
Type: "remote",
|
||||||
|
AutoRename: policy.AutoRename,
|
||||||
|
DirNameRule: policy.SavePath,
|
||||||
|
FileNameRule: policy.FileName,
|
||||||
|
}
|
||||||
|
}
|
||||||
return filepath.Join(
|
return filepath.Join(
|
||||||
nilPolicy.GeneratePath(
|
anonymousPolicy.GeneratePath(
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
nilPolicy.GenerateFileName(
|
anonymousPolicy.GenerateFileName(
|
||||||
0,
|
0,
|
||||||
"",
|
file.GetFileName(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/response"
|
"github.com/HFO4/cloudreve/pkg/filesystem/response"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -139,3 +140,22 @@ func TestFileSystem_Upload(t *testing.T) {
|
||||||
testHandller2.AssertExpectations(t)
|
testHandller2.AssertExpectations(t)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileSystem_GenerateSavePath_Anonymous(t *testing.T) {
|
||||||
|
asserts := assert.New(t)
|
||||||
|
fs := FileSystem{User: &model.User{}}
|
||||||
|
ctx := context.WithValue(
|
||||||
|
context.Background(),
|
||||||
|
fsctx.UploadPolicyCtx,
|
||||||
|
serializer.UploadPolicy{
|
||||||
|
SavePath: "{randomkey16}",
|
||||||
|
AutoRename: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
savePath := fs.GenerateSavePath(ctx, local.FileStream{
|
||||||
|
Name: "test.test",
|
||||||
|
})
|
||||||
|
asserts.Len(savePath, 26)
|
||||||
|
asserts.Contains(savePath, "test.test")
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
// UploadPolicy slave模式下传递的上传策略
|
// UploadPolicy slave模式下传递的上传策略
|
||||||
type UploadPolicy struct {
|
type UploadPolicy struct {
|
||||||
SavePath string `json:"save_path"`
|
SavePath string `json:"save_path"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
AutoRename bool `json:"auto_rename"`
|
||||||
MaxSize uint64 `json:"max_size"`
|
MaxSize uint64 `json:"max_size"`
|
||||||
AllowedExtension []string `json:"allowed_extension"`
|
AllowedExtension []string `json:"allowed_extension"`
|
||||||
CallbackURL string `json:"callback_url"`
|
CallbackURL string `json:"callback_url"`
|
||||||
|
|
|
@ -24,19 +24,22 @@ func SlaveUpload(c *gin.Context) {
|
||||||
c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err))
|
c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fs.Handler = local.Handler{}
|
||||||
|
|
||||||
// 从请求中取得上传策略
|
// 从请求中取得上传策略
|
||||||
uploadPolicyRaw := c.GetHeader("X-Policy")
|
uploadPolicyRaw := c.GetHeader("X-Policy")
|
||||||
if uploadPolicyRaw == "" {
|
if uploadPolicyRaw == "" {
|
||||||
c.JSON(200, serializer.ParamErr("未指定上传策略", nil))
|
c.JSON(200, serializer.ParamErr("未指定上传策略", nil))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析上传策略
|
// 解析上传策略
|
||||||
uploadPolicy, err := serializer.DecodeUploadPolicy(uploadPolicyRaw)
|
uploadPolicy, err := serializer.DecodeUploadPolicy(uploadPolicyRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(200, serializer.ParamErr("上传策略格式有误", err))
|
c.JSON(200, serializer.ParamErr("上传策略格式有误", err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, fsctx.UploadPolicyCtx, uploadPolicy)
|
ctx = context.WithValue(ctx, fsctx.UploadPolicyCtx, *uploadPolicy)
|
||||||
|
|
||||||
// 取得文件大小
|
// 取得文件大小
|
||||||
fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64)
|
fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64)
|
||||||
|
@ -62,6 +65,7 @@ func SlaveUpload(c *gin.Context) {
|
||||||
// 给文件系统分配钩子
|
// 给文件系统分配钩子
|
||||||
fs.Use("BeforeUpload", filesystem.HookSlaveUploadValidate)
|
fs.Use("BeforeUpload", filesystem.HookSlaveUploadValidate)
|
||||||
fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
|
fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
|
||||||
|
fs.Use("AfterUpload", filesystem.SlaveAfterUpload)
|
||||||
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
|
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
|
||||||
|
|
||||||
// 执行上传
|
// 执行上传
|
||||||
|
|
Loading…
Reference in New Issue