作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
- cnblogs博客
- zhihu
- Github
- 公众号:一本正经的瞎扯
团队中之前的文件下载做得比较复杂,因为担心量太大,是后台做异步的下载,最终生成文件,传送文件到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. 😃
来源:https://www.cnblogs.com/ahfuzhang/p/16854798.html
本站部分图文来源于网络,如有侵权请联系删除。