gin框架中如何实现流式下载

打印 上一主题 下一主题

主题 861|帖子 861|积分 2585

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
团队中之前的文件下载做得比较复杂,因为担心量太大,是后台做异步的下载,最终生成文件,传送文件到CDN服务器,最后再告诉用户下载链接。
其实在查询接口中就可以实现流式下载,这样查询接口和下载接口可以合二为一,更加简单。
下面是我的demo:
1.建立一个download_file的文件夹作为项目文件夹
  1. go mod init download_file
复制代码
2.生成go.mod文件,并准备对应的包:
  1. go get github.com/gin-gonic/gin@latest
  2. go get github.com/gin-contrib/gzip
复制代码
go.mod文件内容如下:
  1. module download_file
  2. go 1.17
  3. require github.com/gin-gonic/gin v1.8.1
  4. require (
  5.         github.com/gin-contrib/gzip v0.0.6 // indirect
  6.         github.com/gin-contrib/sse v0.1.0 // indirect
  7.         github.com/go-playground/locales v0.14.0 // indirect
  8.         github.com/go-playground/universal-translator v0.18.0 // indirect
  9.         github.com/go-playground/validator/v10 v10.10.0 // indirect
  10.         github.com/goccy/go-json v0.9.7 // indirect
  11.         github.com/json-iterator/go v1.1.12 // indirect
  12.         github.com/leodido/go-urn v1.2.1 // indirect
  13.         github.com/mattn/go-isatty v0.0.14 // indirect
  14.         github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
  15.         github.com/modern-go/reflect2 v1.0.2 // indirect
  16.         github.com/pelletier/go-toml/v2 v2.0.1 // indirect
  17.         github.com/ugorji/go/codec v1.2.7 // indirect
  18.         golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
  19.         golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
  20.         golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
  21.         golang.org/x/text v0.3.6 // indirect
  22.         google.golang.org/protobuf v1.28.0 // indirect
  23.         gopkg.in/yaml.v2 v2.4.0 // indirect
  24. )
复制代码
3.main.go文件:

3.1 初始化gin框架
  1. func main() {
  2.         log.SetFlags(log.LstdFlags | log.Lshortfile)
  3.         engine := gin.New()
  4.         // engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释
  5.         engine.Handle("POST", "/query", downloadFile)  // 假定查询和下载接口都是这条接口实现
  6.         engine.Handle("GET", "/", homepage)
  7.         engine.Run(":8080")
  8. }
复制代码
3.2 下载链接页,模拟post到新窗口的场景
  1. func homepage(ctx *gin.Context) {
  2.         ctx.Header("Content-Type", "text/html")
  3.         ctx.Writer.WriteString(`
  4. <html>
  5. <body>
  6. open window and to download:
  7. <a target="_blank" href="https://www.cnblogs.com/javascript:download()">download</a>
  8. <form action="/query" method="POST" enctype="multipart/form-data">
  9. <input type="hidden" name="json" value=""/>
  10. </form>
  11. </body>
  12. </html>
  13. `)
  14. }
复制代码
点击链接后,弹出新窗口,在新窗口中POST json数据
3.3 流式下载功能
  1. func downloadFile(ctx *gin.Context) {
  2.         reqData, has := ctx.GetPostForm("json")
  3.         if !has {
  4.                 ctx.Data(400, "text/plain","not found json form data")
  5.                 return
  6.         }
  7.         // 此处省略查询的业务逻辑
  8.         //  todo:
  9.         // 下面开始下载的准备
  10.         ctx.Writer.WriteHeader(200)
  11.         ctx.Header("Content-Type", "text/plain; charset=utf-8")
  12.         ctx.Header("Transfer-Encoding", "chunked")  // 告诉浏览器,分段的流式的输出数据
  13.         //   ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
  14.         now := time.Now()
  15.         fileName := now.Format("20060102_150405.csv")
  16.         ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename="%s"", fileName))  // 设置下载的文件名
  17.         ctx.Writer.WriteHeaderNow()
  18.         // 下面模拟一个周期非常长的数据处理和下载过程
  19.         for i := 0; i < 100; i++ {
  20.                 ctx.Writer.WriteString(""")
  21.                 ctx.Writer.WriteString(str)
  22.                 ctx.Writer.WriteString(""\t")
  23.                 ctx.Writer.WriteString(""")
  24.                 ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
  25.                 ctx.Writer.WriteString(""\n")
  26.                 ctx.Writer.Flush()  // 产生一定的数据后, flush到浏览器端
  27.                 time.Sleep(time.Duration(500) * time.Millisecond)
  28.         }
  29. }
复制代码
打开浏览器,输入:http://127.0.0.1:8080
然后点击链接,过一会儿后会出现文件下载框。点击保存后,可以看见陆续下载文件的过程。
注意:为什么过了一会儿才出现文件下载框?这是由于浏览器的缓冲机制导致的。如果一开始下载的字节数很多,就会很快出现下载框
3.4 启用gzip压缩

大流量的文本下载,可能很占带宽,我们可以开启GZIP压缩:
  1. func main() {
  2.         log.SetFlags(log.LstdFlags | log.Lshortfile)
  3.         engine := gin.New()
  4.         engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释
  5.         engine.Handle("POST", "/query", downloadFile)  // 假定查询和下载接口都是这条接口实现
  6.         engine.Handle("GET", "/", homepage)
  7.         engine.Run(":8080")
  8. }
复制代码
gin框架中已经提供gzip压缩的能力。
3.5 完整代码:
  1. // main.gopackage mainimport (        "fmt"        "log"        "time"        "github.com/gin-contrib/gzip"        "github.com/gin-gonic/gin")func useGzip(engine *gin.Engine) {        engine.Use(gzip.Gzip(gzip.DefaultCompression))}func main() {        log.SetFlags(log.LstdFlags | log.Lshortfile)        engine := gin.New()        // engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释        engine.Handle("POST", "/query", downloadFile)        engine.Handle("GET", "/", homepage)        engine.Run(":8080")}func downloadFile(ctx *gin.Context) {
  2.         reqData, has := ctx.GetPostForm("json")
  3.         if !has {
  4.                 ctx.Data(400, "text/plain","not found json form data")
  5.                 return
  6.         }
  7.         // 此处省略查询的业务逻辑
  8.         //  todo:
  9.         // 下面开始下载的准备
  10.         ctx.Writer.WriteHeader(200)
  11.         ctx.Header("Content-Type", "text/plain; charset=utf-8")
  12.         ctx.Header("Transfer-Encoding", "chunked")  // 告诉浏览器,分段的流式的输出数据
  13.         //   ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
  14.         now := time.Now()
  15.         fileName := now.Format("20060102_150405.csv")
  16.         ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename="%s"", fileName))  // 设置下载的文件名
  17.         ctx.Writer.WriteHeaderNow()
  18.         // 下面模拟一个周期非常长的数据处理和下载过程
  19.         for i := 0; i < 100; i++ {
  20.                 ctx.Writer.WriteString(""")
  21.                 ctx.Writer.WriteString(str)
  22.                 ctx.Writer.WriteString(""\t")
  23.                 ctx.Writer.WriteString(""")
  24.                 ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
  25.                 ctx.Writer.WriteString(""\n")
  26.                 ctx.Writer.Flush()  // 产生一定的数据后, flush到浏览器端
  27.                 time.Sleep(time.Duration(500) * time.Millisecond)
  28.         }
  29. }func homepage(ctx *gin.Context) {
  30.         ctx.Header("Content-Type", "text/html")
  31.         ctx.Writer.WriteString(`
  32. <html>
  33. <body>
  34. open window and to download:
  35. <a target="_blank" href="https://www.cnblogs.com/javascript:download()">download</a>
  36. <form action="/query" method="POST" enctype="multipart/form-data">
  37. <input type="hidden" name="json" value=""/>
  38. </form>
  39. </body>
  40. </html>
  41. `)
  42. }
复制代码
have fun.
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表