This commit is contained in:
2024-02-25 08:30:34 +08:00
commit 4947f39e74
273 changed files with 45396 additions and 0 deletions

152
service/share/manage.go Normal file
View File

@ -0,0 +1,152 @@
package share
import (
"net/url"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/gin-gonic/gin"
)
// ShareCreateService 创建新分享服务
type ShareCreateService struct {
SourceID string `json:"id" binding:"required"`
IsDir bool `json:"is_dir"`
Password string `json:"password" binding:"max=255"`
RemainDownloads int `json:"downloads"`
Expire int `json:"expire"`
Score int `json:"score" binding:"gte=0"`
Preview bool `json:"preview"`
}
// ShareUpdateService 分享更新服务
type ShareUpdateService struct {
Prop string `json:"prop" binding:"required,eq=password|eq=preview_enabled"`
Value string `json:"value" binding:"max=255"`
}
// Delete 删除分享
func (service *Service) Delete(c *gin.Context, user *model.User) serializer.Response {
share := model.GetShareByHashID(c.Param("id"))
if share == nil || share.Creator().ID != user.ID {
return serializer.Err(serializer.CodeShareLinkNotFound, "", nil)
}
if err := share.Delete(); err != nil {
return serializer.DBErr("Failed to delete share record", err)
}
return serializer.Response{}
}
// Update 更新分享属性
func (service *ShareUpdateService) Update(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
switch service.Prop {
case "password":
err := share.Update(map[string]interface{}{"password": service.Value})
if err != nil {
return serializer.DBErr("Failed to update share record", err)
}
case "preview_enabled":
value := service.Value == "true"
err := share.Update(map[string]interface{}{"preview_enabled": value})
if err != nil {
return serializer.DBErr("Failed to update share record", err)
}
return serializer.Response{
Data: value,
}
}
return serializer.Response{
Data: service.Value,
}
}
// Create 创建新分享
func (service *ShareCreateService) Create(c *gin.Context) serializer.Response {
userCtx, _ := c.Get("user")
user := userCtx.(*model.User)
// 是否拥有权限
if !user.Group.ShareEnabled {
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
}
// 源对象真实ID
var (
sourceID uint
sourceName string
err error
)
if service.IsDir {
sourceID, err = hashid.DecodeHashID(service.SourceID, hashid.FolderID)
} else {
sourceID, err = hashid.DecodeHashID(service.SourceID, hashid.FileID)
}
if err != nil {
return serializer.Err(serializer.CodeNotFound, "", nil)
}
// 对象是否存在
exist := true
if service.IsDir {
folder, err := model.GetFoldersByIDs([]uint{sourceID}, user.ID)
if err != nil || len(folder) == 0 {
exist = false
} else {
sourceName = folder[0].Name
}
} else {
file, err := model.GetFilesByIDs([]uint{sourceID}, user.ID)
if err != nil || len(file) == 0 {
exist = false
} else {
sourceName = file[0].Name
}
}
if !exist {
return serializer.Err(serializer.CodeNotFound, "", nil)
}
newShare := model.Share{
Password: service.Password,
IsDir: service.IsDir,
UserID: user.ID,
SourceID: sourceID,
Score: service.Score,
RemainDownloads: -1,
PreviewEnabled: service.Preview,
SourceName: sourceName,
}
// 如果开启了自动过期
if service.RemainDownloads > 0 {
expires := time.Now().Add(time.Duration(service.Expire) * time.Second)
newShare.RemainDownloads = service.RemainDownloads
newShare.Expires = &expires
}
// 创建分享
id, err := newShare.Create()
if err != nil {
return serializer.DBErr("Failed to create share link record", err)
}
// 获取分享的唯一id
uid := hashid.HashID(id, hashid.ShareID)
// 最终得到分享链接
siteURL := model.GetSiteURL()
sharePath, _ := url.Parse("/s/" + uid)
shareURL := siteURL.ResolveReference(sharePath)
return serializer.Response{
Code: 0,
Data: shareURL.String(),
}
}

477
service/share/visit.go Normal file
View File

@ -0,0 +1,477 @@
package share
import (
"context"
"fmt"
"net/http"
"path"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/gin-gonic/gin"
)
// ShareUserGetService 获取用户的分享服务
type ShareUserGetService struct {
Type string `form:"type" binding:"required,eq=hot|eq=default"`
Page uint `form:"page" binding:"required,min=1"`
}
// ShareGetService 获取分享服务
type ShareGetService struct {
Password string `form:"password" binding:"max=255"`
}
// Service 对分享进行操作的服务,
// path 为可选文件完整路径,在目录分享下有效
type Service struct {
Path string `form:"path" uri:"path" binding:"max=65535"`
}
// ArchiveService 分享归档下载服务
type ArchiveService struct {
Path string `json:"path" binding:"required,max=65535"`
Items []string `json:"items"`
Dirs []string `json:"dirs"`
}
// ShareListService 列出分享
type ShareListService struct {
Page uint `form:"page" binding:"required,min=1"`
OrderBy string `form:"order_by" binding:"required,eq=created_at|eq=downloads|eq=views"`
Order string `form:"order" binding:"required,eq=DESC|eq=ASC"`
Keywords string `form:"keywords"`
}
// ShareReportService 举报分享
type ShareReportService struct {
Reason int `json:"reason" binding:"gte=0,lte=4"`
Des string `json:"des"`
}
// Get 获取给定用户的分享
func (service *ShareReportService) Report(c *gin.Context) serializer.Response {
// 取得分享ID
shareID, _ := c.Get("share")
report := &model.Report{
ShareID: shareID.(*model.Share).ID,
Reason: service.Reason,
Description: service.Des,
}
if err := report.Create(); err != nil {
return serializer.DBErr("Failed to create report record", err)
}
return serializer.Response{}
}
// Get 获取给定用户的分享
func (service *ShareUserGetService) Get(c *gin.Context) serializer.Response {
// 取得用户
userID, _ := c.Get("object_id")
user, err := model.GetActiveUserByID(userID.(uint))
if err != nil || user.OptionsSerialized.ProfileOff {
return serializer.Err(serializer.CodeNotFound, "", err)
}
// 列出分享
hotNum := model.GetIntSetting("hot_share_num", 10)
if service.Type == "default" {
hotNum = 10
}
orderBy := "created_at desc"
if service.Type == "hot" {
orderBy = "views desc"
}
shares, total := model.ListShares(user.ID, int(service.Page), hotNum, orderBy, true)
// 列出分享对应的文件
for i := 0; i < len(shares); i++ {
shares[i].Source()
}
res := serializer.BuildShareList(shares, total)
res.Data.(map[string]interface{})["user"] = struct {
ID string `json:"id"`
Nick string `json:"nick"`
Group string `json:"group"`
Date string `json:"date"`
}{
hashid.HashID(user.ID, hashid.UserID),
user.Nick,
user.Group.Name,
user.CreatedAt.Format("2006-01-02 15:04:05"),
}
return res
}
// Search 搜索公共分享
func (service *ShareListService) Search(c *gin.Context) serializer.Response {
// 列出分享
shares, total := model.SearchShares(int(service.Page), 18, service.OrderBy+" "+
service.Order, service.Keywords)
// 列出分享对应的文件
for i := 0; i < len(shares); i++ {
shares[i].Source()
}
return serializer.BuildShareList(shares, total)
}
// List 列出用户分享
func (service *ShareListService) List(c *gin.Context, user *model.User) serializer.Response {
// 列出分享
shares, total := model.ListShares(user.ID, int(service.Page), 18, service.OrderBy+" "+
service.Order, false)
// 列出分享对应的文件
for i := 0; i < len(shares); i++ {
shares[i].Source()
}
return serializer.BuildShareList(shares, total)
}
// Get 获取分享内容
func (service *ShareGetService) Get(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
userCtx, _ := c.Get("user")
user := userCtx.(*model.User)
// 是否已解锁
unlocked := true
if share.Password != "" {
sessionKey := fmt.Sprintf("share_unlock_%d", share.ID)
unlocked = util.GetSession(c, sessionKey) != nil
if !unlocked && service.Password != "" {
// 如果未解锁,且指定了密码,则尝试解锁
if service.Password == share.Password {
unlocked = true
util.SetSession(c, map[string]interface{}{sessionKey: true})
}
}
}
if unlocked {
share.Viewed()
}
// 如果已经下载过或者是自己的分享,不需要付积分
if share.UserID == user.ID || share.WasDownloadedBy(user, c) {
share.Score = 0
}
return serializer.Response{
Code: 0,
Data: serializer.BuildShareResponse(share, unlocked),
}
}
// CreateDownloadSession 创建下载会话
func (service *Service) CreateDownloadSession(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
userCtx, _ := c.Get("user")
user := userCtx.(*model.User)
// 创建文件系统
fs, err := filesystem.NewFileSystem(user)
if err != nil {
return serializer.DBErr("Failed to update share record", err)
}
defer fs.Recycle()
// 重设文件系统处理目标为源文件
err = fs.SetTargetByInterface(share.Source())
if err != nil {
return serializer.Err(serializer.CodeFileNotFound, "", err)
}
ctx := context.Background()
// 重设根目录
if share.IsDir {
fs.Root = &fs.DirTarget[0]
// 找到目标文件
err = fs.ResetFileIfNotExist(ctx, service.Path)
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
}
// 取得下载地址
downloadURL, err := fs.GetDownloadURL(ctx, 0, "download_timeout")
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
return serializer.Response{
Code: 0,
Data: downloadURL,
}
}
// PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会
// 强制经由服务端中转
func (service *Service) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
// 用于调下层service
if share.IsDir {
ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source())
ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path)
} else {
ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
}
subService := explorer.FileIDService{}
return subService.PreviewContent(ctx, c, isText)
}
// CreateDocPreviewSession 创建Office预览会话返回预览地址
func (service *Service) CreateDocPreviewSession(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
// 用于调下层service
ctx := context.Background()
if share.IsDir {
ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source())
ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path)
} else {
ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
}
subService := explorer.FileIDService{}
return subService.CreateDocPreviewSession(ctx, c, false)
}
// SaveToMyFile 将此分享转存到自己的网盘
func (service *Service) SaveToMyFile(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
userCtx, _ := c.Get("user")
user := userCtx.(*model.User)
// 不能转存自己的文件
if share.UserID == user.ID {
return serializer.Err(serializer.CodeSaveOwnShare, "", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(user)
if err != nil {
return serializer.Err(serializer.CodeCreateFSError, "", err)
}
defer fs.Recycle()
// 重设文件系统处理目标为源文件
err = fs.SetTargetByInterface(share.Source())
if err != nil {
return serializer.Err(serializer.CodeFileNotFound, "", err)
}
err = fs.SaveTo(context.Background(), service.Path)
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
return serializer.Response{}
}
// List 列出分享的目录下的对象
func (service *Service) List(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
if !share.IsDir {
return serializer.ParamErr("This is not a shared folder", nil)
}
if !path.IsAbs(service.Path) {
return serializer.ParamErr("Invalid path", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(share.Creator())
if err != nil {
return serializer.Err(serializer.CodeCreateFSError, "", err)
}
defer fs.Recycle()
// 上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 重设根目录
fs.Root = share.Source().(*model.Folder)
fs.Root.Name = "/"
// 分享Key上下文
ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID))
// 获取子项目
objects, err := fs.List(ctx, service.Path, nil)
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
return serializer.Response{
Code: 0,
Data: serializer.BuildObjectList(0, objects, nil),
}
}
// Thumb 获取被分享文件的缩略图
func (service *Service) Thumb(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
if !share.IsDir {
return serializer.ParamErr("This share has no thumb", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(share.Creator())
if err != nil {
return serializer.Err(serializer.CodeCreateFSError, "", err)
}
defer fs.Recycle()
// 重设根目录
fs.Root = share.Source().(*model.Folder)
// 找到缩略图的父目录
exist, parent := fs.IsPathExist(service.Path)
if !exist {
return serializer.Err(serializer.CodeParentNotExist, "", nil)
}
ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent)
// 获取文件ID
fileID, err := hashid.DecodeHashID(c.Param("file"), hashid.FileID)
if err != nil {
return serializer.Err(serializer.CodeNotFound, "", err)
}
// 获取缩略图
resp, err := fs.GetThumb(ctx, uint(fileID))
if err != nil {
return serializer.Err(serializer.CodeNotSet, "Failed to get thumb", err)
}
if resp.Redirect {
c.Header("Cache-Control", fmt.Sprintf("max-age=%d", resp.MaxAge))
c.Redirect(http.StatusMovedPermanently, resp.URL)
return serializer.Response{Code: -1}
}
defer resp.Content.Close()
http.ServeContent(c.Writer, c.Request, "thumb.png", fs.FileTarget[0].UpdatedAt, resp.Content)
return serializer.Response{Code: -1}
}
// Archive 创建批量下载归档
func (service *ArchiveService) Archive(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
userCtx, _ := c.Get("user")
user := userCtx.(*model.User)
// 是否有权限
if !user.Group.OptionsSerialized.ArchiveDownload {
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
}
if !share.IsDir {
return serializer.ParamErr("This share cannot be batch downloaded", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(user)
if err != nil {
return serializer.Err(serializer.CodeCreateFSError, "", err)
}
defer fs.Recycle()
// 重设根目录
fs.Root = share.Source().(*model.Folder)
// 找到要打包文件的父目录
exist, parent := fs.IsPathExist(service.Path)
if !exist {
return serializer.Err(serializer.CodeParentNotExist, "", nil)
}
// 限制操作范围为父目录下
ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent)
// 用于调下层service
tempUser := share.Creator()
tempUser.Group.OptionsSerialized.ArchiveDownload = true
c.Set("user", tempUser)
subService := explorer.ItemIDService{
Dirs: service.Dirs,
Items: service.Items,
}
return subService.Archive(ctx, c)
}
// SearchService 对分享的目录进行搜索
type SearchService struct {
explorer.ItemSearchService
}
// Search 执行搜索
func (service *SearchService) Search(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
if !share.IsDir {
return serializer.ParamErr("This is not a shared folder", nil)
}
if service.Path != "" && !path.IsAbs(service.Path) {
return serializer.ParamErr("Invalid path", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(share.Creator())
if err != nil {
return serializer.Err(serializer.CodeCreateFSError, "", err)
}
defer fs.Recycle()
// 上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 重设根目录
fs.Root = share.Source().(*model.Folder)
fs.Root.Name = "/"
if service.Path != "" {
ok, parent := fs.IsPathExist(service.Path)
if !ok {
return serializer.Err(serializer.CodeParentNotExist, "", nil)
}
fs.Root = parent
}
// 分享Key上下文
ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID))
return service.SearchKeywords(c, fs, "%"+service.Keywords+"%")
}