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  }