Bootstrap

Django restframework 实现文件下载 实现详解及踩坑记录

原理

后端接口读取文件对象,用文件流的形式发送给浏览器,前端创建一个临时的下载a标签,并模拟a标签点击下载的过程,将接口传来的文件下载到本地。

后端实现

settings.py中配置静态文件根目录, 不提

STATIC_ROOT = os.path.join(BASE_DIR, 'static')

将需要下载的文件放入静态文件目录中,实现下载方法

@action(detail=False, methods=['get'], url_path="client/download")
def download(self, request, pk=None):
    """
    文件下载
    :return: 文件流对象
    """
    # 迭代读取文件
    def file_iterator(file_name, chunk_size=512):
        with open(file_name, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break

    # 指定需要下载的文件
    static_root = settings.STATIC_ROOT
    file_dir = os.path.join(static_root, 'storage')
    file_name = 'client.exe'
    download_file = os.path.join(file_dir, file_name)

    # 将服务器上的文件,通过文件流传输到浏览器
    response = StreamingHttpResponse(file_iterator(download_file))
    # 让文件流写入硬盘,需要对下面两个字段赋值
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename="{0}"'.format(file_name)

    return response

一个坑

这里遇到过一个坑,读文件函数一开始是写成open(file_name),没有加其它参数,但只能读文本类文件,读exe、zip等文件都会报错。

尝试指定编码格式,就是加参数open(file_name, encoding=’gbk’),从默认的UTF-8改到GBK,再改到GBK的超集GB18030,均会出现编码错误。

再尝试修改严格模式,忽略报错,改成open(file_name, encoding=’gbk’, errors='ignore'),下载不再报错,但下载后的文件是不能用的错乱版本。

最后试了'rb'参数,将文件转成二进制,居然就好了…一切花里胡哨的招试完以后,问题居然出在最简单朴素的地方。

不过也罢,解决问题的过程中又发现了自己薄弱的地方,捎带学到了不少东西。

前端实现

前端是vue的框架,先写一个下载按钮,绑定一个下载方法

// 为待下载的文件命名并下载
setDownloadFileName(url, file_name) {
  const a = document.createElement('a')
  a.href = url
  a.download = file_name
  document.body.appendChild(a)
  a.click()
  window.URL.revokeObjectURL(url)
  document.body.removeChild(a)
},
handleDownload(data) {
  let downloadUrl = process.env.VUE_APP_BASE_API + '/client/download/'
  this.setDownloadFileName(downloadUrl, 'client.exe')
},

文件下载的功能就完成了

参考文献

django 流式大文件文件下载 - ronon77的文章

;