Modify: change raw object ID to Hash ID in file service
This commit is contained in:
parent
f235ad1def
commit
9be1b4366f
16 changed files with 287 additions and 121 deletions
26
middleware/explorer.go
Normal file
26
middleware/explorer.go
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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抽离,以便检索文件
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 根据给定的完整创建目录,支持递归创建
|
||||
|
|
|
@ -8,8 +8,10 @@ import "github.com/speps/go-hashids"
|
|||
|
||||
// ID类型
|
||||
const (
|
||||
ShareID = iota // 分享
|
||||
UserID // 用户
|
||||
ShareID = iota // 分享
|
||||
UserID // 用户
|
||||
FileID // 文件ID
|
||||
FolderID // 目录ID
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
// 取消下载任务
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
52
service/explorer/search.go
Normal file
52
service/explorer/search.go
Normal file
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue