diff --git a/middleware/explorer.go b/middleware/explorer.go new file mode 100644 index 0000000..8ea9add --- /dev/null +++ b/middleware/explorer.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "github.com/HFO4/cloudreve/pkg/hashid" + "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/gin-gonic/gin" +) + +// HashID 将给定文件的HashID转换为真实ID +func HashID(IDType int) gin.HandlerFunc { + return func(c *gin.Context) { + if c.Param("id") != "" { + id, err := hashid.DecodeHashID(c.Param("id"), IDType) + if err == nil { + c.Set("object_id", id) + c.Next() + return + } + c.JSON(200, serializer.ParamErr("无法解析对象ID", nil)) + c.Abort() + return + + } + c.Next() + } +} diff --git a/models/file.go b/models/file.go index 17473a2..a5aa6ce 100644 --- a/models/file.go +++ b/models/file.go @@ -78,6 +78,21 @@ func GetFilesByIDs(ids []uint, uid uint) ([]File, error) { return files, result.Error } +// GetFilesByKeywords 根据关键字搜索文件, +// UID为0表示忽略用户,只根据文件ID检索 +// TODO 测试 +func GetFilesByKeywords(keywords string, uid uint) ([]File, error) { + var files []File + var result *gorm.DB + + if uid == 0 { + result = DB.Where("name like ?", keywords).Find(&files) + } else { + result = DB.Where("name like ? AND user_id = ?", keywords, uid).Find(&files) + } + return files, result.Error +} + // GetChildFilesOfFolders 批量检索目录子文件 func GetChildFilesOfFolders(folders *[]Folder) ([]File, error) { // 将所有待删除目录ID抽离,以便检索文件 diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index 330af0b..bea166b 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -94,8 +94,8 @@ func (fs *FileSystem) GetPhysicalFileContent(ctx context.Context, path string) ( // path - 文件虚拟路径 // isText - 是否为文本文件,文本文件会忽略重定向,直接由 // 服务端拉取中转给用户,故会对文件大小进行限制 -func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*response.ContentResponse, error) { - err := fs.resetFileIfNotExist(ctx, path) +func (fs *FileSystem) Preview(ctx context.Context, id uint, isText bool) (*response.ContentResponse, error) { + err := fs.resetFileIDIfNotExist(ctx, id) if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*r // 是否直接返回文件内容 if isText || fs.Policy.IsDirectlyPreview() { - resp, err := fs.GetDownloadContent(ctx, path) + resp, err := fs.GetDownloadContent(ctx, id) if err != nil { return nil, err } @@ -132,9 +132,9 @@ func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*r } // GetDownloadContent 获取用于下载的文件流 -func (fs *FileSystem) GetDownloadContent(ctx context.Context, path string) (response.RSCloser, error) { +func (fs *FileSystem) GetDownloadContent(ctx context.Context, id uint) (response.RSCloser, error) { // 获取原始文件流 - rs, err := fs.GetContent(ctx, path) + rs, err := fs.GetContent(ctx, id) if err != nil { return nil, err } @@ -145,7 +145,7 @@ func (fs *FileSystem) GetDownloadContent(ctx context.Context, path string) (resp } // GetContent 获取文件内容,path为虚拟路径 -func (fs *FileSystem) GetContent(ctx context.Context, path string) (response.RSCloser, error) { +func (fs *FileSystem) GetContent(ctx context.Context, id uint) (response.RSCloser, error) { // 触发`下载前`钩子 err := fs.Trigger(ctx, "BeforeFileDownload") if err != nil { @@ -153,7 +153,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (response.RSC return nil, err } - err = fs.resetFileIfNotExist(ctx, path) + err = fs.resetFileIDIfNotExist(ctx, id) if err != nil { return nil, err } @@ -218,8 +218,8 @@ func (fs *FileSystem) GroupFileByPolicy(ctx context.Context, files []model.File) } // GetDownloadURL 创建文件下载链接, timeout 为数据库中存储过期时间的字段 -func (fs *FileSystem) GetDownloadURL(ctx context.Context, path string, timeout string) (string, error) { - err := fs.resetFileIfNotExist(ctx, path) +func (fs *FileSystem) GetDownloadURL(ctx context.Context, id uint, timeout string) (string, error) { + err := fs.resetFileIDIfNotExist(ctx, id) if err != nil { return "", err } @@ -240,7 +240,7 @@ func (fs *FileSystem) GetDownloadURL(ctx context.Context, path string, timeout s return source, nil } -// Source 获取可直接访问文件的外链地址 +// GetSource 获取可直接访问文件的外链地址 func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error) { // 查找文件记录 err := fs.resetFileIDIfNotExist(ctx, fileID) @@ -340,3 +340,11 @@ func (fs *FileSystem) resetPolicyToFirstFile(ctx context.Context) error { } return nil } + +// Search 搜索文件 +func (fs *FileSystem) Search(ctx context.Context, keywords string) ([]Object, error) { + files, _ := model.GetFilesByKeywords(keywords, fs.User.ID) + fs.SetTargetFile(&files) + + return fs.listObjects(ctx, "/", files, nil, nil), nil +} diff --git a/pkg/filesystem/manage.go b/pkg/filesystem/manage.go index fded098..4ec9c25 100644 --- a/pkg/filesystem/manage.go +++ b/pkg/filesystem/manage.go @@ -5,6 +5,7 @@ import ( "fmt" model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "path" @@ -18,7 +19,7 @@ import ( // Object 文件或者目录 type Object struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Path string `json:"path"` Pic string `json:"pic"` @@ -262,36 +263,41 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu var childFolders []model.Folder var childFiles []model.File - // 分享文件的ID - shareKey := "" - if key, ok := ctx.Value(fsctx.ShareKeyCtx).(string); ok { - shareKey = key - } - // 获取子目录 childFolders, _ = folder.GetChildFolder() // 获取子文件 childFiles, _ = folder.GetChildFiles() + return fs.listObjects(ctx, parentPath, childFiles, childFolders, pathProcessor), nil +} + +func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []model.File, folders []model.Folder, pathProcessor func(string) string) []Object { + // 分享文件的ID + shareKey := "" + if key, ok := ctx.Value(fsctx.ShareKeyCtx).(string); ok { + shareKey = key + } + // 汇总处理结果 - objects := make([]Object, 0, len(childFiles)+len(childFolders)) + objects := make([]Object, 0, len(files)+len(folders)) + // 所有对象的父目录 var processedPath string - for _, subFolder := range childFolders { + for _, subFolder := range folders { // 路径处理钩子, // 所有对象父目录都是一样的,所以只处理一次 if processedPath == "" { if pathProcessor != nil { - processedPath = pathProcessor(parentPath) + processedPath = pathProcessor(parent) } else { - processedPath = parentPath + processedPath = parent } } objects = append(objects, Object{ - ID: subFolder.ID, + ID: hashid.HashID(subFolder.ID, hashid.FolderID), Name: subFolder.Name, Path: processedPath, Pic: "", @@ -301,17 +307,17 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu }) } - for _, file := range childFiles { + for _, file := range files { if processedPath == "" { if pathProcessor != nil { - processedPath = pathProcessor(parentPath) + processedPath = pathProcessor(parent) } else { - processedPath = parentPath + processedPath = parent } } newFile := Object{ - ID: file.ID, + ID: hashid.HashID(file.ID, hashid.FileID), Name: file.Name, Path: processedPath, Pic: file.PicInfo, @@ -325,7 +331,7 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu objects = append(objects, newFile) } - return objects, nil + return objects } // CreateDirectory 根据给定的完整创建目录,支持递归创建 diff --git a/pkg/hashid/hash.go b/pkg/hashid/hash.go index cd27167..14b4c10 100644 --- a/pkg/hashid/hash.go +++ b/pkg/hashid/hash.go @@ -8,8 +8,10 @@ import "github.com/speps/go-hashids" // ID类型 const ( - ShareID = iota // 分享 - UserID // 用户 + ShareID = iota // 分享 + UserID // 用户 + FileID // 文件ID + FolderID // 目录ID ) var ( diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index b220ab7..4229c2f 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -231,7 +231,13 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs * ctx := r.Context() - rs, err := fs.Preview(ctx, reqPath, false) + exist, file := fs.IsFileExist(reqPath) + if !exist { + return http.StatusNotFound, nil + } + fs.SetTargetFile(&[]model.File{*file}) + + rs, err := fs.Preview(ctx, 0, false) if err != nil { if err == filesystem.ErrObjectNotExist { return http.StatusNotFound, err diff --git a/routers/controllers/aria2.go b/routers/controllers/aria2.go index f26ecf5..2c6aade 100644 --- a/routers/controllers/aria2.go +++ b/routers/controllers/aria2.go @@ -3,11 +3,9 @@ package controllers import ( "context" ariaCall "github.com/HFO4/cloudreve/pkg/aria2" - "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/service/aria2" "github.com/HFO4/cloudreve/service/explorer" "github.com/gin-gonic/gin" - "strings" ) // AddAria2URL 添加离线下载URL @@ -38,15 +36,8 @@ func AddAria2Torrent(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { - // 验证必须是种子文件 - filePath := c.Param("path") - if !strings.HasSuffix(filePath, ".torrent") { - c.JSON(200, serializer.ParamErr("只能下载 .torrent 文件", nil)) - return - } - // 获取种子内容的下载地址 res := service.CreateDownloadSession(ctx, c) if res.Code != 0 { diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 3942466..7bd2dc6 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -38,7 +38,7 @@ func Archive(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.ItemService + var service explorer.ItemIDService if err := c.ShouldBindJSON(&service); err == nil { res := service.Archive(ctx, c) c.JSON(200, res) @@ -86,7 +86,7 @@ func AnonymousGetContent(c *gin.Context) { } } -// Source 获取文件的外链地址 +// GetSource 获取文件的外链地址 func GetSource(c *gin.Context) { // 创建上下文 ctx, cancel := context.WithCancel(context.Background()) @@ -100,13 +100,13 @@ func GetSource(c *gin.Context) { defer fs.Recycle() // 获取文件ID - fileID, err := strconv.ParseUint(c.Param("id"), 10, 32) - if err != nil { - c.JSON(200, serializer.ParamErr("无法解析文件ID", err)) + fileID, ok := c.Get("object_id") + if !ok { + c.JSON(200, serializer.ParamErr("文件不存在", err)) return } - sourceURL, err := fs.GetSource(ctx, uint(fileID)) + sourceURL, err := fs.GetSource(ctx, fileID.(uint)) if err != nil { c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) return @@ -135,14 +135,14 @@ func Thumb(c *gin.Context) { defer fs.Recycle() // 获取文件ID - fileID, err := strconv.ParseUint(c.Param("id"), 10, 32) - if err != nil { - c.JSON(200, serializer.ParamErr("无法解析文件ID", err)) + fileID, ok := c.Get("object_id") + if !ok { + c.JSON(200, serializer.ParamErr("文件不存在", err)) return } // 获取缩略图 - resp, err := fs.GetThumb(ctx, uint(fileID)) + resp, err := fs.GetThumb(ctx, fileID.(uint)) if err != nil { c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法获取缩略图", err)) return @@ -165,7 +165,7 @@ func Preview(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { res := service.PreviewContent(ctx, c, false) // 是否需要重定向 @@ -188,7 +188,7 @@ func PreviewText(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { res := service.PreviewContent(ctx, c, true) // 是否有错误发生 @@ -206,7 +206,7 @@ func GetDocPreview(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { res := service.CreateDocPreviewSession(ctx, c) c.JSON(200, res) @@ -221,7 +221,7 @@ func CreateDownloadSession(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { res := service.CreateDownloadSession(ctx, c) c.JSON(200, res) @@ -253,7 +253,7 @@ func PutContent(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.SingleFileService + var service explorer.FileIDService if err := c.ShouldBindUri(&service); err == nil { res := service.PutContent(ctx, c) c.JSON(200, res) @@ -344,9 +344,9 @@ func GetUploadCredential(c *gin.Context) { // SearchFile 搜索文件 func SearchFile(c *gin.Context) { - var service explorer.ItemDecompressService - if err := c.ShouldBindJSON(&service); err == nil { - res := service.CreateDecompressTask(c) + var service explorer.ItemSearchService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Search(c) c.JSON(200, res) } else { c.JSON(200, ErrorResponse(err)) diff --git a/routers/controllers/objects.go b/routers/controllers/objects.go index 044a942..267d1e0 100644 --- a/routers/controllers/objects.go +++ b/routers/controllers/objects.go @@ -12,7 +12,7 @@ func Delete(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.ItemService + var service explorer.ItemIDService if err := c.ShouldBindJSON(&service); err == nil { res := service.Delete(ctx, c) c.JSON(200, res) diff --git a/routers/router.go b/routers/router.go index 26958eb..9f730b2 100644 --- a/routers/router.go +++ b/routers/router.go @@ -3,6 +3,7 @@ package routers import ( "github.com/HFO4/cloudreve/middleware" "github.com/HFO4/cloudreve/pkg/conf" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/routers/controllers" "github.com/gin-contrib/cors" @@ -245,22 +246,22 @@ func InitMasterRouter() *gin.Engine { } // 文件 - file := auth.Group("file") + file := auth.Group("file", middleware.HashID(hashid.FileID)) { // 文件上传 file.POST("upload", controllers.FileUploadStream) // 获取上传凭证 file.GET("upload/credential", controllers.GetUploadCredential) // 更新文件 - file.PUT("update/*path", controllers.PutContent) + file.PUT("update/:id", controllers.PutContent) // 创建文件下载会话 - file.PUT("download/*path", controllers.CreateDownloadSession) + file.PUT("download/:id", controllers.CreateDownloadSession) // 预览文件 - file.GET("preview/*path", controllers.Preview) + file.GET("preview/:id", controllers.Preview) // 获取文本文件内容 - file.GET("content/*path", controllers.PreviewText) + file.GET("content/:id", controllers.PreviewText) // 取得Office文档预览地址 - file.GET("doc/*path", controllers.GetDocPreview) + file.GET("doc/:id", controllers.GetDocPreview) // 获取缩略图 file.GET("thumb/:id", controllers.Thumb) // 取得文件外链 @@ -281,7 +282,7 @@ func InitMasterRouter() *gin.Engine { // 创建URL下载任务 aria2.POST("url", controllers.AddAria2URL) // 创建种子下载任务 - aria2.POST("torrent/*path", controllers.AddAria2Torrent) + aria2.POST("torrent/:id", middleware.HashID(hashid.FileID), controllers.AddAria2Torrent) // 重新选择要下载的文件 aria2.PUT("select/:gid", controllers.SelectAria2File) // 取消下载任务 diff --git a/service/explorer/directory.go b/service/explorer/directory.go index f981536..fffc585 100644 --- a/service/explorer/directory.go +++ b/service/explorer/directory.go @@ -3,6 +3,7 @@ package explorer import ( "context" "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" ) @@ -39,7 +40,7 @@ func (service *DirectoryService) ListDirectory(c *gin.Context) serializer.Respon return serializer.Response{ Code: 0, Data: map[string]interface{}{ - "parent": parentID, + "parent": hashid.HashID(parentID, hashid.FolderID), "objects": objects, }, } diff --git a/service/explorer/file.go b/service/explorer/file.go index ea38c2c..1e3cb64 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -15,7 +15,6 @@ import ( "github.com/jinzhu/gorm" "net/http" "net/url" - "path" "strconv" "time" ) @@ -25,6 +24,10 @@ type SingleFileService struct { Path string `uri:"path" binding:"required,min=1,max=65535"` } +// FileIDService 通过文件ID对文件进行操作的服务 +type FileIDService struct { +} + // FileAnonymousGetService 匿名(外链)获取文件服务 type FileAnonymousGetService struct { ID uint `uri:"id" binding:"required,min=1"` @@ -105,7 +108,7 @@ func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Con } // 获取文件流 - rs, err := fs.GetDownloadContent(ctx, "") + rs, err := fs.GetDownloadContent(ctx, 0) defer rs.Close() if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) @@ -120,7 +123,7 @@ func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Con } // CreateDocPreviewSession 创建DOC文件预览会话,返回预览地址 -func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c *gin.Context) serializer.Response { +func (service *FileIDService) CreateDocPreviewSession(ctx context.Context, c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -138,8 +141,11 @@ func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c fs.Root = folder } + // 获取对象id + objectID, _ := c.Get("object_id") + // 获取文件临时下载地址 - downloadURL, err := fs.GetDownloadURL(ctx, service.Path, "doc_preview_timeout") + downloadURL, err := fs.GetDownloadURL(ctx, objectID.(uint), "doc_preview_timeout") if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -158,7 +164,7 @@ func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c } // CreateDownloadSession 创建下载会话,获取下载URL -func (service *SingleFileService) CreateDownloadSession(ctx context.Context, c *gin.Context) serializer.Response { +func (service *FileIDService) CreateDownloadSession(ctx context.Context, c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -166,8 +172,11 @@ func (service *SingleFileService) CreateDownloadSession(ctx context.Context, c * } defer fs.Recycle() + // 获取对象id + objectID, _ := c.Get("object_id") + // 获取下载地址 - downloadURL, err := fs.GetDownloadURL(ctx, service.Path, "download_timeout") + downloadURL, err := fs.GetDownloadURL(ctx, objectID.(uint), "download_timeout") if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -196,7 +205,7 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se // 开始处理下载 ctx = context.WithValue(ctx, fsctx.GinCtx, c) - rs, err := fs.GetDownloadContent(ctx, "") + rs, err := fs.GetDownloadContent(ctx, 0) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -220,7 +229,7 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se // PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会 // 强制经由服务端中转 -func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response { +func (service *FileIDService) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -238,8 +247,11 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con fs.Root = folder } + // 获取对象id + objectID, _ := c.Get("object_id") + // 获取文件预览响应 - resp, err := fs.Preview(ctx, service.Path, isText) + resp, err := fs.Preview(ctx, objectID.(uint), isText) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -267,7 +279,7 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con } // PutContent 更新文件内容 -func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context) serializer.Response { +func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) serializer.Response { // 创建上下文 ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -280,11 +292,9 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context } fileData := local.FileStream{ - MIMEType: c.Request.Header.Get("Content-Type"), - File: c.Request.Body, - Size: fileSize, - Name: path.Base(service.Path), - VirtualPath: path.Dir(service.Path), + MIMEType: c.Request.Header.Get("Content-Type"), + File: c.Request.Body, + Size: fileSize, } // 创建文件系统 @@ -295,16 +305,18 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) // 取得现有文件 - exist, originFile := fs.IsFileExist(service.Path) - if !exist { + fileID, _ := c.Get("object_id") + originFile, _ := model.GetFilesByIDs([]uint{fileID.(uint)}, fs.User.ID) + if len(originFile) == 0 { return serializer.Err(404, "文件不存在", nil) } + fileData.Name = originFile[0].Name // 检查此文件是否有软链接 - fileList, err := model.RemoveFilesWithSoftLinks([]model.File{*originFile}) + fileList, err := model.RemoveFilesWithSoftLinks([]model.File{originFile[0]}) if err == nil && len(fileList) == 0 { // 如果包含软连接,应重新生成新文件副本,并更新source_name - originFile.SourceName = fs.GenerateSavePath(uploadCtx, fileData) + originFile[0].SourceName = fs.GenerateSavePath(uploadCtx, fileData) fs.Use("AfterUpload", filesystem.HookUpdateSourceName) fs.Use("AfterUploadCanceled", filesystem.HookUpdateSourceName) fs.Use("AfterValidateFailed", filesystem.HookUpdateSourceName) @@ -323,7 +335,7 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity) // 执行上传 - uploadCtx = context.WithValue(uploadCtx, fsctx.FileModelCtx, *originFile) + uploadCtx = context.WithValue(uploadCtx, fsctx.FileModelCtx, originFile[0]) err = fs.Upload(uploadCtx, fileData) if err != nil { return serializer.Err(serializer.CodeUploadFailed, err.Error(), err) @@ -365,7 +377,7 @@ func (service *SlaveDownloadService) ServeFile(ctx context.Context, c *gin.Conte // 开始处理下载 ctx = context.WithValue(ctx, fsctx.GinCtx, c) - rs, err := fs.GetDownloadContent(ctx, "") + rs, err := fs.GetDownloadContent(ctx, 0) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } diff --git a/service/explorer/objects.go b/service/explorer/objects.go index 3a023b1..e042393 100644 --- a/service/explorer/objects.go +++ b/service/explorer/objects.go @@ -8,6 +8,7 @@ import ( "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/filesystem" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/task" "github.com/HFO4/cloudreve/pkg/util" @@ -21,15 +22,15 @@ import ( // ItemMoveService 处理多文件/目录移动 type ItemMoveService struct { - SrcDir string `json:"src_dir" binding:"required,min=1,max=65535"` - Src ItemService `json:"src" binding:"exists"` - Dst string `json:"dst" binding:"required,min=1,max=65535"` + SrcDir string `json:"src_dir" binding:"required,min=1,max=65535"` + Src ItemIDService `json:"src" binding:"exists"` + Dst string `json:"dst" binding:"required,min=1,max=65535"` } // ItemRenameService 处理多文件/目录重命名 type ItemRenameService struct { - Src ItemService `json:"src" binding:"exists"` - NewName string `json:"new_name" binding:"required,min=1,max=255"` + Src ItemIDService `json:"src" binding:"exists"` + NewName string `json:"new_name" binding:"required,min=1,max=255"` } // ItemService 处理多文件/目录相关服务 @@ -38,11 +39,18 @@ type ItemService struct { Dirs []uint `json:"dirs" binding:"exists"` } +// ItemIDService 处理多文件/目录相关服务,字段值为HashID,可通过Raw()方法获取原始ID +type ItemIDService struct { + Items []string `json:"items" binding:"exists"` + Dirs []string `json:"dirs" binding:"exists"` + Source *ItemService +} + // ItemCompressService 文件压缩任务服务 type ItemCompressService struct { - Src ItemService `json:"src" binding:"exists"` - Dst string `json:"dst" binding:"required,min=1,max=65535"` - Name string `json:"name" binding:"required,min=1,max=255"` + Src ItemIDService `json:"src" binding:"exists"` + Dst string `json:"dst" binding:"required,min=1,max=65535"` + Name string `json:"name" binding:"required,min=1,max=255"` } // ItemDecompressService 文件解压缩任务服务 @@ -51,6 +59,32 @@ type ItemDecompressService struct { Dst string `json:"dst" binding:"required,min=1,max=65535"` } +// Raw 批量解码HashID,获取原始ID +func (service *ItemIDService) Raw() *ItemService { + if service.Source != nil { + return service.Source + } + + service.Source = &ItemService{ + Dirs: make([]uint, 0, len(service.Dirs)), + Items: make([]uint, 0, len(service.Items)), + } + for _, folder := range service.Dirs { + id, err := hashid.DecodeHashID(folder, hashid.FolderID) + if err == nil { + service.Source.Dirs = append(service.Source.Dirs, id) + } + } + for _, file := range service.Items { + id, err := hashid.DecodeHashID(file, hashid.FileID) + if err == nil { + service.Source.Items = append(service.Source.Items, id) + } + } + + return service.Source +} + // CreateDecompressTask 创建文件解压缩任务 func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) serializer.Response { // 创建文件系统 @@ -129,7 +163,7 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize } // 递归列出待压缩子目录 - folders, err := model.GetRecursiveChildFolder(service.Src.Dirs, fs.User.ID, true) + folders, err := model.GetRecursiveChildFolder(service.Src.Raw().Dirs, fs.User.ID, true) if err != nil { return serializer.Err(serializer.CodeDBError, "无法列出子目录", err) } @@ -160,8 +194,8 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize } // 创建任务 - job, err := task.NewCompressTask(fs.User, path.Join(service.Dst, service.Name), service.Src.Dirs, - service.Src.Items) + job, err := task.NewCompressTask(fs.User, path.Join(service.Dst, service.Name), service.Src.Raw().Dirs, + service.Src.Raw().Items) if err != nil { return serializer.Err(serializer.CodeNotSet, "任务创建失败", err) } @@ -172,7 +206,7 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize } // Archive 创建归档 -func (service *ItemService) Archive(ctx context.Context, c *gin.Context) serializer.Response { +func (service *ItemIDService) Archive(ctx context.Context, c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -187,7 +221,8 @@ func (service *ItemService) Archive(ctx context.Context, c *gin.Context) seriali // 开始压缩 ctx = context.WithValue(ctx, fsctx.GinCtx, c) - zipFile, err := fs.Compress(ctx, service.Dirs, service.Items, true) + items := service.Raw() + zipFile, err := fs.Compress(ctx, items.Dirs, items.Items, true) if err != nil { return serializer.Err(serializer.CodeNotSet, "无法创建压缩文件", err) } @@ -219,7 +254,7 @@ func (service *ItemService) Archive(ctx context.Context, c *gin.Context) seriali } // Delete 删除对象 -func (service *ItemService) Delete(ctx context.Context, c *gin.Context) serializer.Response { +func (service *ItemIDService) Delete(ctx context.Context, c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -228,7 +263,8 @@ func (service *ItemService) Delete(ctx context.Context, c *gin.Context) serializ defer fs.Recycle() // 删除对象 - err = fs.Delete(ctx, service.Dirs, service.Items) + items := service.Raw() + err = fs.Delete(ctx, items.Dirs, items.Items) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -249,7 +285,8 @@ func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serial defer fs.Recycle() // 移动对象 - err = fs.Move(ctx, service.Src.Dirs, service.Src.Items, service.SrcDir, service.Dst) + items := service.Src.Raw() + err = fs.Move(ctx, items.Dirs, items.Items, service.SrcDir, service.Dst) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -275,7 +312,7 @@ func (service *ItemMoveService) Copy(ctx context.Context, c *gin.Context) serial defer fs.Recycle() // 复制对象 - err = fs.Copy(ctx, service.Src.Dirs, service.Src.Items, service.SrcDir, service.Dst) + err = fs.Copy(ctx, service.Src.Raw().Dirs, service.Src.Raw().Items, service.SrcDir, service.Dst) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -301,7 +338,7 @@ func (service *ItemRenameService) Rename(ctx context.Context, c *gin.Context) se defer fs.Recycle() // 重命名对象 - err = fs.Rename(ctx, service.Src.Dirs, service.Src.Items, service.NewName) + err = fs.Rename(ctx, service.Src.Raw().Dirs, service.Src.Raw().Items, service.NewName) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } diff --git a/service/explorer/search.go b/service/explorer/search.go new file mode 100644 index 0000000..fc63930 --- /dev/null +++ b/service/explorer/search.go @@ -0,0 +1,52 @@ +package explorer + +import ( + "context" + "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/gin-gonic/gin" +) + +// ItemSearchService 文件搜索服务 +type ItemSearchService struct { + Type string `uri:"type" binding:"required"` + Keywords string `uri:"keywords" binding:"required"` +} + +// Search 执行搜索 +func (service *ItemSearchService) Search(c *gin.Context) serializer.Response { + // 创建文件系统 + fs, err := filesystem.NewFileSystemFromContext(c) + if err != nil { + return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) + } + defer fs.Recycle() + + switch service.Type { + case "keywords": + return service.SearchKeywords(c, "%"+service.Keywords+"%", fs) + default: + return serializer.ParamErr("未知搜索类型", nil) + } +} + +// SearchKeywords 根据关键字搜索文件 +func (service *ItemSearchService) SearchKeywords(c *gin.Context, keywords string, fs *filesystem.FileSystem) serializer.Response { + // 上下文 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // 获取子项目 + objects, err := fs.Search(ctx, keywords) + if err != nil { + return serializer.Err(serializer.CodeNotSet, err.Error(), err) + } + + return serializer.Response{ + Code: 0, + Data: map[string]interface{}{ + "parent": 0, + "objects": objects, + }, + } +} diff --git a/service/share/manage.go b/service/share/manage.go index 65c01a2..48f4452 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -11,7 +11,7 @@ import ( // ShareCreateService 创建新分享服务 type ShareCreateService struct { - SourceID uint `json:"id" binding:"required"` + SourceID string `json:"id" binding:"required"` IsDir bool `json:"is_dir"` Password string `json:"password" binding:"max=255"` RemainDownloads int `json:"downloads"` @@ -30,15 +30,29 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { return serializer.Err(serializer.CodeNoPermissionErr, "您无权创建分享链接", nil) } + // 源对象真实ID + var ( + sourceID uint + 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{service.SourceID}, user.ID) + folder, err := model.GetFoldersByIDs([]uint{sourceID}, user.ID) if err != nil || len(folder) == 0 { exist = false } } else { - file, err := model.GetFilesByIDs([]uint{service.SourceID}, user.ID) + file, err := model.GetFilesByIDs([]uint{sourceID}, user.ID) if err != nil || len(file) == 0 { exist = false } @@ -51,7 +65,7 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response { Password: service.Password, IsDir: service.IsDir, UserID: user.ID, - SourceID: service.SourceID, + SourceID: sourceID, Score: service.Score, RemainDownloads: -1, PreviewEnabled: service.Preview, diff --git a/service/share/visit.go b/service/share/visit.go index 1737143..2ea9e69 100644 --- a/service/share/visit.go +++ b/service/share/visit.go @@ -96,7 +96,8 @@ func (service *Service) CreateDownloadSession(c *gin.Context) serializer.Respons } // 取得下载地址 - downloadURL, err := fs.GetDownloadURL(context.Background(), service.Path, "download_timeout") + // TODO 改为真实ID + downloadURL, err := fs.GetDownloadURL(context.Background(), 0, "download_timeout") if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } @@ -119,9 +120,7 @@ func (service *Service) PreviewContent(ctx context.Context, c *gin.Context, isTe } else { ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) } - subService := explorer.SingleFileService{ - Path: service.Path, - } + subService := explorer.FileIDService{} return subService.PreviewContent(ctx, c, isText) } @@ -138,9 +137,7 @@ func (service *Service) CreateDocPreviewSession(c *gin.Context) serializer.Respo } else { ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) } - subService := explorer.SingleFileService{ - Path: service.Path, - } + subService := explorer.FileIDService{} return subService.CreateDocPreviewSession(ctx, c) } @@ -321,10 +318,8 @@ func (service *ArchiveService) Archive(c *gin.Context) serializer.Response { tempUser.Group.OptionsSerialized.ArchiveDownload = true c.Set("user", tempUser) - subService := explorer.ItemService{ - Items: service.Items, - Dirs: service.Dirs, - } + // todo 改成真实 + subService := explorer.ItemIDService{} return subService.Archive(ctx, c) }