diff --git a/middleware/auth.go b/middleware/auth.go index c81fd0f..c295771 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,12 +1,26 @@ package middleware import ( + "fmt" "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) +// SignRequired 验证请求签名 +func SignRequired() gin.HandlerFunc { + return func(c *gin.Context) { + // 获取待验证的签名正文 + queries := c.Request.URL.Query() + queries.Del("sign") + c.Request.URL.RawQuery = queries.Encode() + requestURI := c.Request.URL.RequestURI() + fmt.Println(requestURI) + c.Next() + } +} + // CurrentUser 获取登录用户 func CurrentUser() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/models/setting.go b/models/setting.go index 2a9498b..591aef1 100644 --- a/models/setting.go +++ b/models/setting.go @@ -3,6 +3,7 @@ package model import ( "github.com/HFO4/cloudreve/pkg/cache" "github.com/jinzhu/gorm" + "net/url" ) // Setting 系统设置模型 @@ -62,3 +63,12 @@ func GetSettingByType(types []string) map[string]string { return res } + +// GetSiteURL 获取站点地址 +func GetSiteURL() *url.URL { + base, err := url.Parse(GetSettingByName("siteURL")) + if err != nil { + base, _ = url.Parse("https://cloudreve.org") + } + return base +} diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 5c2ed62..748987f 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -3,6 +3,7 @@ package auth import ( model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/serializer" + "net/url" ) var ( @@ -21,7 +22,26 @@ type Auth interface { Check(body string, sign string) error } +// SignURI 对URI进行签名 +// TODO 测试 +func SignURI(uri string, expires int64) (*url.URL, error) { + // 生成签名 + sign := General.Sign(uri, expires) + + // 将签名加到URI中 + base, err := url.Parse(uri) + if err != nil { + return nil, err + } + queries := base.Query() + queries.Set("sign", sign) + base.RawQuery = queries.Encode() + + return base, nil +} + // Init 初始化通用鉴权器 +// TODO slave模式下从配置文件获取 func Init() { General = HMACAuth{ SecretKey: []byte(model.GetSettingByName("secret_key")), diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index 2a3582f..0fa3db8 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -3,6 +3,7 @@ package filesystem import ( "context" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "github.com/juju/ratelimit" @@ -33,8 +34,8 @@ func withSpeedLimit(rs io.ReadSeeker, speed int) io.ReadSeeker { // AddFile 新增文件记录 func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model.File, error) { - file := ctx.Value(FileHeaderCtx).(FileHeader) - filePath := ctx.Value(SavePathCtx).(string) + file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) + filePath := ctx.Value(fsctx.SavePathCtx).(string) newFile := model.File{ Name: file.GetFileName(), @@ -86,7 +87,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (io.ReadSeeke return nil, ErrObjectNotExist } fs.FileTarget = []model.File{*file} - + ctx = context.WithValue(ctx, fsctx.FileModelCtx, file) // 将当前存储策略重设为文件使用的 fs.Policy = file.GetPolicy() err = fs.dispatchHandler() @@ -161,6 +162,8 @@ func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error } fs.FileTarget = []model.File{fileObject[0]} + ctx = context.WithValue(ctx, fsctx.FileModelCtx, fileObject[0]) + // 将当前存储策略重设为文件使用的 fs.Policy = fileObject[0].GetPolicy() err = fs.dispatchHandler() @@ -172,5 +175,13 @@ func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error if !fs.Policy.IsOriginLinkEnable { return "", serializer.NewError(serializer.CodePolicyNotAllowed, "当前存储策略无法获得外链", nil) } - return "", nil + + // 生成外链地址 + siteURL := model.GetSiteURL() + source, err := fs.Handler.Source(ctx, fileObject[0].SourceName, *siteURL, 0) + if err != nil { + return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err) + } + + return source, nil } diff --git a/pkg/filesystem/file_test.go b/pkg/filesystem/file_test.go index cd3c656..6b1dd87 100644 --- a/pkg/filesystem/file_test.go +++ b/pkg/filesystem/file_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/DATA-DOG/go-sqlmock" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/jinzhu/gorm" @@ -35,8 +36,8 @@ func TestFileSystem_AddFile(t *testing.T) { }, }, } - ctx := context.WithValue(context.Background(), FileHeaderCtx, file) - ctx = context.WithValue(ctx, SavePathCtx, "/Uploads/1_sad.txt") + ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, file) + ctx = context.WithValue(ctx, fsctx.SavePathCtx, "/Uploads/1_sad.txt") _, err := fs.AddFile(ctx, &folder) diff --git a/pkg/filesystem/filesystem.go b/pkg/filesystem/filesystem.go index ca0f1e0..894e877 100644 --- a/pkg/filesystem/filesystem.go +++ b/pkg/filesystem/filesystem.go @@ -8,6 +8,7 @@ import ( "github.com/HFO4/cloudreve/pkg/filesystem/response" "github.com/gin-gonic/gin" "io" + "net/url" ) // FileHeader 上传来的文件数据处理器 @@ -30,6 +31,8 @@ type Handler interface { Get(ctx context.Context, path string) (io.ReadSeeker, error) // 获取缩略图 Thumb(ctx context.Context, path string) (*response.ContentResponse, error) + // 获取外链地址,url + Source(ctx context.Context, path string, url url.URL, expires int64) (string, error) } // FileSystem 管理文件的文件系统 @@ -79,17 +82,23 @@ func NewFileSystem(user *model.User) (*FileSystem, error) { // dispatchHandler 根据存储策略分配文件适配器 func (fs *FileSystem) dispatchHandler() error { var policyType string + var currentPolicy *model.Policy + if fs.Policy == nil { // 如果没有具体指定,就是用用户当前存储策略 policyType = fs.User.Policy.Type + currentPolicy = &fs.User.Policy } else { policyType = fs.Policy.Type + currentPolicy = fs.Policy } // 根据存储策略类型分配适配器 switch policyType { case "local": - fs.Handler = local.Handler{} + fs.Handler = local.Handler{ + Policy: currentPolicy, + } return nil default: return ErrUnknownPolicyType diff --git a/pkg/filesystem/context.go b/pkg/filesystem/fsctx/context.go similarity index 76% rename from pkg/filesystem/context.go rename to pkg/filesystem/fsctx/context.go index adce850..d06235f 100644 --- a/pkg/filesystem/context.go +++ b/pkg/filesystem/fsctx/context.go @@ -1,4 +1,4 @@ -package filesystem +package fsctx type key int @@ -11,4 +11,6 @@ const ( FileHeaderCtx // PathCtx 文件或目录的虚拟路径 PathCtx + // FileModelCtx 文件数据库模型 + FileModelCtx ) diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go index 833ba6f..a509efd 100644 --- a/pkg/filesystem/hooks.go +++ b/pkg/filesystem/hooks.go @@ -3,6 +3,7 @@ package filesystem import ( "context" "errors" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/util" ) @@ -40,7 +41,7 @@ func (fs *FileSystem) Trigger(ctx context.Context, hooks []Hook) error { // HookIsFileExist 检查虚拟路径文件是否存在 func HookIsFileExist(ctx context.Context, fs *FileSystem) error { - filePath := ctx.Value(PathCtx).(string) + filePath := ctx.Value(fsctx.PathCtx).(string) if ok, _ := fs.IsFileExist(filePath); ok { return nil } @@ -49,7 +50,7 @@ func HookIsFileExist(ctx context.Context, fs *FileSystem) error { // HookValidateFile 一系列对文件检验的集合 func HookValidateFile(ctx context.Context, fs *FileSystem) error { - file := ctx.Value(FileHeaderCtx).(FileHeader) + file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) // 验证单文件尺寸 if !fs.ValidateFileSize(ctx, file.GetSize()) { @@ -72,7 +73,7 @@ func HookValidateFile(ctx context.Context, fs *FileSystem) error { // HookValidateCapacity 验证并扣除用户容量,包含数据库操作 func HookValidateCapacity(ctx context.Context, fs *FileSystem) error { - file := ctx.Value(FileHeaderCtx).(FileHeader) + file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) // 验证并扣除容量 if !fs.ValidateCapacity(ctx, file.GetSize()) { return ErrInsufficientCapacity @@ -82,7 +83,7 @@ func HookValidateCapacity(ctx context.Context, fs *FileSystem) error { // HookDeleteTempFile 删除已保存的临时文件 func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error { - filePath := ctx.Value(SavePathCtx).(string) + filePath := ctx.Value(fsctx.SavePathCtx).(string) // 删除临时文件 if util.Exists(filePath) { _, err := fs.Handler.Delete(ctx, []string{filePath}) @@ -96,7 +97,7 @@ func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error { // HookGiveBackCapacity 归还用户容量 func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error { - file := ctx.Value(FileHeaderCtx).(FileHeader) + file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) // 归还用户容量 if !fs.User.DeductionStorage(file.GetSize()) { @@ -108,7 +109,7 @@ func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error { // GenericAfterUpload 文件上传完成后,包含数据库操作 func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { // 文件存放的虚拟路径 - virtualPath := ctx.Value(FileHeaderCtx).(FileHeader).GetVirtualPath() + virtualPath := ctx.Value(fsctx.FileHeaderCtx).(FileHeader).GetVirtualPath() // 检查路径是否存在 isExist, folder := fs.IsPathExist(virtualPath) @@ -119,7 +120,7 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { // 检查文件是否存在 if ok, _ := fs.IsChildFileExist( folder, - ctx.Value(FileHeaderCtx).(FileHeader).GetFileName(), + ctx.Value(fsctx.FileHeaderCtx).(FileHeader).GetFileName(), ); ok { return ErrFileExisted } diff --git a/pkg/filesystem/hooks_test.go b/pkg/filesystem/hooks_test.go index a04135f..f2e3096 100644 --- a/pkg/filesystem/hooks_test.go +++ b/pkg/filesystem/hooks_test.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/DATA-DOG/go-sqlmock" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" @@ -18,7 +19,7 @@ func TestGenericBeforeUpload(t *testing.T) { Size: 5, Name: "1.txt", } - ctx := context.WithValue(context.Background(), FileHeaderCtx, file) + ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, file) fs := FileSystem{ User: &model.User{ Storage: 0, @@ -38,15 +39,15 @@ func TestGenericBeforeUpload(t *testing.T) { file.Size = 1 file.Name = "1" - ctx = context.WithValue(context.Background(), FileHeaderCtx, file) + ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file) asserts.Error(HookValidateFile(ctx, &fs)) file.Name = "1.txt" - ctx = context.WithValue(context.Background(), FileHeaderCtx, file) + ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file) asserts.NoError(HookValidateFile(ctx, &fs)) file.Name = "1.t/xt" - ctx = context.WithValue(context.Background(), FileHeaderCtx, file) + ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file) asserts.Error(HookValidateFile(ctx, &fs)) } @@ -59,8 +60,8 @@ func TestGenericAfterUploadCanceled(t *testing.T) { Size: 5, Name: "TestGenericAfterUploadCanceled", } - ctx := context.WithValue(context.Background(), SavePathCtx, "TestGenericAfterUploadCanceled") - ctx = context.WithValue(ctx, FileHeaderCtx, file) + ctx := context.WithValue(context.Background(), fsctx.SavePathCtx, "TestGenericAfterUploadCanceled") + ctx = context.WithValue(ctx, fsctx.FileHeaderCtx, file) fs := FileSystem{ User: &model.User{Storage: 5}, Handler: local.Handler{}, @@ -97,11 +98,11 @@ func TestGenericAfterUpload(t *testing.T) { }, } - ctx := context.WithValue(context.Background(), FileHeaderCtx, local.FileStream{ + ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, local.FileStream{ VirtualPath: "/我的文件", Name: "test.txt", }) - ctx = context.WithValue(ctx, SavePathCtx, "") + ctx = context.WithValue(ctx, fsctx.SavePathCtx, "") // 正常 mock.ExpectQuery("SELECT(.+)"). @@ -228,7 +229,7 @@ func TestHookIsFileExist(t *testing.T) { ID: 1, }, }} - ctx := context.WithValue(context.Background(), PathCtx, "/test.txt") + ctx := context.WithValue(context.Background(), fsctx.PathCtx, "/test.txt") { mock.ExpectQuery("SELECT(.+)"). WithArgs(1). @@ -263,7 +264,7 @@ func TestHookValidateCapacity(t *testing.T) { MaxStorage: 11, }, }} - ctx := context.WithValue(context.Background(), FileHeaderCtx, local.FileStream{Size: 10}) + ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, local.FileStream{Size: 10}) { err := HookValidateCapacity(ctx, fs) asserts.NoError(err) diff --git a/pkg/filesystem/local/handler.go b/pkg/filesystem/local/handler.go index 087da77..828852e 100644 --- a/pkg/filesystem/local/handler.go +++ b/pkg/filesystem/local/handler.go @@ -2,16 +2,25 @@ package local import ( "context" + "errors" + "fmt" + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/auth" "github.com/HFO4/cloudreve/pkg/conf" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/response" + "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "io" + "net/url" "os" "path/filepath" ) // Handler 本地策略适配器 -type Handler struct{} +type Handler struct { + Policy *model.Policy +} // Get 获取文件内容 func (handler Handler) Get(ctx context.Context, path string) (io.ReadSeeker, error) { @@ -97,3 +106,23 @@ func (handler Handler) Thumb(ctx context.Context, path string) (*response.Conten Content: file, }, nil } + +// Source 获取外链URL +func (handler Handler) Source(ctx context.Context, path string, url url.URL, expires int64) (string, error) { + file, ok := ctx.Value(fsctx.FileModelCtx).(model.File) + if !ok { + return "", errors.New("无法获取文件记录上下文") + } + + // 签名生成文件记录 + signedURI, err := auth.SignURI( + fmt.Sprintf("/api/v3/file/get/%d/%s", file.ID, file.Name), + 0, + ) + if err != nil { + return "", serializer.NewError(serializer.CodeEncryptError, "无法对URL进行签名", err) + } + + finalURL := url.ResolveReference(signedURI).String() + return finalURL, nil +} diff --git a/pkg/filesystem/upload.go b/pkg/filesystem/upload.go index 7e3c5c6..c066c82 100644 --- a/pkg/filesystem/upload.go +++ b/pkg/filesystem/upload.go @@ -2,6 +2,7 @@ package filesystem import ( "context" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" "path/filepath" @@ -14,7 +15,7 @@ import ( // Upload 上传文件 func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) { - ctx = context.WithValue(ctx, FileHeaderCtx, file) + ctx = context.WithValue(ctx, fsctx.FileHeaderCtx, file) // 上传前的钩子 err = fs.Trigger(ctx, fs.BeforeUpload) @@ -35,7 +36,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) { } // 上传完成后的钩子 - ctx = context.WithValue(ctx, SavePathCtx, savePath) + ctx = context.WithValue(ctx, fsctx.SavePathCtx, savePath) err = fs.Trigger(ctx, fs.AfterUpload) if err != nil { @@ -70,7 +71,7 @@ func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) str // CancelUpload 监测客户端取消上传 func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) { - ginCtx := ctx.Value(GinCtx).(*gin.Context) + ginCtx := ctx.Value(fsctx.GinCtx).(*gin.Context) select { case <-ginCtx.Request.Context().Done(): select { @@ -82,7 +83,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe if fs.AfterUploadCanceled == nil { return } - ctx = context.WithValue(ctx, SavePathCtx, path) + ctx = context.WithValue(ctx, fsctx.SavePathCtx, path) err := fs.Trigger(ctx, fs.AfterUploadCanceled) if err != nil { util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err) diff --git a/pkg/filesystem/upload_test.go b/pkg/filesystem/upload_test.go index f4181d9..adf98f9 100644 --- a/pkg/filesystem/upload_test.go +++ b/pkg/filesystem/upload_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/HFO4/cloudreve/pkg/filesystem/response" "github.com/gin-gonic/gin" @@ -13,6 +14,7 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "testing" ) @@ -40,6 +42,11 @@ 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) (string, error) { + args := m.Called(ctx, path, url, expires) + return args.Get(0).(string), args.Error(1) +} + func TestFileSystem_Upload(t *testing.T) { asserts := assert.New(t) @@ -61,7 +68,7 @@ func TestFileSystem_Upload(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) c, _ := gin.CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - ctx = context.WithValue(ctx, GinCtx, c) + ctx = context.WithValue(ctx, fsctx.GinCtx, c) cancel() file := local.FileStream{ Size: 5, diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 4d86913..e1acb5e 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -5,6 +5,7 @@ import ( "context" "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/local" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" @@ -15,6 +16,11 @@ import ( "strconv" ) +// AnonymousGetContent 匿名获取文件资源 +func AnonymousGetContent(c *gin.Context) { + c.JSON(200, serializer.Response{}) +} + // GetSource 获取文件的外链地址 func GetSource(c *gin.Context) { // 创建上下文 @@ -152,7 +158,7 @@ func FileUploadStream(c *gin.Context) { fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity) // 执行上传 - uploadCtx := context.WithValue(ctx, filesystem.GinCtx, c) + uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) err = fs.Upload(uploadCtx, fileData) if err != nil { c.JSON(200, serializer.Err(serializer.CodeUploadFailed, err.Error(), err)) diff --git a/routers/router.go b/routers/router.go index 4ec7d1e..b0e1f28 100644 --- a/routers/router.go +++ b/routers/router.go @@ -39,23 +39,37 @@ func InitRouter() *gin.Engine { */ v3 := r.Group("/api/v3") { - // 测试用路由 - v3.GET("site/ping", controllers.Ping) + // 全局设置相关 + site := v3.Group("site") + { + // 测试用路由 + site.GET("ping", controllers.Ping) + // 验证码 + site.GET("captcha", controllers.Captcha) + // 站点全局配置 + site.GET("config", controllers.SiteConfig) + } - // 不需要登录的用户相关路由 + // 用户相关路由 + user := v3.Group("user") { // 用户登录 - v3.POST("user/session", controllers.UserLogin) + user.POST("session", controllers.UserLogin) // WebAuthn登陆初始化 - v3.GET("user/authn/:username", controllers.StartLoginAuthn) + user.GET("authn/:username", controllers.StartLoginAuthn) // WebAuthn登陆 - v3.POST("user/authn/finish/:username", controllers.FinishLoginAuthn) + user.POST("authn/finish/:username", controllers.FinishLoginAuthn) } - // 验证码 - v3.GET("captcha", controllers.Captcha) - // 站点全局配置 - v3.GET("site/config", controllers.SiteConfig) + // 需要携带签名验证的 + sign := v3.Group("") + sign.Use(middleware.SignRequired()) + { + file := sign.Group("file") + { + file.GET("get/:id/:name", controllers.AnonymousGetContent) + } + } // 需要登录保护的 auth := v3.Group("") diff --git a/service/explorer/file.go b/service/explorer/file.go index ecdd32d..39b115b 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -3,6 +3,7 @@ package explorer import ( "context" "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" "net/http" @@ -22,7 +23,7 @@ func (service *FileDownloadService) Download(ctx context.Context, c *gin.Context } // 开始处理下载 - ctx = context.WithValue(ctx, filesystem.GinCtx, c) + ctx = context.WithValue(ctx, fsctx.GinCtx, c) rs, err := fs.GetDownloadContent(ctx, service.Path) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err)