github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/service/explorer/slave.go (about) 1 package explorer 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/url" 10 "time" 11 12 model "github.com/cloudreve/Cloudreve/v3/models" 13 "github.com/cloudreve/Cloudreve/v3/pkg/cache" 14 "github.com/cloudreve/Cloudreve/v3/pkg/cluster" 15 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" 16 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" 17 "github.com/cloudreve/Cloudreve/v3/pkg/serializer" 18 "github.com/cloudreve/Cloudreve/v3/pkg/task" 19 "github.com/cloudreve/Cloudreve/v3/pkg/task/slavetask" 20 "github.com/cloudreve/Cloudreve/v3/pkg/util" 21 "github.com/gin-gonic/gin" 22 "github.com/jinzhu/gorm" 23 ) 24 25 // SlaveDownloadService 从机文件下載服务 26 type SlaveDownloadService struct { 27 PathEncoded string `uri:"path" binding:"required"` 28 Name string `uri:"name" binding:"required"` 29 Speed int `uri:"speed" binding:"min=0"` 30 } 31 32 // SlaveFileService 从机单文件文件相关服务 33 type SlaveFileService struct { 34 PathEncoded string `uri:"path" binding:"required"` 35 Ext string `uri:"ext"` 36 } 37 38 // SlaveFilesService 从机多文件相关服务 39 type SlaveFilesService struct { 40 Files []string `json:"files" binding:"required,gt=0"` 41 } 42 43 // SlaveListService 从机列表服务 44 type SlaveListService struct { 45 Path string `json:"path" binding:"required,min=1,max=65535"` 46 Recursive bool `json:"recursive"` 47 } 48 49 // ServeFile 通过签名的URL下载从机文件 50 func (service *SlaveDownloadService) ServeFile(ctx context.Context, c *gin.Context, isDownload bool) serializer.Response { 51 // 创建文件系统 52 fs, err := filesystem.NewAnonymousFileSystem() 53 if err != nil { 54 return serializer.Err(serializer.CodeCreateFSError, "", err) 55 } 56 defer fs.Recycle() 57 58 // 解码文件路径 59 fileSource, err := base64.RawURLEncoding.DecodeString(service.PathEncoded) 60 if err != nil { 61 return serializer.Err(serializer.CodeFileNotFound, "", err) 62 } 63 64 // 根据URL里的信息创建一个文件对象和用户对象 65 file := model.File{ 66 Name: service.Name, 67 SourceName: string(fileSource), 68 Policy: model.Policy{ 69 Model: gorm.Model{ID: 1}, 70 Type: "local", 71 }, 72 } 73 fs.User = &model.User{ 74 Group: model.Group{SpeedLimit: service.Speed}, 75 } 76 fs.FileTarget = []model.File{file} 77 78 // 开始处理下载 79 ctx = context.WithValue(ctx, fsctx.GinCtx, c) 80 rs, err := fs.GetDownloadContent(ctx, 0) 81 if err != nil { 82 return serializer.Err(serializer.CodeNotSet, err.Error(), err) 83 } 84 defer rs.Close() 85 86 // 设置下载文件名 87 if isDownload { 88 c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape(fs.FileTarget[0].Name)+"\"") 89 } 90 91 // 发送文件 92 http.ServeContent(c.Writer, c.Request, fs.FileTarget[0].Name, time.Now(), rs) 93 94 return serializer.Response{} 95 } 96 97 // Delete 通过签名的URL删除从机文件 98 func (service *SlaveFilesService) Delete(ctx context.Context, c *gin.Context) serializer.Response { 99 // 创建文件系统 100 fs, err := filesystem.NewAnonymousFileSystem() 101 if err != nil { 102 return serializer.Err(serializer.CodeCreateFSError, "", err) 103 } 104 defer fs.Recycle() 105 106 // 删除文件 107 failed, err := fs.Handler.Delete(ctx, service.Files) 108 if err != nil { 109 // 将Data字段写为字符串方便主控端解析 110 data, _ := json.Marshal(serializer.RemoteDeleteRequest{Files: failed}) 111 112 return serializer.Response{ 113 Code: serializer.CodeNotFullySuccess, 114 Data: string(data), 115 Msg: fmt.Sprintf("Failed to delete %d files(s)", len(failed)), 116 Error: err.Error(), 117 } 118 } 119 return serializer.Response{} 120 } 121 122 // Thumb 通过签名URL获取从机文件缩略图 123 func (service *SlaveFileService) Thumb(ctx context.Context, c *gin.Context) serializer.Response { 124 // 创建文件系统 125 fs, err := filesystem.NewAnonymousFileSystem() 126 if err != nil { 127 return serializer.Err(serializer.CodeCreateFSError, "", err) 128 } 129 defer fs.Recycle() 130 131 // 解码文件路径 132 fileSource, err := base64.RawURLEncoding.DecodeString(service.PathEncoded) 133 if err != nil { 134 return serializer.Err(serializer.CodeFileNotFound, "", err) 135 } 136 fs.FileTarget = []model.File{{SourceName: string(fileSource), Name: fmt.Sprintf("%s.%s", fileSource, service.Ext), PicInfo: "1,1"}} 137 138 // 获取缩略图 139 resp, err := fs.GetThumb(ctx, 0) 140 if err != nil { 141 return serializer.Err(serializer.CodeNotSet, "Failed to get thumb", err) 142 } 143 144 defer resp.Content.Close() 145 http.ServeContent(c.Writer, c.Request, "thumb.png", time.Now(), resp.Content) 146 147 return serializer.Response{} 148 } 149 150 // CreateTransferTask 创建从机文件转存任务 151 func CreateTransferTask(c *gin.Context, req *serializer.SlaveTransferReq) serializer.Response { 152 if id, ok := c.Get("MasterSiteID"); ok { 153 job := &slavetask.TransferTask{ 154 Req: req, 155 MasterID: id.(string), 156 } 157 158 if err := cluster.DefaultController.SubmitTask(job.MasterID, job, req.Hash(job.MasterID), func(job interface{}) { 159 task.TaskPoll.Submit(job.(task.Job)) 160 }); err != nil { 161 return serializer.Err(serializer.CodeCreateTaskError, "", err) 162 } 163 164 return serializer.Response{} 165 } 166 167 return serializer.ParamErr("未知的主机节点ID", nil) 168 } 169 170 // SlaveListService 从机上传会话服务 171 type SlaveCreateUploadSessionService struct { 172 Session serializer.UploadSession `json:"session" binding:"required"` 173 TTL int64 `json:"ttl"` 174 Overwrite bool `json:"overwrite"` 175 } 176 177 // Create 从机创建上传会话 178 func (service *SlaveCreateUploadSessionService) Create(ctx context.Context, c *gin.Context) serializer.Response { 179 if !service.Overwrite && util.Exists(service.Session.SavePath) { 180 return serializer.Err(serializer.CodeConflict, "placeholder file already exist", nil) 181 } 182 183 err := cache.Set( 184 filesystem.UploadSessionCachePrefix+service.Session.Key, 185 service.Session, 186 int(service.TTL), 187 ) 188 if err != nil { 189 return serializer.Err(serializer.CodeCacheOperation, "Failed to create upload session in slave node", err) 190 } 191 192 return serializer.Response{} 193 }