gin框架中如何實現流式下載

来源:https://www.cnblogs.com/ahfuzhang/archive/2022/11/03/16854798.html
-Advertisement-
Play Games

作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝! cnblogs博客 zhihu Github 公眾號:一本正經的瞎扯 團隊中之前的文件下載做得比較複雜,因為擔心量太大,是後臺做非同步的下載,最終生成文件,傳送文件到CDN伺服器,最後再告訴用戶下載鏈接。 其實在查詢介面中就可以實 ...


作者:張富春(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.

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 熱度已經過了,但還是覺得有必要從架構設計的角度來討論一下此事。並用以往我的經驗來設計一套負載能力更好一些的系統。 先說一下基本的架構思路: 最大限度的避免計算,靜態化 不用資料庫,更新類操作使用APPEND模式的文本文件 流程最短,最好是客戶端訪問的第一臺伺服器就能完成全部工作 善用CDN 客戶端負 ...
  • 您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 從之前的Lambda表達式的演變過程可以知道,Lambda表達式其實是一個對匿名內部類的簡化過程:去掉了多餘的語法修飾,只保留最最核心的部分。在Java中類似這種使用匿名內部類寫代碼的場景非常多,比如Runnable介面,就是典型的最好使用La ...
  • 本節內容會用到之前給大家講過的這兩篇: 2流高手速成記(之六):從SpringBoot到SpringCloudAlibaba 2流高手速成記(之三):SpringBoot整合mybatis/mybatis-plus實現數據持久化 鏈接掛出來,方便咱們中途對比著看 老規矩,先放出本節的項目結構: 我們 ...
  • XML 官方文檔:https://www.w3school.com.cn/xml/index.asp 1.為什麼需要xml? 需求1:兩個程式間進行數據通信? 需求2:給一臺伺服器,做一個配置文件,當伺服器程式啟動時,去讀取它應當監聽的埠號、還有連接資料庫的用戶名和密碼 spring中的IOC配置 ...
  • 大家好,我是metahuber,數字宇宙探索者。 本系列教程是Python的入門教程,本篇文章是此教程的第一篇,希望大家多多關註。 在學習Python之前,我們首先瞭解下什麼是編程語言。 說到編程語言,還需要從程式說起。 其實,程式就是一系列指令,電腦之所以能夠工作,根本的原因是它能夠識別人類發出 ...
  • 登錄介面分析 登錄分為多方式登錄和驗證碼登錄方式 多方式登錄 1)前臺提供賬號密碼,賬號可能是 用戶名、手機號、郵箱等 介面: 後臺只需要提供一個多方式登錄介面即可 - 多方式登錄介面 多方式登錄介面 前端輸入完賬號和密碼,點擊登錄,向後端發送請求進行校驗用戶登錄數據 urls.py from dj ...
  • 簡介: 適配器模式屬於結構型設計模式。 將一個類的介面轉換成可應用的相容介面。適配器使原本由於介面不相容而不能一起工作的那些類可以一起工作。 適配器模式有兩種實現方案,一種是繼承的方式,一種是組合的方式。 適用場景: 相容不方便更改的“祖傳”代碼。 歸納具有相似點的模塊,比如Laravel File ...
  • 前言 所謂熱部署,簡單來說,就是代碼修改後不需重啟項目就可自動載入出新的內容。 ==註意:熱部署在 debug 調試模式下才生效!== IDEA 配置 在 IDE(IDEA)中開啟相關項目自動構建選項 開啟編譯器設置中修改後自動編譯的選項(下圖是 IDEA 2021版本,其他版本可能在其他位置) S ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...