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 }