Modify: change raw object ID to Hash ID in file service

pull/247/head
HFO4 4 years ago
parent f235ad1def
commit 9be1b4366f

@ -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 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 批量检索目录子文件 // GetChildFilesOfFolders 批量检索目录子文件
func GetChildFilesOfFolders(folders *[]Folder) ([]File, error) { func GetChildFilesOfFolders(folders *[]Folder) ([]File, error) {
// 将所有待删除目录ID抽离以便检索文件 // 将所有待删除目录ID抽离以便检索文件

@ -94,8 +94,8 @@ func (fs *FileSystem) GetPhysicalFileContent(ctx context.Context, path string) (
// path - 文件虚拟路径 // path - 文件虚拟路径
// isText - 是否为文本文件,文本文件会忽略重定向,直接由 // isText - 是否为文本文件,文本文件会忽略重定向,直接由
// 服务端拉取中转给用户,故会对文件大小进行限制 // 服务端拉取中转给用户,故会对文件大小进行限制
func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*response.ContentResponse, error) { func (fs *FileSystem) Preview(ctx context.Context, id uint, isText bool) (*response.ContentResponse, error) {
err := fs.resetFileIfNotExist(ctx, path) err := fs.resetFileIDIfNotExist(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -108,7 +108,7 @@ func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*r
// 是否直接返回文件内容 // 是否直接返回文件内容
if isText || fs.Policy.IsDirectlyPreview() { if isText || fs.Policy.IsDirectlyPreview() {
resp, err := fs.GetDownloadContent(ctx, path) resp, err := fs.GetDownloadContent(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,9 +132,9 @@ func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*r
} }
// GetDownloadContent 获取用于下载的文件流 // 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 { if err != nil {
return nil, err return nil, err
} }
@ -145,7 +145,7 @@ func (fs *FileSystem) GetDownloadContent(ctx context.Context, path string) (resp
} }
// GetContent 获取文件内容path为虚拟路径 // 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") err := fs.Trigger(ctx, "BeforeFileDownload")
if err != nil { if err != nil {
@ -153,7 +153,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (response.RSC
return nil, err return nil, err
} }
err = fs.resetFileIfNotExist(ctx, path) err = fs.resetFileIDIfNotExist(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -218,8 +218,8 @@ func (fs *FileSystem) GroupFileByPolicy(ctx context.Context, files []model.File)
} }
// GetDownloadURL 创建文件下载链接, timeout 为数据库中存储过期时间的字段 // GetDownloadURL 创建文件下载链接, timeout 为数据库中存储过期时间的字段
func (fs *FileSystem) GetDownloadURL(ctx context.Context, path string, timeout string) (string, error) { func (fs *FileSystem) GetDownloadURL(ctx context.Context, id uint, timeout string) (string, error) {
err := fs.resetFileIfNotExist(ctx, path) err := fs.resetFileIDIfNotExist(ctx, id)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -240,7 +240,7 @@ func (fs *FileSystem) GetDownloadURL(ctx context.Context, path string, timeout s
return source, nil return source, nil
} }
// Source 获取可直接访问文件的外链地址 // GetSource 获取可直接访问文件的外链地址
func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error) { func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error) {
// 查找文件记录 // 查找文件记录
err := fs.resetFileIDIfNotExist(ctx, fileID) err := fs.resetFileIDIfNotExist(ctx, fileID)
@ -340,3 +340,11 @@ func (fs *FileSystem) resetPolicyToFirstFile(ctx context.Context) error {
} }
return nil 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" "fmt"
model "github.com/HFO4/cloudreve/models" model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"path" "path"
@ -18,7 +19,7 @@ import (
// Object 文件或者目录 // Object 文件或者目录
type Object struct { type Object struct {
ID uint `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
Pic string `json:"pic"` Pic string `json:"pic"`
@ -262,36 +263,41 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu
var childFolders []model.Folder var childFolders []model.Folder
var childFiles []model.File var childFiles []model.File
// 分享文件的ID
shareKey := ""
if key, ok := ctx.Value(fsctx.ShareKeyCtx).(string); ok {
shareKey = key
}
// 获取子目录 // 获取子目录
childFolders, _ = folder.GetChildFolder() childFolders, _ = folder.GetChildFolder()
// 获取子文件 // 获取子文件
childFiles, _ = folder.GetChildFiles() 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 var processedPath string
for _, subFolder := range childFolders { for _, subFolder := range folders {
// 路径处理钩子, // 路径处理钩子,
// 所有对象父目录都是一样的,所以只处理一次 // 所有对象父目录都是一样的,所以只处理一次
if processedPath == "" { if processedPath == "" {
if pathProcessor != nil { if pathProcessor != nil {
processedPath = pathProcessor(parentPath) processedPath = pathProcessor(parent)
} else { } else {
processedPath = parentPath processedPath = parent
} }
} }
objects = append(objects, Object{ objects = append(objects, Object{
ID: subFolder.ID, ID: hashid.HashID(subFolder.ID, hashid.FolderID),
Name: subFolder.Name, Name: subFolder.Name,
Path: processedPath, Path: processedPath,
Pic: "", 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 processedPath == "" {
if pathProcessor != nil { if pathProcessor != nil {
processedPath = pathProcessor(parentPath) processedPath = pathProcessor(parent)
} else { } else {
processedPath = parentPath processedPath = parent
} }
} }
newFile := Object{ newFile := Object{
ID: file.ID, ID: hashid.HashID(file.ID, hashid.FileID),
Name: file.Name, Name: file.Name,
Path: processedPath, Path: processedPath,
Pic: file.PicInfo, Pic: file.PicInfo,
@ -325,7 +331,7 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu
objects = append(objects, newFile) objects = append(objects, newFile)
} }
return objects, nil return objects
} }
// CreateDirectory 根据给定的完整创建目录,支持递归创建 // CreateDirectory 根据给定的完整创建目录,支持递归创建

@ -8,8 +8,10 @@ import "github.com/speps/go-hashids"
// ID类型 // ID类型
const ( const (
ShareID = iota // 分享 ShareID = iota // 分享
UserID // 用户 UserID // 用户
FileID // 文件ID
FolderID // 目录ID
) )
var ( var (

@ -231,7 +231,13 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
ctx := r.Context() 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 != nil {
if err == filesystem.ErrObjectNotExist { if err == filesystem.ErrObjectNotExist {
return http.StatusNotFound, err return http.StatusNotFound, err

@ -3,11 +3,9 @@ package controllers
import ( import (
"context" "context"
ariaCall "github.com/HFO4/cloudreve/pkg/aria2" ariaCall "github.com/HFO4/cloudreve/pkg/aria2"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/service/aria2" "github.com/HFO4/cloudreve/service/aria2"
"github.com/HFO4/cloudreve/service/explorer" "github.com/HFO4/cloudreve/service/explorer"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strings"
) )
// AddAria2URL 添加离线下载URL // AddAria2URL 添加离线下载URL
@ -38,15 +36,8 @@ func AddAria2Torrent(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { 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) res := service.CreateDownloadSession(ctx, c)
if res.Code != 0 { if res.Code != 0 {

@ -38,7 +38,7 @@ func Archive(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.ItemService var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil { if err := c.ShouldBindJSON(&service); err == nil {
res := service.Archive(ctx, c) res := service.Archive(ctx, c)
c.JSON(200, res) c.JSON(200, res)
@ -86,7 +86,7 @@ func AnonymousGetContent(c *gin.Context) {
} }
} }
// Source 获取文件的外链地址 // GetSource 获取文件的外链地址
func GetSource(c *gin.Context) { func GetSource(c *gin.Context) {
// 创建上下文 // 创建上下文
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -100,13 +100,13 @@ func GetSource(c *gin.Context) {
defer fs.Recycle() defer fs.Recycle()
// 获取文件ID // 获取文件ID
fileID, err := strconv.ParseUint(c.Param("id"), 10, 32) fileID, ok := c.Get("object_id")
if err != nil { if !ok {
c.JSON(200, serializer.ParamErr("无法解析文件ID", err)) c.JSON(200, serializer.ParamErr("文件不存在", err))
return return
} }
sourceURL, err := fs.GetSource(ctx, uint(fileID)) sourceURL, err := fs.GetSource(ctx, fileID.(uint))
if err != nil { if err != nil {
c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err)) c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err))
return return
@ -135,14 +135,14 @@ func Thumb(c *gin.Context) {
defer fs.Recycle() defer fs.Recycle()
// 获取文件ID // 获取文件ID
fileID, err := strconv.ParseUint(c.Param("id"), 10, 32) fileID, ok := c.Get("object_id")
if err != nil { if !ok {
c.JSON(200, serializer.ParamErr("无法解析文件ID", err)) c.JSON(200, serializer.ParamErr("文件不存在", err))
return return
} }
// 获取缩略图 // 获取缩略图
resp, err := fs.GetThumb(ctx, uint(fileID)) resp, err := fs.GetThumb(ctx, fileID.(uint))
if err != nil { if err != nil {
c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法获取缩略图", err)) c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法获取缩略图", err))
return return
@ -165,7 +165,7 @@ func Preview(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.PreviewContent(ctx, c, false) res := service.PreviewContent(ctx, c, false)
// 是否需要重定向 // 是否需要重定向
@ -188,7 +188,7 @@ func PreviewText(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.PreviewContent(ctx, c, true) res := service.PreviewContent(ctx, c, true)
// 是否有错误发生 // 是否有错误发生
@ -206,7 +206,7 @@ func GetDocPreview(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDocPreviewSession(ctx, c) res := service.CreateDocPreviewSession(ctx, c)
c.JSON(200, res) c.JSON(200, res)
@ -221,7 +221,7 @@ func CreateDownloadSession(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDownloadSession(ctx, c) res := service.CreateDownloadSession(ctx, c)
c.JSON(200, res) c.JSON(200, res)
@ -253,7 +253,7 @@ func PutContent(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.SingleFileService var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.PutContent(ctx, c) res := service.PutContent(ctx, c)
c.JSON(200, res) c.JSON(200, res)
@ -344,9 +344,9 @@ func GetUploadCredential(c *gin.Context) {
// SearchFile 搜索文件 // SearchFile 搜索文件
func SearchFile(c *gin.Context) { func SearchFile(c *gin.Context) {
var service explorer.ItemDecompressService var service explorer.ItemSearchService
if err := c.ShouldBindJSON(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDecompressTask(c) res := service.Search(c)
c.JSON(200, res) c.JSON(200, res)
} else { } else {
c.JSON(200, ErrorResponse(err)) c.JSON(200, ErrorResponse(err))

@ -12,7 +12,7 @@ func Delete(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.ItemService var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil { if err := c.ShouldBindJSON(&service); err == nil {
res := service.Delete(ctx, c) res := service.Delete(ctx, c)
c.JSON(200, res) c.JSON(200, res)

@ -3,6 +3,7 @@ package routers
import ( import (
"github.com/HFO4/cloudreve/middleware" "github.com/HFO4/cloudreve/middleware"
"github.com/HFO4/cloudreve/pkg/conf" "github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/routers/controllers" "github.com/HFO4/cloudreve/routers/controllers"
"github.com/gin-contrib/cors" "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.POST("upload", controllers.FileUploadStream)
// 获取上传凭证 // 获取上传凭证
file.GET("upload/credential", controllers.GetUploadCredential) 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文档预览地址 // 取得Office文档预览地址
file.GET("doc/*path", controllers.GetDocPreview) file.GET("doc/:id", controllers.GetDocPreview)
// 获取缩略图 // 获取缩略图
file.GET("thumb/:id", controllers.Thumb) file.GET("thumb/:id", controllers.Thumb)
// 取得文件外链 // 取得文件外链
@ -281,7 +282,7 @@ func InitMasterRouter() *gin.Engine {
// 创建URL下载任务 // 创建URL下载任务
aria2.POST("url", controllers.AddAria2URL) 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) aria2.PUT("select/:gid", controllers.SelectAria2File)
// 取消下载任务 // 取消下载任务

@ -3,6 +3,7 @@ package explorer
import ( import (
"context" "context"
"github.com/HFO4/cloudreve/pkg/filesystem" "github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -39,7 +40,7 @@ func (service *DirectoryService) ListDirectory(c *gin.Context) serializer.Respon
return serializer.Response{ return serializer.Response{
Code: 0, Code: 0,
Data: map[string]interface{}{ Data: map[string]interface{}{
"parent": parentID, "parent": hashid.HashID(parentID, hashid.FolderID),
"objects": objects, "objects": objects,
}, },
} }

@ -15,7 +15,6 @@ import (
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"net/http" "net/http"
"net/url" "net/url"
"path"
"strconv" "strconv"
"time" "time"
) )
@ -25,6 +24,10 @@ type SingleFileService struct {
Path string `uri:"path" binding:"required,min=1,max=65535"` Path string `uri:"path" binding:"required,min=1,max=65535"`
} }
// FileIDService 通过文件ID对文件进行操作的服务
type FileIDService struct {
}
// FileAnonymousGetService 匿名(外链)获取文件服务 // FileAnonymousGetService 匿名(外链)获取文件服务
type FileAnonymousGetService struct { type FileAnonymousGetService struct {
ID uint `uri:"id" binding:"required,min=1"` 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() defer rs.Close()
if err != nil { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
@ -120,7 +123,7 @@ func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Con
} }
// CreateDocPreviewSession 创建DOC文件预览会话返回预览地址 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -138,8 +141,11 @@ func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c
fs.Root = folder 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }
@ -158,7 +164,7 @@ func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c
} }
// CreateDownloadSession 创建下载会话获取下载URL // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -166,8 +172,11 @@ func (service *SingleFileService) CreateDownloadSession(ctx context.Context, c *
} }
defer fs.Recycle() 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) 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) ctx = context.WithValue(ctx, fsctx.GinCtx, c)
rs, err := fs.GetDownloadContent(ctx, "") rs, err := fs.GetDownloadContent(ctx, 0)
if err != nil { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) 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 - 是否为文本文件,文本文件会 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -238,8 +247,11 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con
fs.Root = folder 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }
@ -267,7 +279,7 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con
} }
// PutContent 更新文件内容 // 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()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -280,11 +292,9 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context
} }
fileData := local.FileStream{ fileData := local.FileStream{
MIMEType: c.Request.Header.Get("Content-Type"), MIMEType: c.Request.Header.Get("Content-Type"),
File: c.Request.Body, File: c.Request.Body,
Size: fileSize, Size: fileSize,
Name: path.Base(service.Path),
VirtualPath: path.Dir(service.Path),
} }
// 创建文件系统 // 创建文件系统
@ -295,16 +305,18 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context
uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c)
// 取得现有文件 // 取得现有文件
exist, originFile := fs.IsFileExist(service.Path) fileID, _ := c.Get("object_id")
if !exist { originFile, _ := model.GetFilesByIDs([]uint{fileID.(uint)}, fs.User.ID)
if len(originFile) == 0 {
return serializer.Err(404, "文件不存在", nil) 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 { if err == nil && len(fileList) == 0 {
// 如果包含软连接应重新生成新文件副本并更新source_name // 如果包含软连接应重新生成新文件副本并更新source_name
originFile.SourceName = fs.GenerateSavePath(uploadCtx, fileData) originFile[0].SourceName = fs.GenerateSavePath(uploadCtx, fileData)
fs.Use("AfterUpload", filesystem.HookUpdateSourceName) fs.Use("AfterUpload", filesystem.HookUpdateSourceName)
fs.Use("AfterUploadCanceled", filesystem.HookUpdateSourceName) fs.Use("AfterUploadCanceled", filesystem.HookUpdateSourceName)
fs.Use("AfterValidateFailed", 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) 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) err = fs.Upload(uploadCtx, fileData)
if err != nil { if err != nil {
return serializer.Err(serializer.CodeUploadFailed, err.Error(), err) 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) ctx = context.WithValue(ctx, fsctx.GinCtx, c)
rs, err := fs.GetDownloadContent(ctx, "") rs, err := fs.GetDownloadContent(ctx, 0)
if err != nil { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }

@ -8,6 +8,7 @@ import (
"github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem" "github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/task" "github.com/HFO4/cloudreve/pkg/task"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
@ -21,15 +22,15 @@ import (
// ItemMoveService 处理多文件/目录移动 // ItemMoveService 处理多文件/目录移动
type ItemMoveService struct { type ItemMoveService struct {
SrcDir string `json:"src_dir" binding:"required,min=1,max=65535"` SrcDir string `json:"src_dir" binding:"required,min=1,max=65535"`
Src ItemService `json:"src" binding:"exists"` Src ItemIDService `json:"src" binding:"exists"`
Dst string `json:"dst" binding:"required,min=1,max=65535"` Dst string `json:"dst" binding:"required,min=1,max=65535"`
} }
// ItemRenameService 处理多文件/目录重命名 // ItemRenameService 处理多文件/目录重命名
type ItemRenameService struct { type ItemRenameService struct {
Src ItemService `json:"src" binding:"exists"` Src ItemIDService `json:"src" binding:"exists"`
NewName string `json:"new_name" binding:"required,min=1,max=255"` NewName string `json:"new_name" binding:"required,min=1,max=255"`
} }
// ItemService 处理多文件/目录相关服务 // ItemService 处理多文件/目录相关服务
@ -38,11 +39,18 @@ type ItemService struct {
Dirs []uint `json:"dirs" binding:"exists"` 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 文件压缩任务服务 // ItemCompressService 文件压缩任务服务
type ItemCompressService struct { type ItemCompressService struct {
Src ItemService `json:"src" binding:"exists"` Src ItemIDService `json:"src" binding:"exists"`
Dst string `json:"dst" binding:"required,min=1,max=65535"` Dst string `json:"dst" binding:"required,min=1,max=65535"`
Name string `json:"name" binding:"required,min=1,max=255"` Name string `json:"name" binding:"required,min=1,max=255"`
} }
// ItemDecompressService 文件解压缩任务服务 // ItemDecompressService 文件解压缩任务服务
@ -51,6 +59,32 @@ type ItemDecompressService struct {
Dst string `json:"dst" binding:"required,min=1,max=65535"` 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 创建文件解压缩任务 // CreateDecompressTask 创建文件解压缩任务
func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) serializer.Response { 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 { if err != nil {
return serializer.Err(serializer.CodeDBError, "无法列出子目录", err) 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, job, err := task.NewCompressTask(fs.User, path.Join(service.Dst, service.Name), service.Src.Raw().Dirs,
service.Src.Items) service.Src.Raw().Items)
if err != nil { if err != nil {
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err) return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
} }
@ -172,7 +206,7 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
} }
// Archive 创建归档 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { 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) 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, "无法创建压缩文件", err) return serializer.Err(serializer.CodeNotSet, "无法创建压缩文件", err)
} }
@ -219,7 +254,7 @@ func (service *ItemService) Archive(ctx context.Context, c *gin.Context) seriali
} }
// Delete 删除对象 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -228,7 +263,8 @@ func (service *ItemService) Delete(ctx context.Context, c *gin.Context) serializ
defer fs.Recycle() 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) 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() 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) 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() 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) 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() 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }

@ -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 创建新分享服务 // ShareCreateService 创建新分享服务
type ShareCreateService struct { type ShareCreateService struct {
SourceID uint `json:"id" binding:"required"` SourceID string `json:"id" binding:"required"`
IsDir bool `json:"is_dir"` IsDir bool `json:"is_dir"`
Password string `json:"password" binding:"max=255"` Password string `json:"password" binding:"max=255"`
RemainDownloads int `json:"downloads"` RemainDownloads int `json:"downloads"`
@ -30,15 +30,29 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response {
return serializer.Err(serializer.CodeNoPermissionErr, "您无权创建分享链接", nil) 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 exist := true
if service.IsDir { 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 { if err != nil || len(folder) == 0 {
exist = false exist = false
} }
} else { } else {
file, err := model.GetFilesByIDs([]uint{service.SourceID}, user.ID) file, err := model.GetFilesByIDs([]uint{sourceID}, user.ID)
if err != nil || len(file) == 0 { if err != nil || len(file) == 0 {
exist = false exist = false
} }
@ -51,7 +65,7 @@ func (service *ShareCreateService) Create(c *gin.Context) serializer.Response {
Password: service.Password, Password: service.Password,
IsDir: service.IsDir, IsDir: service.IsDir,
UserID: user.ID, UserID: user.ID,
SourceID: service.SourceID, SourceID: sourceID,
Score: service.Score, Score: service.Score,
RemainDownloads: -1, RemainDownloads: -1,
PreviewEnabled: service.Preview, 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }
@ -119,9 +120,7 @@ func (service *Service) PreviewContent(ctx context.Context, c *gin.Context, isTe
} else { } else {
ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
} }
subService := explorer.SingleFileService{ subService := explorer.FileIDService{}
Path: service.Path,
}
return subService.PreviewContent(ctx, c, isText) return subService.PreviewContent(ctx, c, isText)
} }
@ -138,9 +137,7 @@ func (service *Service) CreateDocPreviewSession(c *gin.Context) serializer.Respo
} else { } else {
ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source())
} }
subService := explorer.SingleFileService{ subService := explorer.FileIDService{}
Path: service.Path,
}
return subService.CreateDocPreviewSession(ctx, c) return subService.CreateDocPreviewSession(ctx, c)
} }
@ -321,10 +318,8 @@ func (service *ArchiveService) Archive(c *gin.Context) serializer.Response {
tempUser.Group.OptionsSerialized.ArchiveDownload = true tempUser.Group.OptionsSerialized.ArchiveDownload = true
c.Set("user", tempUser) c.Set("user", tempUser)
subService := explorer.ItemService{ // todo 改成真实
Items: service.Items, subService := explorer.ItemIDService{}
Dirs: service.Dirs,
}
return subService.Archive(ctx, c) return subService.Archive(ctx, c)
} }

Loading…
Cancel
Save