diff --git a/pkg/filesystem/driver/local/handler.go b/pkg/filesystem/driver/local/handler.go index 7f00216..7985038 100644 --- a/pkg/filesystem/driver/local/handler.go +++ b/pkg/filesystem/driver/local/handler.go @@ -218,5 +218,6 @@ func (handler Driver) Source( func (handler Driver) Token(ctx context.Context, ttl int64, uploadSession *serializer.UploadSession, file fsctx.FileHeader) (serializer.UploadCredential, error) { return serializer.UploadCredential{ SessionID: uploadSession.Key, + ChunkSize: handler.Policy.OptionsSerialized.ChunkSize, }, nil } diff --git a/pkg/filesystem/upload.go b/pkg/filesystem/upload.go index 15e5146..98a3d79 100644 --- a/pkg/filesystem/upload.go +++ b/pkg/filesystem/upload.go @@ -4,6 +4,7 @@ import ( "context" "os" "path" + "time" model "github.com/cloudreve/Cloudreve/v3/models" "github.com/cloudreve/Cloudreve/v3/pkg/cache" @@ -167,6 +168,7 @@ func (fs *FileSystem) CreateUploadSession(ctx context.Context, file *fsctx.FileS fs.Use("BeforeUpload", HookValidateFile) fs.Use("AfterUpload", HookClearFileHeaderSize) + // TODO: 只有本机策略才添加文件 fs.Use("AfterUpload", GenericAfterUpload) if err := fs.Upload(ctx, file); err != nil { return nil, err @@ -200,6 +202,9 @@ func (fs *FileSystem) CreateUploadSession(ctx context.Context, file *fsctx.FileS return nil, err } + // 补全上传凭证其他信息 + credential.Expires = time.Now().Add(time.Duration(callBackSessionTTL) * time.Second).Unix() + return &credential, nil } diff --git a/pkg/serializer/explorer.go b/pkg/serializer/explorer.go index 9820895..905f2d2 100644 --- a/pkg/serializer/explorer.go +++ b/pkg/serializer/explorer.go @@ -46,12 +46,11 @@ type Object struct { // PolicySummary 用于前端组件使用的存储策略概况 type PolicySummary struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - MaxSize uint64 `json:"max_size"` - FileType []string `json:"file_type"` - ChunkSize uint64 `json:"chunk_size"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + MaxSize uint64 `json:"max_size"` + FileType []string `json:"file_type"` } // BuildObjectList 构建列目录响应 @@ -66,12 +65,11 @@ func BuildObjectList(parent uint, objects []Object, policy *model.Policy) Object if policy != nil { res.Policy = &PolicySummary{ - ID: hashid.HashID(policy.ID, hashid.PolicyID), - Name: policy.Name, - Type: policy.Type, - MaxSize: policy.MaxSize, - FileType: policy.OptionsSerialized.FileType, - ChunkSize: policy.OptionsSerialized.ChunkSize, + ID: hashid.HashID(policy.ID, hashid.PolicyID), + Name: policy.Name, + Type: policy.Type, + MaxSize: policy.MaxSize, + FileType: policy.OptionsSerialized.FileType, } } diff --git a/pkg/serializer/upload.go b/pkg/serializer/upload.go index 87fd8e1..09547bc 100644 --- a/pkg/serializer/upload.go +++ b/pkg/serializer/upload.go @@ -20,6 +20,8 @@ type UploadPolicy struct { // UploadCredential 返回给客户端的上传凭证 type UploadCredential struct { SessionID string `json:"sessionID"` + ChunkSize uint64 `json:"chunkSize"` // 分块大小,0 为部分快 + Expires int64 `json:"expires"` // 上传凭证过期时间, Unix 时间戳 Token string `json:"token"` Policy string `json:"policy"` @@ -39,7 +41,7 @@ type UploadSession struct { Name string // 文件名 Size uint64 // 文件大小 SavePath string // 物理存储路径,包含物理文件名 - ChunkSize uint64 // 分块大小,0 为部分快 + ChunkSize uint64 // 分块大小,0 为不分快 LastModified *time.Time // 可选的文件最后修改日期 } diff --git a/pkg/serializer/user.go b/pkg/serializer/user.go index faf82a4..fcbc4db 100644 --- a/pkg/serializer/user.go +++ b/pkg/serializer/user.go @@ -31,14 +31,6 @@ type User struct { Tags []tag `json:"tags"` } -type policy struct { - SaveType string `json:"saveType"` - MaxSize string `json:"maxSize"` - AllowedType []string `json:"allowedType"` - UploadURL string `json:"upUrl"` - AllowGetSource bool `json:"allowSource"` -} - type group struct { ID uint `json:"id"` Name string `json:"name"` diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 23d5f64..83b0c51 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -3,15 +3,10 @@ package controllers import ( "context" "fmt" - "github.com/cloudreve/Cloudreve/v3/pkg/request" "net/http" - "net/url" - "strconv" - "sync" "github.com/cloudreve/Cloudreve/v3/pkg/conf" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" - "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" "github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/service/explorer" "github.com/gin-gonic/gin" @@ -285,72 +280,65 @@ func PutContent(c *gin.Context) { } } -// FileUploadStream 本地策略流式上传 -func FileUploadStream(c *gin.Context) { +// FileUpload 本地策略文件上传 +func FileUpload(c *gin.Context) { // 创建上下文 ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // 取得文件大小 - fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64) - if err != nil { - c.JSON(200, ErrorResponse(err)) - return - } - - // 解码文件名和路径 - fileName, err := url.QueryUnescape(c.Request.Header.Get("X-Cr-FileName")) - filePath, err := url.QueryUnescape(c.Request.Header.Get("X-Cr-Path")) - if err != nil { + var service explorer.UploadService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Upload(ctx, c) + c.JSON(200, res) + } else { c.JSON(200, ErrorResponse(err)) - return - } - - fileData := fsctx.FileStream{ - MIMEType: c.Request.Header.Get("Content-Type"), - File: c.Request.Body, - Size: fileSize, - Name: fileName, - VirtualPath: filePath, - Mode: fsctx.Create, - } - - // 创建文件系统 - fs, err := filesystem.NewFileSystemFromContext(c) - if err != nil { - c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)) - return - } - - // 非可用策略时拒绝上传 - if !fs.Policy.IsTransitUpload(fileSize) { - request.BlackHole(c.Request.Body) - c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, "当前存储策略无法使用", nil)) - return } - // 给文件系统分配钩子 - 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) - fs.Use("AfterUploadFailed", filesystem.HookGiveBackCapacity) - - // 执行上传 - ctx = context.WithValue(ctx, fsctx.ValidateCapacityOnceCtx, &sync.Once{}) - uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) - err = fs.Upload(uploadCtx, &fileData) - if err != nil { - c.JSON(200, serializer.Err(serializer.CodeUploadFailed, err.Error(), err)) - return - } - - c.JSON(200, serializer.Response{ - Code: 0, - }) + //fileData := fsctx.FileStream{ + // MIMEType: c.Request.Header.Get("Content-Type"), + // File: c.Request.Body, + // Size: fileSize, + // Name: fileName, + // VirtualPath: filePath, + // Mode: fsctx.Create, + //} + // + //// 创建文件系统 + //fs, err := filesystem.NewFileSystemFromContext(c) + //if err != nil { + // c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)) + // return + //} + // + //// 非可用策略时拒绝上传 + //if !fs.Policy.IsTransitUpload(fileSize) { + // request.BlackHole(c.Request.Body) + // c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, "当前存储策略无法使用", nil)) + // return + //} + // + //// 给文件系统分配钩子 + //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) + //fs.Use("AfterUploadFailed", filesystem.HookGiveBackCapacity) + // + //// 执行上传 + //ctx = context.WithValue(ctx, fsctx.ValidateCapacityOnceCtx, &sync.Once{}) + //uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) + //err = fs.Upload(uploadCtx, &fileData) + //if err != nil { + // c.JSON(200, serializer.Err(serializer.CodeUploadFailed, err.Error(), err)) + // return + //} + // + //c.JSON(200, serializer.Response{ + // Code: 0, + //}) } // GetUploadCredential 创建上传会话 diff --git a/routers/router.go b/routers/router.go index dd64545..10e496d 100644 --- a/routers/router.go +++ b/routers/router.go @@ -504,8 +504,10 @@ func InitMasterRouter() *gin.Engine { // 文件 file := auth.Group("file", middleware.HashID(hashid.FileID)) { + // 文件上传 + file.POST("upload/:sessionId/:index", controllers.FileUpload) // 创建上传会话 - file.PUT("upload/session", controllers.GetUploadCredential) + file.PUT("upload", controllers.GetUploadCredential) // 更新文件 file.PUT("update/:id", controllers.PutContent) // 创建空白文件 diff --git a/service/explorer/upload.go b/service/explorer/upload.go index 282a8cc..679d126 100644 --- a/service/explorer/upload.go +++ b/service/explorer/upload.go @@ -2,6 +2,7 @@ package explorer import ( "context" + "github.com/cloudreve/Cloudreve/v3/pkg/request" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" @@ -57,3 +58,17 @@ func (service *UploadSessionService) Create(ctx context.Context, c *gin.Context) Data: credential, } } + +// UploadService 本机策略上传服务 +type UploadService struct { + ID string `uri:"sessionId" binding:"required"` + Index int `uri:"index"` +} + +// Upload 处理本机文件分片上传 +func (service *UploadService) Upload(ctx context.Context, c *gin.Context) serializer.Response { + request.BlackHole(c.Request.Body) + return serializer.Response{ + Code: 0, + } +}