先上最终解决代码(有待验证各浏览器效果):
def download_file(request, file_path): file_name = os.path.basename(file_path) if not os.path.isfile(file_path): return HttpResponse(file_name) def file_iterator(tar_file_path, chunk_size=512): with open(tar_file_path, mode=\'rb\') as file: while True: content = file.read(chunk_size) if content: yield content else: break try: response = StreamingHttpResponse(file_iterator(file_path)) response[\'Content-Type\'] = \'application/octet-stream\'response["Content-Disposition"] = "attachment; filename*=UTF-8\'\'{}".format(escape_uri_path(file_name)) except: return HttpResponse("Sorry but Not Found the File") return response
重点在于黄色记号笔标注的那行代码。
网上大多资料都是这么写的:
response[\'Content-Disposition\'] = \'attachment;filename="{}"\'.format(file_name)
这种写法对应纯英文的文件名是没有问题的,因为 Content-Disposition 里面的 filename ,不是RFC标准,仅支持ASCII编码的文件名。如果文件名不是英文的,就会出现名字乱码,或者被改名的情况。
如何直接采用解码的方式也还是会出现byte数组的文件名:
response[\'Content-Disposition\'] = \'attachment;filename="{}"\'.format(file_name.encode(\'utf8\'))
得到的结果类似于这样:b\'-xc6-xbd-xcc-xa8-xc4-xda-xb2-xbf-xb2-xe2-xca-xd4.xlsx\' (3).xls
原因是不同浏览器对于下载文件文件名的编码解析格式不一样,常用浏览器解析格式如下:
- IE浏览器,采用URLEncoder编码
- Opera浏览器,采用filename*方式
- Safari浏览器,采用ISO编码的中文输出
- Chrome浏览器,采用Base64编码或ISO编码的中文输出
- FireFox浏览器,采用Base64或filename*或ISO编码的中文输出
如果硬来的话就是在后台把文件名先 encode 成 bytes,再判断浏览器,根据不同的浏览器用相应的编码decode一下就好了
例如浏览器是FireFox,后台编码是 utf-8,则进行如下操作
response[\'Content-Disposition\'] = \'attachment; filename=\' + filename.encode(\'utf-8).decode(\'ISO-8859-1\')