diff --git a/controller/file_controller.go b/controller/file_controller.go index 683546a..555ebd6 100644 --- a/controller/file_controller.go +++ b/controller/file_controller.go @@ -22,6 +22,12 @@ func (c *file) DownloadToFile(ctx context.Context, req *dto.DownloadToFileReq) ( return } +// DownloadToBrowser 下载文件到浏览器 +func (c *file) DownloadToBrowser(ctx context.Context, req *dto.DownloadToBrowserReq) (res *beans.ResponseEmpty, err error) { + err = service.File.DownloadToBrowser(ctx, req) + return +} + // UploadFile 上传文件 func (c *file) UploadFile(ctx context.Context, req *dto.UploadFileReq) (res *dto.UploadFileRes, err error) { return service.File.UploadFile(ctx, req) diff --git a/minio/minio.go b/minio/minio.go index 131d583..0e7ee05 100644 --- a/minio/minio.go +++ b/minio/minio.go @@ -5,6 +5,7 @@ import ( "context" "encoding/base64" "fmt" + "io" "net/http" "path/filepath" "strings" @@ -223,3 +224,45 @@ func DownloadToFile(ctx context.Context, fileURL string, localPath string) (err return } + +// DownloadToBrowser 下载文件到浏览器(返回文件流,由 HTTP Response 直接写出) +// 参数 fileURL: MinIO 中的对象路径(即 objectName) +// 返回: 文件字节流、文件名、ContentType、错误 +func DownloadToBrowser(ctx context.Context, fileURL string) (fileBytes []byte, fileName string, contentType string, err error) { + bucketName, err := utils.GetBucketName(ctx) + if err != nil { + glog.Errorf(ctx, "获取桶名称失败: %v", err) + return + } + + // 获取对象流 + object, err := minioClient.GetObject(ctx, bucketName, fileURL, minio.GetObjectOptions{}) + if err != nil { + glog.Errorf(ctx, "获取文件流失败: %v", err) + return + } + defer object.Close() + + // 获取对象元信息(用于读取 ContentType 和 Size) + info, err := object.Stat() + if err != nil { + glog.Errorf(ctx, "获取文件信息失败: %v", err) + return + } + + // 读取全部内容 + fileBytes, err = io.ReadAll(object) + if err != nil { + glog.Errorf(ctx, "读取文件流失败: %v", err) + return + } + + // 提取文件名 + fileName = filepath.Base(fileURL) + contentType = info.ContentType + if contentType == "" { + contentType = "application/octet-stream" + } + + return +} diff --git a/model/dto/file_dto.go b/model/dto/file_dto.go index 4e5f695..89233c8 100644 --- a/model/dto/file_dto.go +++ b/model/dto/file_dto.go @@ -11,6 +11,12 @@ type DownloadToFileReq struct { LocalPath string `json:"localPath" dc:"本地路径"` } +// DownloadToBrowserReq 下载文件到浏览器请求 +type DownloadToBrowserReq struct { + g.Meta `path:"/downloadToBrowser" method:"post" tags:"存储管理" summary:"下载文件到浏览器" dc:"下载文件到浏览器"` + FileURL string `json:"fileURL" dc:"文件URL" in:"query"` +} + // UploadFileReq 上传文件请求 type UploadFileReq struct { g.Meta `path:"/uploadFile" method:"post" tags:"存储管理" summary:"上传文件" dc:"上传文件"` diff --git a/service/file_service.go b/service/file_service.go index 6373506..2734f37 100644 --- a/service/file_service.go +++ b/service/file_service.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "net/url" "oss/consts" "oss/dao" "oss/minio" @@ -10,6 +11,7 @@ import ( "oss/model/entity" "time" + "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/glog" "gitea.com/red-future/common/utils" @@ -27,6 +29,23 @@ func (f *file) DownloadToFile(ctx context.Context, req *dto.DownloadToFileReq) ( return minio.DownloadToFile(ctx, req.FileURL, req.LocalPath) } +// DownloadToBrowser 下载文件到浏览器 +func (f *file) DownloadToBrowser(ctx context.Context, req *dto.DownloadToBrowserReq) (err error) { + fileBytes, fileName, contentType, err := minio.DownloadToBrowser(ctx, req.FileURL) + if err != nil { + return err + } + + r := ghttp.RequestFromCtx(ctx) + // 设置响应头 + r.Response.Header().Set("Content-Type", contentType) + r.Response.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, fileName, url.PathEscape(fileName))) + r.Response.Header().Set("Content-Length", fmt.Sprintf("%d", len(fileBytes))) + // 写出二进制流 + r.Response.Write(fileBytes) + return +} + func (f *file) UploadFile(ctx context.Context, req *dto.UploadFileReq) (res *dto.UploadFileRes, err error) { fileSize := gconv.Int(req.File.Size) totalFileSize := 0