mirror of
https://github.com/halejohn/Cloudreve.git
synced 2026-01-26 09:34:57 +08:00
Feat: client-upload file in oss
This commit is contained in:
@@ -414,6 +414,35 @@ func TestHookClearFileSize(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestHookUpdateSourceName(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{ID: 1},
|
||||
}}
|
||||
|
||||
// 成功
|
||||
{
|
||||
originFile := model.File{
|
||||
Model: gorm.Model{ID: 1},
|
||||
SourceName: "new.txt",
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), fsctx.FileModelCtx, originFile)
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").WithArgs("new.txt", sqlmock.AnyArg(), 1).WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
err := HookUpdateSourceName(ctx, fs)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.NoError(err)
|
||||
}
|
||||
|
||||
// 上下文错误
|
||||
{
|
||||
ctx := context.Background()
|
||||
err := HookUpdateSourceName(ctx, fs)
|
||||
asserts.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericAfterUpdate(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
|
||||
@@ -2,17 +2,64 @@ package oss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"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"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UploadPolicy 阿里云OSS上传策略
|
||||
type UploadPolicy struct {
|
||||
Expiration string `json:"expiration"`
|
||||
Conditions []interface{} `json:"conditions"`
|
||||
}
|
||||
|
||||
// CallbackPolicy 回调策略
|
||||
type CallbackPolicy struct {
|
||||
CallbackURL string `json:"callbackUrl"`
|
||||
CallbackBody string `json:"callbackBody"`
|
||||
CallbackBodyType string `json:"callbackBodyType"`
|
||||
}
|
||||
|
||||
// Handler 阿里云OSS策略适配器
|
||||
type Handler struct {
|
||||
Policy *model.Policy
|
||||
client *oss.Client
|
||||
bucket *oss.Bucket
|
||||
}
|
||||
|
||||
// InitOSSClient 初始化OSS鉴权客户端
|
||||
func (handler *Handler) InitOSSClient() error {
|
||||
if handler.Policy == nil {
|
||||
return errors.New("存储策略为空")
|
||||
}
|
||||
|
||||
// 初始化客户端
|
||||
client, err := oss.New(handler.Policy.Server, handler.Policy.AccessKey, handler.Policy.SecretKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handler.client = client
|
||||
|
||||
// 初始化存储桶
|
||||
bucket, err := client.Bucket(handler.Policy.BucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handler.bucket = bucket
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取文件
|
||||
@@ -50,5 +97,74 @@ func (handler Handler) Source(
|
||||
|
||||
// Token 获取上传策略和认证Token
|
||||
func (handler Handler) Token(ctx context.Context, TTL int64, key string) (serializer.UploadCredential, error) {
|
||||
return serializer.UploadCredential{}, errors.New("未实现")
|
||||
// 读取上下文中生成的存储路径
|
||||
savePath, ok := ctx.Value(fsctx.SavePathCtx).(string)
|
||||
if !ok {
|
||||
return serializer.UploadCredential{}, errors.New("无法获取存储路径")
|
||||
}
|
||||
|
||||
// 生成回调地址
|
||||
siteURL := model.GetSiteURL()
|
||||
apiBaseURI, _ := url.Parse("/api/v3/callback/oss/" + key)
|
||||
apiURL := siteURL.ResolveReference(apiBaseURI)
|
||||
|
||||
// 回调策略
|
||||
callbackPolicy := CallbackPolicy{
|
||||
CallbackURL: apiURL.String(),
|
||||
CallbackBody: `{"name":${x:fname},"source_name":${object},"size":${size},"pic_info":"${imageInfo.width},${imageInfo.height}"}`,
|
||||
CallbackBodyType: "application/json",
|
||||
}
|
||||
|
||||
// 上传策略
|
||||
postPolicy := UploadPolicy{
|
||||
Expiration: time.Now().UTC().Add(time.Duration(TTL) * time.Second).Format(time.RFC3339),
|
||||
Conditions: []interface{}{
|
||||
map[string]string{"bucket": handler.Policy.BucketName},
|
||||
[]string{"starts-with", "$key", path.Dir(savePath)},
|
||||
[]interface{}{"content-length-range", 0, handler.Policy.MaxSize},
|
||||
},
|
||||
}
|
||||
|
||||
return handler.getUploadCredential(ctx, postPolicy, callbackPolicy, TTL)
|
||||
}
|
||||
|
||||
func (handler Handler) getUploadCredential(ctx context.Context, policy UploadPolicy, callback CallbackPolicy, TTL int64) (serializer.UploadCredential, error) {
|
||||
// 读取上下文中生成的存储路径
|
||||
savePath, ok := ctx.Value(fsctx.SavePathCtx).(string)
|
||||
if !ok {
|
||||
return serializer.UploadCredential{}, errors.New("无法获取存储路径")
|
||||
}
|
||||
|
||||
// 处理回调策略
|
||||
callbackPolicyEncoded := ""
|
||||
if callback.CallbackURL != "" {
|
||||
callbackPolicyJSON, err := json.Marshal(callback)
|
||||
if err != nil {
|
||||
return serializer.UploadCredential{}, err
|
||||
}
|
||||
callbackPolicyEncoded = base64.StdEncoding.EncodeToString(callbackPolicyJSON)
|
||||
policy.Conditions = append(policy.Conditions, map[string]string{"callback": callbackPolicyEncoded})
|
||||
}
|
||||
|
||||
// 编码上传策略
|
||||
policyJSON, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return serializer.UploadCredential{}, err
|
||||
}
|
||||
policyEncoded := base64.StdEncoding.EncodeToString(policyJSON)
|
||||
|
||||
// 签名上传策略
|
||||
hmacSign := hmac.New(sha1.New, []byte(handler.Policy.SecretKey))
|
||||
_, err = io.WriteString(hmacSign, policyEncoded)
|
||||
if err != nil {
|
||||
return serializer.UploadCredential{}, err
|
||||
}
|
||||
signature := base64.StdEncoding.EncodeToString(hmacSign.Sum(nil))
|
||||
|
||||
return serializer.UploadCredential{
|
||||
Policy: fmt.Sprintf("%s:%s", callbackPolicyEncoded, policyEncoded),
|
||||
Path: savePath,
|
||||
AccessKey: handler.Policy.AccessKey,
|
||||
Token: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user