mirror of https://github.com/cloudreve/Cloudreve
Feat: download / preview files in slave side
parent
35c2a5c977
commit
b19910867e
|
@ -282,7 +282,7 @@ func (fs *FileSystem) signURL(ctx context.Context, file *model.File, ttl int64,
|
||||||
// 签名最终URL
|
// 签名最终URL
|
||||||
// 生成外链地址
|
// 生成外链地址
|
||||||
siteURL := model.GetSiteURL()
|
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 {
|
if err != nil {
|
||||||
return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err)
|
return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ type Handler interface {
|
||||||
// 获取外链/下载地址,
|
// 获取外链/下载地址,
|
||||||
// url - 站点本身地址,
|
// url - 站点本身地址,
|
||||||
// isDownload - 是否直接下载
|
// 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 获取有效期为ttl的上传凭证和签名,同时回调会话有效期为sessionTTL
|
||||||
Token(ctx context.Context, ttl int64, callbackKey string) (serializer.UploadCredential, error)
|
Token(ctx context.Context, ttl int64, callbackKey string) (serializer.UploadCredential, error)
|
||||||
|
|
|
@ -117,6 +117,7 @@ func (handler Handler) Source(
|
||||||
baseURL url.URL,
|
baseURL url.URL,
|
||||||
ttl int64,
|
ttl int64,
|
||||||
isDownload bool,
|
isDownload bool,
|
||||||
|
speed int,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -52,6 +52,7 @@ func (handler Handler) Source(
|
||||||
baseURL url.URL,
|
baseURL url.URL,
|
||||||
ttl int64,
|
ttl int64,
|
||||||
isDownload bool,
|
isDownload bool,
|
||||||
|
speed int,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -80,7 +81,7 @@ func (handler Handler) Source(
|
||||||
authInstance := auth.HMACAuth{SecretKey: []byte(handler.Policy.SecretKey)}
|
authInstance := auth.HMACAuth{SecretKey: []byte(handler.Policy.SecretKey)}
|
||||||
signedURI, err = auth.SignURI(
|
signedURI, err = auth.SignURI(
|
||||||
authInstance,
|
authInstance,
|
||||||
fmt.Sprintf("%s/%s", controller, sourcePath),
|
fmt.Sprintf("%s/%d/%s/%s", controller, speed, sourcePath, file.Name),
|
||||||
expires,
|
expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,8 @@ func (m FileHeaderMock) Thumb(ctx context.Context, files string) (*response.Cont
|
||||||
return args.Get(0).(*response.ContentResponse), args.Error(1)
|
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) {
|
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)
|
args := m.Called(ctx, path, url, expires, isDownload, speed)
|
||||||
return args.Get(0).(string), args.Error(1)
|
return args.Get(0).(string), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
|
"github.com/HFO4/cloudreve/service/explorer"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -80,3 +81,37 @@ func SlaveUpload(c *gin.Context) {
|
||||||
Code: 0,
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,12 @@ func InitSlaveRouter() *gin.Engine {
|
||||||
路由
|
路由
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
|
// 上传
|
||||||
v3.POST("upload", controllers.SlaveUpload)
|
v3.POST("upload", controllers.SlaveUpload)
|
||||||
|
// 下载
|
||||||
|
v3.GET("download/:speed/:path/:name", controllers.SlaveDownload)
|
||||||
|
// 预览 / 外链
|
||||||
|
v3.GET("source/:speed/:path/:name", controllers.SlavePreview)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package explorer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
"github.com/HFO4/cloudreve/pkg/cache"
|
"github.com/HFO4/cloudreve/pkg/cache"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -32,6 +34,13 @@ type DownloadService struct {
|
||||||
ID string `uri:"id" binding:"required"`
|
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 下載已打包的多文件
|
// DownloadArchived 下載已打包的多文件
|
||||||
func (service *DownloadService) DownloadArchived(ctx context.Context, c *gin.Context) serializer.Response {
|
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)
|
ctx = context.WithValue(ctx, fsctx.GinCtx, c)
|
||||||
rs, err := fs.GetDownloadContent(ctx, "")
|
rs, err := fs.GetDownloadContent(ctx, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
defer rs.Close()
|
||||||
|
|
||||||
// 设置文件名
|
// 设置文件名
|
||||||
c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape(fs.FileTarget[0].Name)+"\"")
|
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,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue