原理
后端接口读取文件对象,用文件流的形式发送给浏览器,前端创建一个临时的下载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')
},
文件下载的功能就完成了