作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


团队中之前的文件下载做得比较复杂,因为担心量太大,是后台做异步的下载,最终生成文件,传送文件到CDN服务器,最后再告诉用户下载链接。

其实在查询接口中就可以实现流式下载,这样查询接口和下载接口可以合二为一,更加简单。

下面是我的demo:

1.建立一个download_file的文件夹作为项目文件夹

go mod init download_file

2.生成go.mod文件,并准备对应的包:

go get github.com/gin-gonic/gin@latest
go get github.com/gin-contrib/gzip

go.mod文件内容如下:

module download_file

go 1.17

require github.com/gin-gonic/gin v1.8.1

require (
github.com/gin-contrib/gzip v0.0.6 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

3.main.go文件:

3.1 初始化gin框架

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")
}

3.2 下载链接页,模拟post到新窗口的场景

func homepage(ctx *gin.Context) {
ctx.Header("Content-Type", "text/html")
ctx.Writer.WriteString(`
<html>
<body>
open window and to download:
<a href="javascript:download()">download</a>
<script>
function download(){
var handle = window.open("about:blank", "my_download_window");
document.forms[0].target = "my_download_window";
document.forms[0].json.value="ahfu test";
document.forms[0].submit();
}
</script>
<form action="/query" method="POST" enctype="multipart/form-data">
<input type="hidden" name="json" value=""/>
</form>
</body>
</html>
`)
}

点击链接后,弹出新窗口,在新窗口中POST json数据

3.3 流式下载功能

func downloadFile(ctx *gin.Context) {
reqData, has := ctx.GetPostForm("json")
if !has {
ctx.Data(400, "text/plain","not found json form data")
return
}
// 此处省略查询的业务逻辑
// todo:
// 下面开始下载的准备
ctx.Writer.WriteHeader(200)
ctx.Header("Content-Type", "text/plain; charset=utf-8")
ctx.Header("Transfer-Encoding", "chunked") // 告诉浏览器,分段的流式的输出数据
// ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
now := time.Now()
fileName := now.Format("20060102_150405.csv")
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName)) // 设置下载的文件名
ctx.Writer.WriteHeaderNow()
// 下面模拟一个周期非常长的数据处理和下载过程
for i := 0; i < 100; i++ {
ctx.Writer.WriteString("\"")
ctx.Writer.WriteString(str)
ctx.Writer.WriteString("\"\t")
ctx.Writer.WriteString("\"")
ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
ctx.Writer.WriteString("\"\n")
ctx.Writer.Flush() // 产生一定的数据后, flush到浏览器端
time.Sleep(time.Duration(500) * time.Millisecond)
}
}

打开浏览器,输入:http://127.0.0.1:8080

然后点击链接,过一会儿后会出现文件下载框。点击保存后,可以看见陆续下载文件的过程。

注意:为什么过了一会儿才出现文件下载框?这是由于浏览器的缓冲机制导致的。如果一开始下载的字节数很多,就会很快出现下载框

3.4 启用gzip压缩

大流量的文本下载,可能很占带宽,我们可以开启GZIP压缩:

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")
}

gin框架中已经提供gzip压缩的能力。

3.5 完整代码:

// main.go
package main import (
"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) {
reqData, has := ctx.GetPostForm("json")
if !has {
ctx.Data(400, "text/plain","not found json form data")
return
}
// 此处省略查询的业务逻辑
// todo:
// 下面开始下载的准备
ctx.Writer.WriteHeader(200)
ctx.Header("Content-Type", "text/plain; charset=utf-8")
ctx.Header("Transfer-Encoding", "chunked") // 告诉浏览器,分段的流式的输出数据
// ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
now := time.Now()
fileName := now.Format("20060102_150405.csv")
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName)) // 设置下载的文件名
ctx.Writer.WriteHeaderNow()
// 下面模拟一个周期非常长的数据处理和下载过程
for i := 0; i < 100; i++ {
ctx.Writer.WriteString("\"")
ctx.Writer.WriteString(str)
ctx.Writer.WriteString("\"\t")
ctx.Writer.WriteString("\"")
ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
ctx.Writer.WriteString("\"\n")
ctx.Writer.Flush() // 产生一定的数据后, flush到浏览器端
time.Sleep(time.Duration(500) * time.Millisecond)
}
} func homepage(ctx *gin.Context) {
ctx.Header("Content-Type", "text/html")
ctx.Writer.WriteString(`
<html>
<body>
open window and to download:
<a href="javascript:download()">download</a>
<script>
function download(){
var handle = window.open("about:blank", "my_download_window");
document.forms[0].target = "my_download_window";
document.forms[0].json.value="ahfu test";
document.forms[0].submit();
}
</script>
<form action="/query" method="POST" enctype="multipart/form-data">
<input type="hidden" name="json" value=""/>
</form>
</body>
</html>
`)
}

have fun.

gin框架中如何实现流式下载的更多相关文章

  1. Java8中的Stream流式操作 - 入门篇

    作者:汤圆 个人博客:javalover.cc 前言 之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧 因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一 ...

  2. golang gin框架中实现一个简单的不是特别精确的秒级限流器

    起因 看了两篇关于golang中限流器的帖子: Gin 开发实践:如何实现限流中间件 常用限流策略--漏桶与令牌桶介绍 我照着用,居然没效果-- 时间有限没有深究.这实在是一个很简单的功能,我的需求是 ...

  3. 在gin框架中使用JWT

    在gin框架中使用JWT JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下. 什么 ...

  4. gin框架中请求路由组的使用

    1. gin框架中可以使用路由组来实现对路由的分类 package main import "github.com/gin-gonic/gin" func main() { rou ...

  5. gin框架中的路由

    基本路由 gin框架中采用的路由库是基于httrouter做的 地址为:https://github.com/julienschmidt/httprouter httprouter路由库 点击查看代码 ...

  6. golang gin框架中实现大文件的流式上传

    一般来说,通过c.Request.FormFile()获取文件的时候,所有内容都全部读到了内存.如果是个巨大的文件,则可能内存会爆掉:且,有的时候我们需要一边上传一边处理. 以下的代码实现了大文件流式 ...

  7. 精讲RestTemplate第6篇-文件上传下载与大文件流式下载

    本文是精讲RestTemplate第6篇,前篇的blog访问地址如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层H ...

  8. Mysql中使用JDBC流式查询避免数据量过大导致OOM

    一.前言 java 中MySQL JDBC 封装了流式查询操作,通过设置几个参数,就可以避免一次返回数据过大导致 OOM. 二.如何使用 2.1 之前查询 public void selectData ...

  9. 【解决了一个小问题】gin框架中出现如下错误:"[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 500"

    POST到数据到一条gin框架的接口后,客户端收到400错误,并且返回了业务中返回的"decode json fail". 关键代码是: func report(c *gin.Co ...

  10. gin框架中中间件的编写与使用

    概念 一个完整的系统可能包括鉴权认证.权限管理.安全检查.日志记录等多维度的系统支持. 中间件位与服务器和实际业务处理程序之间,其含义就相当于在请求和具体的业务处理逻辑之间增加某些操作,这种以额外增加 ...

随机推荐

  1. 万字讲解WiFi为何物

    摘要:WiFi是一种基于IEEE802.11系列协议标准实现的无线通信技术. 本文分享自华为云社区<[云驻共创]物联网无线短距离Wi-Fi技术专题>,作者:Mr红凯. 前言 Wi-Fi通往 ...

  2. iOS移动应用安全加固:保护您的App免受恶意攻击的重要步骤

    ​ 目录 iOS移动应用安全加固:保护您的App免受恶意攻击的重要步骤 摘要 引言 一.APP加固的概念 二.APP加固方案的比较 三.保护iOS应用的安全 四.总结 参考资料 摘要 本文介绍了移动应 ...

  3. ChatGPT访问互联网,不神秘了

    我测试了一下,它其实调用了微软必应的api,搜索了关键词而已 比如我问它:https://twitter.com/elonmusk 马斯克最近在忙什么 它的回答是: 近期,马斯克在做的一些事情包括: ...

  4. Jetpack初步了解

    1.为何要引入Jetpack呢? 通常来说,一个Android应用程序至少需要一个Activity,当我们开发小型Android程序时,可能会将大部分代码写在Activity/Fragment中,例如 ...

  5. 【转】OS | 从一道面试题谈 Linux 下 fork 的运行机制

    今天一位朋友去一个不错的外企面试 Linux 开发职位,面试官出了一个如下的题目: 给出如下C程序,在 Linux 下使用 gcc 编译: #include "stdio.h" # ...

  6. AcWing 第 3 场周赛

    比赛链接:Here AcWing 3660. 最短时间 比较四个方向和 \((r,c)\) 的距离 void solve() { ll n, m, r, c; cin >> n >& ...

  7. 【数据库】E-R图向关系模型转换的规则

    E-R图向关系模型转换的规则: (1) 一个实体型转换为一个关系模式,实体的属性就是关系的属性,实体的码(关键字)就是关系的码. (2) 一个 1:1 联系可以转换为一个独立的关系模式,也可以与任意一 ...

  8. 电缆厂 3D 可视化管控系统 | 图扑数字孪生

    近年来,我国各类器材制造业已经开始向数字化生产转型,使得生产流程变得更加精准高效.通过应用智能设备.物联网和大数据分析等技术,企业可以更好地监控生产线上的运行和质量情况,及时发现和解决问题,从而提高生 ...

  9. 倾斜摄影技术构建图扑 WebGIS 智慧展馆

    前言 智慧展馆通过"云大物移智链"等技术将"物"(展品.设备.环境等)进行互联,并感知"人"(工作人员.观众等)的行为.结合 GIS.BIM ...

  10. SAE 联合乘云至达与谱尼测试携手共同抗疫

    作者 | 营火.计缘.张祖旺 前言 当前疫情形势依然严峻,各行各业众志成城,携手抗疫.新冠病毒核酸检测筛查是疫情防控的重要一环,如何应对疫情的不断反复,以及每日数以万计的核酸检测结果成为每个检测公司的 ...