github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/filesystem/driver/remote/handler.go (about)

     1  package remote
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net/url"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	model "github.com/cloudreve/Cloudreve/v3/models"
    16  	"github.com/cloudreve/Cloudreve/v3/pkg/auth"
    17  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver"
    18  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
    19  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
    20  	"github.com/cloudreve/Cloudreve/v3/pkg/request"
    21  	"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
    22  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
    23  )
    24  
    25  // Driver 远程存储策略适配器
    26  type Driver struct {
    27  	Client       request.Client
    28  	Policy       *model.Policy
    29  	AuthInstance auth.Auth
    30  
    31  	uploadClient Client
    32  }
    33  
    34  // NewDriver initializes a new Driver from policy
    35  // TODO: refactor all method into upload client
    36  func NewDriver(policy *model.Policy) (*Driver, error) {
    37  	client, err := NewClient(policy)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	return &Driver{
    43  		Policy:       policy,
    44  		Client:       request.NewClient(),
    45  		AuthInstance: auth.HMACAuth{[]byte(policy.SecretKey)},
    46  		uploadClient: client,
    47  	}, nil
    48  }
    49  
    50  // List 列取文件
    51  func (handler *Driver) List(ctx context.Context, path string, recursive bool) ([]response.Object, error) {
    52  	var res []response.Object
    53  
    54  	reqBody := serializer.ListRequest{
    55  		Path:      path,
    56  		Recursive: recursive,
    57  	}
    58  	reqBodyEncoded, err := json.Marshal(reqBody)
    59  	if err != nil {
    60  		return res, err
    61  	}
    62  
    63  	// 发送列表请求
    64  	bodyReader := strings.NewReader(string(reqBodyEncoded))
    65  	signTTL := model.GetIntSetting("slave_api_timeout", 60)
    66  	resp, err := handler.Client.Request(
    67  		"POST",
    68  		handler.getAPIUrl("list"),
    69  		bodyReader,
    70  		request.WithCredential(handler.AuthInstance, int64(signTTL)),
    71  		request.WithMasterMeta(),
    72  	).CheckHTTPResponse(200).DecodeResponse()
    73  	if err != nil {
    74  		return res, err
    75  	}
    76  
    77  	// 处理列取结果
    78  	if resp.Code != 0 {
    79  		return res, errors.New(resp.Error)
    80  	}
    81  
    82  	if resStr, ok := resp.Data.(string); ok {
    83  		err = json.Unmarshal([]byte(resStr), &res)
    84  		if err != nil {
    85  			return res, err
    86  		}
    87  	}
    88  
    89  	return res, nil
    90  }
    91  
    92  // getAPIUrl 获取接口请求地址
    93  func (handler *Driver) getAPIUrl(scope string, routes ...string) string {
    94  	serverURL, err := url.Parse(handler.Policy.Server)
    95  	if err != nil {
    96  		return ""
    97  	}
    98  	var controller *url.URL
    99  
   100  	switch scope {
   101  	case "delete":
   102  		controller, _ = url.Parse("/api/v3/slave/delete")
   103  	case "thumb":
   104  		controller, _ = url.Parse("/api/v3/slave/thumb")
   105  	case "list":
   106  		controller, _ = url.Parse("/api/v3/slave/list")
   107  	default:
   108  		controller = serverURL
   109  	}
   110  
   111  	for _, r := range routes {
   112  		controller.Path = path.Join(controller.Path, r)
   113  	}
   114  
   115  	return serverURL.ResolveReference(controller).String()
   116  }
   117  
   118  // Get 获取文件内容
   119  func (handler *Driver) Get(ctx context.Context, path string) (response.RSCloser, error) {
   120  	// 尝试获取速度限制
   121  	speedLimit := 0
   122  	if user, ok := ctx.Value(fsctx.UserCtx).(model.User); ok {
   123  		speedLimit = user.Group.SpeedLimit
   124  	}
   125  
   126  	// 获取文件源地址
   127  	downloadURL, err := handler.Source(ctx, path, 0, true, speedLimit)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	// 获取文件数据流
   133  	resp, err := handler.Client.Request(
   134  		"GET",
   135  		downloadURL,
   136  		nil,
   137  		request.WithContext(ctx),
   138  		request.WithTimeout(time.Duration(0)),
   139  		request.WithMasterMeta(),
   140  	).CheckHTTPResponse(200).GetRSCloser()
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	resp.SetFirstFakeChunk()
   146  
   147  	// 尝试获取文件大小
   148  	if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
   149  		resp.SetContentLength(int64(file.Size))
   150  	}
   151  
   152  	return resp, nil
   153  }
   154  
   155  // Put 将文件流保存到指定目录
   156  func (handler *Driver) Put(ctx context.Context, file fsctx.FileHeader) error {
   157  	defer file.Close()
   158  
   159  	return handler.uploadClient.Upload(ctx, file)
   160  }
   161  
   162  // Delete 删除一个或多个文件,
   163  // 返回未删除的文件,及遇到的最后一个错误
   164  func (handler *Driver) Delete(ctx context.Context, files []string) ([]string, error) {
   165  	// 封装接口请求正文
   166  	reqBody := serializer.RemoteDeleteRequest{
   167  		Files: files,
   168  	}
   169  	reqBodyEncoded, err := json.Marshal(reqBody)
   170  	if err != nil {
   171  		return files, err
   172  	}
   173  
   174  	// 发送删除请求
   175  	bodyReader := strings.NewReader(string(reqBodyEncoded))
   176  	signTTL := model.GetIntSetting("slave_api_timeout", 60)
   177  	resp, err := handler.Client.Request(
   178  		"POST",
   179  		handler.getAPIUrl("delete"),
   180  		bodyReader,
   181  		request.WithCredential(handler.AuthInstance, int64(signTTL)),
   182  		request.WithMasterMeta(),
   183  		request.WithSlaveMeta(handler.Policy.AccessKey),
   184  	).CheckHTTPResponse(200).GetResponse()
   185  	if err != nil {
   186  		return files, err
   187  	}
   188  
   189  	// 处理删除结果
   190  	var reqResp serializer.Response
   191  	err = json.Unmarshal([]byte(resp), &reqResp)
   192  	if err != nil {
   193  		return files, err
   194  	}
   195  	if reqResp.Code != 0 {
   196  		var failedResp serializer.RemoteDeleteRequest
   197  		if failed, ok := reqResp.Data.(string); ok {
   198  			err = json.Unmarshal([]byte(failed), &failedResp)
   199  			if err == nil {
   200  				return failedResp.Files, errors.New(reqResp.Error)
   201  			}
   202  		}
   203  		return files, errors.New("unknown format of returned response")
   204  	}
   205  
   206  	return []string{}, nil
   207  }
   208  
   209  // Thumb 获取文件缩略图
   210  func (handler *Driver) Thumb(ctx context.Context, file *model.File) (*response.ContentResponse, error) {
   211  	// quick check by extension name
   212  	supported := []string{"png", "jpg", "jpeg", "gif"}
   213  	if len(handler.Policy.OptionsSerialized.ThumbExts) > 0 {
   214  		supported = handler.Policy.OptionsSerialized.ThumbExts
   215  	}
   216  
   217  	if !util.IsInExtensionList(supported, file.Name) {
   218  		return nil, driver.ErrorThumbNotSupported
   219  	}
   220  
   221  	sourcePath := base64.RawURLEncoding.EncodeToString([]byte(file.SourceName))
   222  	thumbURL := fmt.Sprintf("%s/%s/%s", handler.getAPIUrl("thumb"), sourcePath, filepath.Ext(file.Name))
   223  	ttl := model.GetIntSetting("preview_timeout", 60)
   224  	signedThumbURL, err := auth.SignURI(handler.AuthInstance, thumbURL, int64(ttl))
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	return &response.ContentResponse{
   230  		Redirect: true,
   231  		URL:      signedThumbURL.String(),
   232  	}, nil
   233  }
   234  
   235  // Source 获取外链URL
   236  func (handler *Driver) Source(ctx context.Context, path string, ttl int64, isDownload bool, speed int) (string, error) {
   237  	// 尝试从上下文获取文件名
   238  	fileName := "file"
   239  	if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
   240  		fileName = file.Name
   241  	}
   242  
   243  	serverURL, err := url.Parse(handler.Policy.Server)
   244  	if err != nil {
   245  		return "", errors.New("无法解析远程服务端地址")
   246  	}
   247  
   248  	// 是否启用了CDN
   249  	if handler.Policy.BaseURL != "" {
   250  		cdnURL, err := url.Parse(handler.Policy.BaseURL)
   251  		if err != nil {
   252  			return "", err
   253  		}
   254  		serverURL = cdnURL
   255  	}
   256  
   257  	var (
   258  		signedURI  *url.URL
   259  		controller = "/api/v3/slave/download"
   260  	)
   261  	if !isDownload {
   262  		controller = "/api/v3/slave/source"
   263  	}
   264  
   265  	// 签名下载地址
   266  	sourcePath := base64.RawURLEncoding.EncodeToString([]byte(path))
   267  	signedURI, err = auth.SignURI(
   268  		handler.AuthInstance,
   269  		fmt.Sprintf("%s/%d/%s/%s", controller, speed, sourcePath, url.PathEscape(fileName)),
   270  		ttl,
   271  	)
   272  
   273  	if err != nil {
   274  		return "", serializer.NewError(serializer.CodeEncryptError, "Failed to sign URL", err)
   275  	}
   276  
   277  	finalURL := serverURL.ResolveReference(signedURI).String()
   278  	return finalURL, nil
   279  
   280  }
   281  
   282  // Token 获取上传策略和认证Token
   283  func (handler *Driver) Token(ctx context.Context, ttl int64, uploadSession *serializer.UploadSession, file fsctx.FileHeader) (*serializer.UploadCredential, error) {
   284  	siteURL := model.GetSiteURL()
   285  	apiBaseURI, _ := url.Parse(path.Join("/api/v3/callback/remote", uploadSession.Key, uploadSession.CallbackSecret))
   286  	apiURL := siteURL.ResolveReference(apiBaseURI)
   287  
   288  	// 在从机端创建上传会话
   289  	uploadSession.Callback = apiURL.String()
   290  	if err := handler.uploadClient.CreateUploadSession(ctx, uploadSession, ttl, false); err != nil {
   291  		return nil, err
   292  	}
   293  
   294  	// 获取上传地址
   295  	uploadURL, sign, err := handler.uploadClient.GetUploadURL(ttl, uploadSession.Key)
   296  	if err != nil {
   297  		return nil, fmt.Errorf("failed to sign upload url: %w", err)
   298  	}
   299  
   300  	return &serializer.UploadCredential{
   301  		SessionID:  uploadSession.Key,
   302  		ChunkSize:  handler.Policy.OptionsSerialized.ChunkSize,
   303  		UploadURLs: []string{uploadURL},
   304  		Credential: sign,
   305  	}, nil
   306  }
   307  
   308  // 取消上传凭证
   309  func (handler *Driver) CancelToken(ctx context.Context, uploadSession *serializer.UploadSession) error {
   310  	return handler.uploadClient.DeleteUploadSession(ctx, uploadSession.Key)
   311  }