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的文章

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是使用 Django Rest Framework 实现文件下载进度条的示例代码: ``` python import os import mimetypes from rest_framework.response import Response from rest_framework.views import APIView from wsgiref.util import FileWrapper from django.http import HttpResponse class DownloadFileView(APIView): def get(self, request, *args, **kwargs): file_path = '/path/to/your/file' # 文件路径 try: file_size = os.path.getsize(file_path) except FileNotFoundError: return Response({'error': 'File not found.'}, status=404) # 设置文件类型和文件名 content_type = mimetypes.guess_type(file_path)[0] file_name = os.path.basename(file_path) # 开始传输文件 chunk_size = 8192 # 每次传输字节数 response = HttpResponse(content_type=content_type) response['Content-Disposition'] = f'attachment; filename="{file_name}"' response['X-Sendfile'] = file_path # 使用 X-Sendfile 指示 Web 服务器直接发送文件 # 发送响应前,先设置一些响应头 response['Content-Length'] = file_size response.accept_ranges = 'bytes' # 开始响应客户端请求 if request.META.get('HTTP_RANGE'): # 支持断点续传,获取客户端请求的断点位置 range_header = request.META.get('HTTP_RANGE').strip().lower() range_match = re.match(r'^bytes=(\d+)-(\d*)$', range_header) if range_match: first_byte_pos = int(range_match.group(1)) last_byte_pos = file_size - 1 if not range_match.group(2) else int(range_match.group(2)) if first_byte_pos >= file_size: response.status_code = 416 # Requested Range Not Satisfiable response['Content-Range'] = f'bytes */{file_size}' return response elif last_byte_pos >= file_size: last_byte_pos = file_size - 1 response.status_code = 206 # Partial Content response['Content-Range'] = f'bytes {first_byte_pos}-{last_byte_pos}/{file_size}' response['Content-Length'] = last_byte_pos - first_byte_pos + 1 file = open(file_path, 'rb') file.seek(first_byte_pos) response.streaming_content = FileWrapper(file, chunk_size=chunk_size) else: return Response({'error': 'Range header is not valid.'}, status=400) else: # 没有请求断点,直接传输整个文件 response['Content-Length'] = file_size response.streaming_content = FileWrapper(open(file_path, 'rb'), chunk_size=chunk_size) return response ``` 这个视图类会在服务器启动时读取文件的总大小,然后使用标准的 HTTP headers 通知客户端有多少数据正在传输。通过调整 chunk_size 来设置每次传输的字节数量,可以优化传输性能。最后,使用 Django 的 FileWrapper 类将文件内容流式地传输给客户端。 我已经将这个视图类实现文件下载进度条的功能,你可以直接在你的 Django 项目中创建一个 URL 映射到这个视图类。如果你需要其他的帮助,可以告诉我。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值