github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/service/share/visit.go (about)

     1  package share
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"path"
     8  
     9  	model "github.com/cloudreve/Cloudreve/v3/models"
    10  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
    11  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
    12  	"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
    13  	"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
    14  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
    15  	"github.com/cloudreve/Cloudreve/v3/service/explorer"
    16  	"github.com/gin-gonic/gin"
    17  )
    18  
    19  // ShareUserGetService 获取用户的分享服务
    20  type ShareUserGetService struct {
    21  	Type string `form:"type" binding:"required,eq=hot|eq=default"`
    22  	Page uint   `form:"page" binding:"required,min=1"`
    23  }
    24  
    25  // ShareGetService 获取分享服务
    26  type ShareGetService struct {
    27  	Password string `form:"password" binding:"max=255"`
    28  }
    29  
    30  // Service 对分享进行操作的服务,
    31  // path 为可选文件完整路径,在目录分享下有效
    32  type Service struct {
    33  	Path string `form:"path" uri:"path" binding:"max=65535"`
    34  }
    35  
    36  // ArchiveService 分享归档下载服务
    37  type ArchiveService struct {
    38  	Path  string   `json:"path" binding:"required,max=65535"`
    39  	Items []string `json:"items"`
    40  	Dirs  []string `json:"dirs"`
    41  }
    42  
    43  // ShareListService 列出分享
    44  type ShareListService struct {
    45  	Page     uint   `form:"page" binding:"required,min=1"`
    46  	OrderBy  string `form:"order_by" binding:"required,eq=created_at|eq=downloads|eq=views"`
    47  	Order    string `form:"order" binding:"required,eq=DESC|eq=ASC"`
    48  	Keywords string `form:"keywords"`
    49  }
    50  
    51  // Get 获取给定用户的分享
    52  func (service *ShareUserGetService) Get(c *gin.Context) serializer.Response {
    53  	// 取得用户
    54  	userID, _ := c.Get("object_id")
    55  	user, err := model.GetActiveUserByID(userID.(uint))
    56  	if err != nil || user.OptionsSerialized.ProfileOff {
    57  		return serializer.Err(serializer.CodeNotFound, "", err)
    58  	}
    59  
    60  	// 列出分享
    61  	hotNum := model.GetIntSetting("hot_share_num", 10)
    62  	if service.Type == "default" {
    63  		hotNum = 10
    64  	}
    65  	orderBy := "created_at desc"
    66  	if service.Type == "hot" {
    67  		orderBy = "views desc"
    68  	}
    69  	shares, total := model.ListShares(user.ID, int(service.Page), hotNum, orderBy, true)
    70  	// 列出分享对应的文件
    71  	for i := 0; i < len(shares); i++ {
    72  		shares[i].Source()
    73  	}
    74  
    75  	res := serializer.BuildShareList(shares, total)
    76  	res.Data.(map[string]interface{})["user"] = struct {
    77  		ID    string `json:"id"`
    78  		Nick  string `json:"nick"`
    79  		Group string `json:"group"`
    80  		Date  string `json:"date"`
    81  	}{
    82  		hashid.HashID(user.ID, hashid.UserID),
    83  		user.Nick,
    84  		user.Group.Name,
    85  		user.CreatedAt.Format("2006-01-02 15:04:05"),
    86  	}
    87  
    88  	return res
    89  }
    90  
    91  // Search 搜索公共分享
    92  func (service *ShareListService) Search(c *gin.Context) serializer.Response {
    93  	// 列出分享
    94  	shares, total := model.SearchShares(int(service.Page), 18, service.OrderBy+" "+
    95  		service.Order, service.Keywords)
    96  	// 列出分享对应的文件
    97  	for i := 0; i < len(shares); i++ {
    98  		shares[i].Source()
    99  	}
   100  
   101  	return serializer.BuildShareList(shares, total)
   102  }
   103  
   104  // List 列出用户分享
   105  func (service *ShareListService) List(c *gin.Context, user *model.User) serializer.Response {
   106  	// 列出分享
   107  	shares, total := model.ListShares(user.ID, int(service.Page), 18, service.OrderBy+" "+
   108  		service.Order, false)
   109  	// 列出分享对应的文件
   110  	for i := 0; i < len(shares); i++ {
   111  		shares[i].Source()
   112  	}
   113  
   114  	return serializer.BuildShareList(shares, total)
   115  }
   116  
   117  // Get 获取分享内容
   118  func (service *ShareGetService) Get(c *gin.Context) serializer.Response {
   119  	shareCtx, _ := c.Get("share")
   120  	share := shareCtx.(*model.Share)
   121  
   122  	// 是否已解锁
   123  	unlocked := true
   124  	if share.Password != "" {
   125  		sessionKey := fmt.Sprintf("share_unlock_%d", share.ID)
   126  		unlocked = util.GetSession(c, sessionKey) != nil
   127  		if !unlocked && service.Password != "" {
   128  			// 如果未解锁,且指定了密码,则尝试解锁
   129  			if service.Password == share.Password {
   130  				unlocked = true
   131  				util.SetSession(c, map[string]interface{}{sessionKey: true})
   132  			}
   133  		}
   134  	}
   135  
   136  	if unlocked {
   137  		share.Viewed()
   138  	}
   139  
   140  	return serializer.Response{
   141  		Code: 0,
   142  		Data: serializer.BuildShareResponse(share, unlocked),
   143  	}
   144  }
   145  
   146  // CreateDownloadSession 创建下载会话
   147  func (service *Service) CreateDownloadSession(c *gin.Context) serializer.Response {
   148  	shareCtx, _ := c.Get("share")
   149  	share := shareCtx.(*model.Share)
   150  	userCtx, _ := c.Get("user")
   151  	user := userCtx.(*model.User)
   152  
   153  	// 创建文件系统
   154  	fs, err := filesystem.NewFileSystem(user)
   155  	if err != nil {
   156  		return serializer.DBErr("Failed to update share record", err)
   157  	}
   158  	defer fs.Recycle()
   159  
   160  	// 重设文件系统处理目标为源文件
   161  	err = fs.SetTargetByInterface(share.Source())
   162  	if err != nil {
   163  		return serializer.Err(serializer.CodeFileNotFound, "", err)
   164  	}
   165  
   166  	ctx := context.Background()
   167  
   168  	// 重设根目录
   169  	if share.IsDir {
   170  		fs.Root = &fs.DirTarget[0]
   171  
   172  		// 找到目标文件
   173  		err = fs.ResetFileIfNotExist(ctx, service.Path)
   174  		if err != nil {
   175  			return serializer.Err(serializer.CodeNotSet, err.Error(), err)
   176  		}
   177  	}
   178  
   179  	// 取得下载地址
   180  	downloadURL, err := fs.GetDownloadURL(ctx, 0, "download_timeout")
   181  	if err != nil {
   182  		return serializer.Err(serializer.CodeNotSet, err.Error(), err)
   183  	}
   184  
   185  	return serializer.Response{
   186  		Code: 0,
   187  		Data: downloadURL,
   188  	}
   189  }
   190  
   191  // PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会
   192  // 强制经由服务端中转
   193  func (service *Service) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response {
   194  	shareCtx, _ := c.Get("share")
   195  	share := shareCtx.(*model.Share)
   196  
   197  	// 用于调下层service
   198  	if share.IsDir {
   199  		ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source())
   200  		ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path)
   201  	} else {
   202  		ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
   203  	}
   204  	subService := explorer.FileIDService{}
   205  
   206  	return subService.PreviewContent(ctx, c, isText)
   207  }
   208  
   209  // CreateDocPreviewSession 创建Office预览会话,返回预览地址
   210  func (service *Service) CreateDocPreviewSession(c *gin.Context) serializer.Response {
   211  	shareCtx, _ := c.Get("share")
   212  	share := shareCtx.(*model.Share)
   213  
   214  	// 用于调下层service
   215  	ctx := context.Background()
   216  	if share.IsDir {
   217  		ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source())
   218  		ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path)
   219  	} else {
   220  		ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
   221  	}
   222  	subService := explorer.FileIDService{}
   223  
   224  	return subService.CreateDocPreviewSession(ctx, c, false)
   225  }
   226  
   227  // List 列出分享的目录下的对象
   228  func (service *Service) List(c *gin.Context) serializer.Response {
   229  	shareCtx, _ := c.Get("share")
   230  	share := shareCtx.(*model.Share)
   231  
   232  	if !share.IsDir {
   233  		return serializer.ParamErr("This is not a shared folder", nil)
   234  	}
   235  
   236  	if !path.IsAbs(service.Path) {
   237  		return serializer.ParamErr("Invalid path", nil)
   238  	}
   239  
   240  	// 创建文件系统
   241  	fs, err := filesystem.NewFileSystem(share.Creator())
   242  	if err != nil {
   243  		return serializer.Err(serializer.CodeCreateFSError, "", err)
   244  	}
   245  	defer fs.Recycle()
   246  
   247  	// 上下文
   248  	ctx, cancel := context.WithCancel(context.Background())
   249  	defer cancel()
   250  
   251  	// 重设根目录
   252  	fs.Root = share.Source().(*model.Folder)
   253  	fs.Root.Name = "/"
   254  
   255  	// 分享Key上下文
   256  	ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID))
   257  
   258  	// 获取子项目
   259  	objects, err := fs.List(ctx, service.Path, nil)
   260  	if err != nil {
   261  		return serializer.Err(serializer.CodeNotSet, err.Error(), err)
   262  	}
   263  
   264  	return serializer.Response{
   265  		Code: 0,
   266  		Data: serializer.BuildObjectList(0, objects, nil),
   267  	}
   268  }
   269  
   270  // Thumb 获取被分享文件的缩略图
   271  func (service *Service) Thumb(c *gin.Context) serializer.Response {
   272  	shareCtx, _ := c.Get("share")
   273  	share := shareCtx.(*model.Share)
   274  
   275  	if !share.IsDir {
   276  		return serializer.ParamErr("This share has no thumb", nil)
   277  	}
   278  
   279  	// 创建文件系统
   280  	fs, err := filesystem.NewFileSystem(share.Creator())
   281  	if err != nil {
   282  		return serializer.Err(serializer.CodeCreateFSError, "", err)
   283  	}
   284  	defer fs.Recycle()
   285  
   286  	// 重设根目录
   287  	fs.Root = share.Source().(*model.Folder)
   288  
   289  	// 找到缩略图的父目录
   290  	exist, parent := fs.IsPathExist(service.Path)
   291  	if !exist {
   292  		return serializer.Err(serializer.CodeParentNotExist, "", nil)
   293  	}
   294  
   295  	ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent)
   296  
   297  	// 获取文件ID
   298  	fileID, err := hashid.DecodeHashID(c.Param("file"), hashid.FileID)
   299  	if err != nil {
   300  		return serializer.Err(serializer.CodeNotFound, "", err)
   301  	}
   302  
   303  	// 获取缩略图
   304  	resp, err := fs.GetThumb(ctx, uint(fileID))
   305  	if err != nil {
   306  		return serializer.Err(serializer.CodeNotSet, "Failed to get thumb", err)
   307  	}
   308  
   309  	if resp.Redirect {
   310  		c.Header("Cache-Control", fmt.Sprintf("max-age=%d", resp.MaxAge))
   311  		c.Redirect(http.StatusMovedPermanently, resp.URL)
   312  		return serializer.Response{Code: -1}
   313  	}
   314  
   315  	defer resp.Content.Close()
   316  	http.ServeContent(c.Writer, c.Request, "thumb.png", fs.FileTarget[0].UpdatedAt, resp.Content)
   317  
   318  	return serializer.Response{Code: -1}
   319  
   320  }
   321  
   322  // Archive 创建批量下载归档
   323  func (service *ArchiveService) Archive(c *gin.Context) serializer.Response {
   324  	shareCtx, _ := c.Get("share")
   325  	share := shareCtx.(*model.Share)
   326  	userCtx, _ := c.Get("user")
   327  	user := userCtx.(*model.User)
   328  
   329  	// 是否有权限
   330  	if !user.Group.OptionsSerialized.ArchiveDownload {
   331  		return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
   332  	}
   333  
   334  	if !share.IsDir {
   335  		return serializer.ParamErr("This share cannot be batch downloaded", nil)
   336  	}
   337  
   338  	// 创建文件系统
   339  	fs, err := filesystem.NewFileSystem(user)
   340  	if err != nil {
   341  		return serializer.Err(serializer.CodeCreateFSError, "", err)
   342  	}
   343  	defer fs.Recycle()
   344  
   345  	// 重设根目录
   346  	fs.Root = share.Source().(*model.Folder)
   347  
   348  	// 找到要打包文件的父目录
   349  	exist, parent := fs.IsPathExist(service.Path)
   350  	if !exist {
   351  		return serializer.Err(serializer.CodeParentNotExist, "", nil)
   352  	}
   353  
   354  	// 限制操作范围为父目录下
   355  	ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent)
   356  
   357  	// 用于调下层service
   358  	tempUser := share.Creator()
   359  	tempUser.Group.OptionsSerialized.ArchiveDownload = true
   360  	c.Set("user", tempUser)
   361  
   362  	subService := explorer.ItemIDService{
   363  		Dirs:  service.Dirs,
   364  		Items: service.Items,
   365  	}
   366  
   367  	return subService.Archive(ctx, c)
   368  }
   369  
   370  // SearchService 对分享的目录进行搜索
   371  type SearchService struct {
   372  	explorer.ItemSearchService
   373  }
   374  
   375  // Search 执行搜索
   376  func (service *SearchService) Search(c *gin.Context) serializer.Response {
   377  	shareCtx, _ := c.Get("share")
   378  	share := shareCtx.(*model.Share)
   379  
   380  	if !share.IsDir {
   381  		return serializer.ParamErr("此分享无法列目录", nil)
   382  	}
   383  
   384  	if service.Path != "" && !path.IsAbs(service.Path) {
   385  		return serializer.ParamErr("路径无效", nil)
   386  	}
   387  
   388  	// 创建文件系统
   389  	fs, err := filesystem.NewFileSystem(share.Creator())
   390  	if err != nil {
   391  		return serializer.Err(serializer.CodeCreateFSError, "", err)
   392  	}
   393  	defer fs.Recycle()
   394  
   395  	// 上下文
   396  	ctx, cancel := context.WithCancel(context.Background())
   397  	defer cancel()
   398  
   399  	// 重设根目录
   400  	fs.Root = share.Source().(*model.Folder)
   401  	fs.Root.Name = "/"
   402  	if service.Path != "" {
   403  		ok, parent := fs.IsPathExist(service.Path)
   404  		if !ok {
   405  			return serializer.Err(serializer.CodeParentNotExist, "Cannot find parent folder", nil)
   406  		}
   407  
   408  		fs.Root = parent
   409  	}
   410  
   411  	// 分享Key上下文
   412  	ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID))
   413  
   414  	return service.SearchKeywords(c, fs, "%"+service.Keywords+"%")
   415  }