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 }