diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index 1537719..21c3a9e 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -282,7 +282,7 @@ func (fs *FileSystem) signURL(ctx context.Context, file *model.File, ttl int64, // 签名最终URL // 生成外链地址 siteURL := model.GetSiteURL() - source, err := fs.Handler.Source(ctx, fs.FileTarget[0].SourceName, *siteURL, ttl, isDownload) + source, err := fs.Handler.Source(ctx, fs.FileTarget[0].SourceName, *siteURL, ttl, isDownload, fs.User.Group.SpeedLimit) if err != nil { return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err) } diff --git a/pkg/filesystem/filesystem.go b/pkg/filesystem/filesystem.go index e644d34..71d8493 100644 --- a/pkg/filesystem/filesystem.go +++ b/pkg/filesystem/filesystem.go @@ -48,7 +48,7 @@ type Handler interface { // 获取外链/下载地址, // url - 站点本身地址, // isDownload - 是否直接下载 - Source(ctx context.Context, path string, url url.URL, ttl int64, isDownload bool) (string, error) + Source(ctx context.Context, path string, url url.URL, ttl int64, isDownload bool, speed int) (string, error) // Token 获取有效期为ttl的上传凭证和签名,同时回调会话有效期为sessionTTL Token(ctx context.Context, ttl int64, callbackKey string) (serializer.UploadCredential, error) diff --git a/pkg/filesystem/local/handler.go b/pkg/filesystem/local/handler.go index 4cde4a8..2f9930a 100644 --- a/pkg/filesystem/local/handler.go +++ b/pkg/filesystem/local/handler.go @@ -117,6 +117,7 @@ func (handler Handler) Source( baseURL url.URL, ttl int64, isDownload bool, + speed int, ) (string, error) { file, ok := ctx.Value(fsctx.FileModelCtx).(model.File) if !ok { diff --git a/pkg/filesystem/remote/handler.go b/pkg/filesystem/remote/handler.go index 78448ae..595ce8a 100644 --- a/pkg/filesystem/remote/handler.go +++ b/pkg/filesystem/remote/handler.go @@ -52,6 +52,7 @@ func (handler Handler) Source( baseURL url.URL, ttl int64, isDownload bool, + speed int, ) (string, error) { file, ok := ctx.Value(fsctx.FileModelCtx).(model.File) if !ok { @@ -80,7 +81,7 @@ func (handler Handler) Source( authInstance := auth.HMACAuth{SecretKey: []byte(handler.Policy.SecretKey)} signedURI, err = auth.SignURI( authInstance, - fmt.Sprintf("%s/%s", controller, sourcePath), + fmt.Sprintf("%s/%d/%s/%s", controller, speed, sourcePath, file.Name), expires, ) diff --git a/pkg/filesystem/upload_test.go b/pkg/filesystem/upload_test.go index 70e0902..1b50efa 100644 --- a/pkg/filesystem/upload_test.go +++ b/pkg/filesystem/upload_test.go @@ -44,8 +44,8 @@ func (m FileHeaderMock) Thumb(ctx context.Context, files string) (*response.Cont return args.Get(0).(*response.ContentResponse), args.Error(1) } -func (m FileHeaderMock) Source(ctx context.Context, path string, url url.URL, expires int64, isDownload bool) (string, error) { - args := m.Called(ctx, path, url, expires, isDownload) +func (m FileHeaderMock) Source(ctx context.Context, path string, url url.URL, expires int64, isDownload bool, speed int) (string, error) { + args := m.Called(ctx, path, url, expires, isDownload, speed) return args.Get(0).(string), args.Error(1) } diff --git a/routers/controllers/slave.go b/routers/controllers/slave.go index a3b24b4..075dd65 100644 --- a/routers/controllers/slave.go +++ b/routers/controllers/slave.go @@ -6,6 +6,7 @@ import ( "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/HFO4/cloudreve/service/explorer" "github.com/gin-gonic/gin" "net/url" "strconv" @@ -80,3 +81,37 @@ func SlaveUpload(c *gin.Context) { Code: 0, }) } + +// SlaveDownload 从机文件下载 +func SlaveDownload(c *gin.Context) { + // 创建上下文 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var service explorer.SlaveDownloadService + if err := c.ShouldBindUri(&service); err == nil { + res := service.ServeFile(ctx, c, true) + if res.Code != 0 { + c.JSON(200, res) + } + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// SlavePreview 从机文件预览 +func SlavePreview(c *gin.Context) { + // 创建上下文 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var service explorer.SlaveDownloadService + if err := c.ShouldBindUri(&service); err == nil { + res := service.ServeFile(ctx, c, false) + if res.Code != 0 { + c.JSON(200, res) + } + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index 816c0be..9f52a27 100644 --- a/routers/router.go +++ b/routers/router.go @@ -33,7 +33,12 @@ func InitSlaveRouter() *gin.Engine { 路由 */ { + // 上传 v3.POST("upload", controllers.SlaveUpload) + // 下载 + v3.GET("download/:speed/:path/:name", controllers.SlaveDownload) + // 预览 / 外链 + v3.GET("source/:speed/:path/:name", controllers.SlavePreview) } return r } diff --git a/service/explorer/file.go b/service/explorer/file.go index 20dd8b2..6f9459d 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -2,6 +2,7 @@ package explorer import ( "context" + "encoding/base64" model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/filesystem" @@ -9,6 +10,7 @@ import ( "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" "net/http" "net/url" "path" @@ -32,6 +34,13 @@ type DownloadService struct { ID string `uri:"id" binding:"required"` } +// SlaveDownloadService 从机文件下載服务 +type SlaveDownloadService struct { + PathEncoded string `uri:"path" binding:"required"` + Name string `uri:"name" binding:"required"` + Speed int `uri:"speed" binding:"min=0"` +} + // DownloadArchived 下載已打包的多文件 func (service *DownloadService) DownloadArchived(ctx context.Context, c *gin.Context) serializer.Response { // 创建文件系统 @@ -166,10 +175,10 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se // 开始处理下载 ctx = context.WithValue(ctx, fsctx.GinCtx, c) rs, err := fs.GetDownloadContent(ctx, "") - defer rs.Close() if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } + defer rs.Close() // 设置文件名 c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape(fs.FileTarget[0].Name)+"\"") @@ -278,3 +287,53 @@ func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context Code: 0, } } + +// ServeFile 通过签名URL的文件下载从机文件 +func (service *SlaveDownloadService) ServeFile(ctx context.Context, c *gin.Context, isDownload bool) serializer.Response { + // 创建文件系统 + fs, err := filesystem.NewAnonymousFileSystem() + if err != nil { + return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) + } + defer fs.Recycle() + + // 解码文件路径 + fileSource, err := base64.RawURLEncoding.DecodeString(service.PathEncoded) + if err != nil { + return serializer.ParamErr("无法解析的文件地址", err) + } + + // 根据URL里的信息创建一个文件对象和用户对象 + file := model.File{ + Name: service.Name, + SourceName: string(fileSource), + Policy: model.Policy{ + Model: gorm.Model{ID: 1}, + Type: "local", + }, + } + fs.User = &model.User{ + Group: model.Group{SpeedLimit: service.Speed}, + } + fs.FileTarget = []model.File{file} + + // 开始处理下载 + ctx = context.WithValue(ctx, fsctx.GinCtx, c) + rs, err := fs.GetDownloadContent(ctx, "") + if err != nil { + return serializer.Err(serializer.CodeNotSet, err.Error(), err) + } + defer rs.Close() + + // 设置下载文件名 + if isDownload { + c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape(fs.FileTarget[0].Name)+"\"") + } + + // 发送文件 + http.ServeContent(c.Writer, c.Request, fs.FileTarget[0].Name, time.Now(), rs) + + return serializer.Response{ + Code: 0, + } +}