From f7ecbce64c875e94ab722aa193606c7f425d5524 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Fri, 17 Jan 2020 14:05:51 +0800 Subject: [PATCH] Feat: generate upload credentials for upyun --- models/policy.go | 2 + pkg/filesystem/filesystem.go | 6 ++ pkg/filesystem/fsctx/context.go | 2 + pkg/filesystem/upload.go | 1 + pkg/filesystem/upyun/handller.go | 124 +++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 pkg/filesystem/upyun/handller.go diff --git a/models/policy.go b/models/policy.go index d58502e..12f003f 100644 --- a/models/policy.go +++ b/models/policy.go @@ -182,6 +182,8 @@ func (policy *Policy) GetUploadURL() string { controller, _ = url.Parse("/api/v3/slave/upload") case "oss": return policy.BaseURL + case "upyun": + return "http://v0.api.upyun.com/" + policy.BucketName default: controller, _ = url.Parse("") } diff --git a/pkg/filesystem/filesystem.go b/pkg/filesystem/filesystem.go index 0b2a26e..7410fd8 100644 --- a/pkg/filesystem/filesystem.go +++ b/pkg/filesystem/filesystem.go @@ -10,6 +10,7 @@ import ( "github.com/HFO4/cloudreve/pkg/filesystem/qiniu" "github.com/HFO4/cloudreve/pkg/filesystem/remote" "github.com/HFO4/cloudreve/pkg/filesystem/response" + "github.com/HFO4/cloudreve/pkg/filesystem/upyun" "github.com/HFO4/cloudreve/pkg/request" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" @@ -175,6 +176,11 @@ func (fs *FileSystem) dispatchHandler() error { HTTPClient: request.HTTPClient{}, } return nil + case "upyun": + fs.Handler = upyun.Driver{ + Policy: currentPolicy, + } + return nil default: return ErrUnknownPolicyType } diff --git a/pkg/filesystem/fsctx/context.go b/pkg/filesystem/fsctx/context.go index 24f80d7..28aef97 100644 --- a/pkg/filesystem/fsctx/context.go +++ b/pkg/filesystem/fsctx/context.go @@ -23,4 +23,6 @@ const ( ThumbSizeCtx // OriginSourceNameCtx 原始原文件名 OriginSourceNameCtx + // FileSizeCtx 文件大小 + FileSizeCtx ) diff --git a/pkg/filesystem/upload.go b/pkg/filesystem/upload.go index afdb7ca..6eec980 100644 --- a/pkg/filesystem/upload.go +++ b/pkg/filesystem/upload.go @@ -151,6 +151,7 @@ func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint if fs.User.Policy.IsPathGenerateNeeded() { ctx = context.WithValue(ctx, fsctx.SavePathCtx, fs.GenerateSavePath(ctx, local.FileStream{})) } + ctx = context.WithValue(ctx, fsctx.FileSizeCtx, size) // 获取上传凭证 callbackKey := util.RandStringRunes(32) diff --git a/pkg/filesystem/upyun/handller.go b/pkg/filesystem/upyun/handller.go new file mode 100644 index 0000000..a948aa5 --- /dev/null +++ b/pkg/filesystem/upyun/handller.go @@ -0,0 +1,124 @@ +package upyun + +import ( + "context" + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" + "github.com/HFO4/cloudreve/pkg/filesystem/response" + "github.com/HFO4/cloudreve/pkg/serializer" + "io" + "net/url" + "strings" + "time" +) + +// UploadPolicy 又拍云上传策略 +type UploadPolicy struct { + Bucket string `json:"bucket"` + SaveKey string `json:"save-key"` + Expiration int64 `json:"expiration"` + CallbackURL string `json:"notify-url"` + ContentLength uint64 `json:"content-length"` + ContentLengthRange string `json:"content-length-range"` + AllowFileType string `json:"allow-file-type,omitempty"` +} + +// Driver 又拍云策略适配器 +type Driver struct { + Policy *model.Policy +} + +// Get 获取文件 +func (handler Driver) Get(ctx context.Context, path string) (response.RSCloser, error) { + return nil, errors.New("未实现") +} + +// Put 将文件流保存到指定目录 +func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error { + return errors.New("未实现") +} + +// Delete 删除一个或多个文件, +// 返回未删除的文件,及遇到的最后一个错误 +func (handler Driver) Delete(ctx context.Context, files []string) ([]string, error) { + return []string{}, errors.New("未实现") +} + +// Thumb 获取文件缩略图 +func (handler Driver) Thumb(ctx context.Context, path string) (*response.ContentResponse, error) { + return nil, errors.New("未实现") +} + +// Source 获取外链URL +func (handler Driver) Source( + ctx context.Context, + path string, + baseURL url.URL, + ttl int64, + isDownload bool, + speed int, +) (string, error) { + return "", errors.New("未实现") +} + +// Token 获取上传策略和认证Token +func (handler Driver) Token(ctx context.Context, TTL int64, key string) (serializer.UploadCredential, error) { + // 读取上下文中生成的存储路径和文件大小 + savePath, ok := ctx.Value(fsctx.SavePathCtx).(string) + if !ok { + return serializer.UploadCredential{}, errors.New("无法获取存储路径") + } + fileSize, ok := ctx.Value(fsctx.FileSizeCtx).(uint64) + if !ok { + return serializer.UploadCredential{}, errors.New("无法获取文件大小") + } + + // 生成回调地址 + siteURL := model.GetSiteURL() + apiBaseURI, _ := url.Parse("/api/v3/callback/upyun/" + key) + apiURL := siteURL.ResolveReference(apiBaseURI) + + // 上传策略 + putPolicy := UploadPolicy{ + Bucket: handler.Policy.BucketName, + // TODO escape + SaveKey: savePath, + Expiration: time.Now().Add(time.Duration(TTL) * time.Second).Unix(), + CallbackURL: apiURL.String(), + ContentLength: fileSize, + ContentLengthRange: fmt.Sprintf("0,%d", fileSize), + AllowFileType: strings.Join(handler.Policy.OptionsSerialized.FileType, ","), + } + + // 生成上传凭证 + return handler.getUploadCredential(ctx, putPolicy) +} + +func (handler Driver) getUploadCredential(ctx context.Context, policy UploadPolicy) (serializer.UploadCredential, error) { + // 生成上传策略 + policyJSON, err := json.Marshal(policy) + if err != nil { + return serializer.UploadCredential{}, err + } + policyEncoded := base64.StdEncoding.EncodeToString(policyJSON) + + // 生成签名 + password := fmt.Sprintf("%x", md5.Sum([]byte(handler.Policy.SecretKey))) + mac := hmac.New(sha1.New, []byte(password)) + elements := []string{"POST", "/" + handler.Policy.BucketName, policyEncoded} + value := strings.Join(elements, "&") + mac.Write([]byte(value)) + signStr := base64.StdEncoding.EncodeToString((mac.Sum(nil))) + + return serializer.UploadCredential{ + Policy: policyEncoded, + Token: fmt.Sprintf("UPYUN %s:%s", handler.Policy.AccessKey, signStr), + }, nil +}