数据流返回大文件(Python,Go实例)

         之前写的一篇文章,nginx转发数据流文件,解释了为什么用数据流返回大文件。用数据流返回大文件(文本类的),数据在所有的节点都不缓存,在服务端读取一部分就返回一部分,也就是先返回响应,然后文件慢慢下载,这样就不会有超时,因为响应已经返回了。而且文件不会服务端先生成,代理再缓存,最后再返回客户端。这样的思路非常有意思。今天写两个具体的demo来看下。

        普通思路是从数据库查询所有数据,再写到文件,然后静态文件返回。这样容易因为文件太大导致下载时间超过几分钟,超时的问题。所以现在的思路是,接到请求,边查询边返回,返回的响应体不是文件而是数据流。

        Go的gin框架demo如下:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"io"
)

type OperationLog struct {
	Id            int    `gorm:"primaryKey"`
	UserId        string `gorm:"user_id"`
	UserName      string `gorm:"user_name"`
}

func main() {

	router := gin.Default()
	router.GET("/streamData", streamData)
	router.Run(":8080")
}

func getDB() (db *gorm.DB, err error) {
	dsn := "root:123456(172.0.0.1:3306)/test?charset=utf8mb4"
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{SingularTable: true}})
	if err != nil {
		print("%v", err)
		//panic("无法连接到数据库")
	}
	return db, err
}

func streamData(c *gin.Context) {
	/*
		先获取数据
	*/
	db, err := getDB()
	if err != nil {
		print("%v", err)
		panic("无法连接到数据库")
	}
	pageSize := 5
	pageNumber := 1

	var logs []OperationLog
	var total int64
	offset := (pageNumber - 1) * pageSize
    // 总数
	db.Offset(offset).Limit(pageSize).Find(&logs).Count(&total)

    // 总页数
	pages := int(total) / pageSize
	rem := int(total) % pageSize
	if rem != 0 {
		pages += 1
	}

	// 分段下载数据
	c.Header("Transfer-Encoding", "chunked")

	// 强制下载
	c.Header("ContentType", "\"application/octet-stream; charset=utf-8-sig\"")
	// 文件名
	c.Header("Content-Disposition", "attachment;filename=test.csv")
	// 流式响应
	c.Stream(func(w io.Writer) bool {
		// 每次只查询一页的数据,每次只返回一页的数据
		for i := 0; i <= pages; i++ {
			pageData := getPageData(db, i, pageSize)
			idStr := ""
			for _, v := range pageData {
				idStr += fmt.Sprintf("%d", v.Id)
			}
			n, errWrite := w.Write([]byte(idStr + "\n"))
			if errWrite != nil {
				print(err)
			}
			print("%v,%d", idStr, n)

		}
		// 返回false结束
		return false
	})
}

func getPageData(db *gorm.DB, pageNumber int, pageSize int) []OperationLog {
	var logs []OperationLog
	offset := (pageNumber - 1) * pageSize
	db.Offset(offset).Limit(pageSize).Find(&logs)
	return logs
}

返回响应非常快

还可以看下Stream(),接收入参为io.Writer的函数,每次把上下文的写入器传入step函数,刷新写入器,最后监听通道关闭,结束。

python 的bottle如下(思路,不能跑),使用游标,每次只查几个,查完就返回

    def stream_data(self, request_body, db):
        engine = get_engine()
        conn = engine.raw_connection()
        name_suffix = str(uuid.uuid4())[:6]
        file_name = resource_type + name_suffix + '.csv'
        sql = "select * from user"

        response = HTTPResponse(body=self.streaming_res(conn, sql))
        response.streaming = True
        # 告诉代理或浏览器,这是分段下载的数据
        response.set_header("Transfer-Encoding", "chunked")
        # 未知类型的数据流文件 utf-8-sig编码让excel默认打开不会乱码
        response.content_type = "application/octet-stream; charset=utf-8-sig"
        # 强制浏览器下载文件
        response.set_header("Content-Disposition", "attachment;filename=" + file_name)
        return response

    def streaming_res(self,db, sql_query: str):
        """
        生成器函数,将sql查询的数据,一行一行返回并格式化
        """
        try:
            cs = db.cursor()
            cs.execute(sql_query)
            while True:
                result = cs.fetchmany(100)
                if result:
                    yield ','.join(result)
                else:
                    break
        except Exception as e:
            log.error("error:" + str(e))
        finally:
            if cs: cs.close()
            if db: db.close()

参考博客:

nginx 转发数据流文件_nginx文件流-CSDN博客文章浏览阅读445次。nginx转发数据流时的配置。_nginx文件流https://blog.csdn.net/qq_26372385/article/details/134005740

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值