投稿说明:如何向本公众号投稿?
最近在利用Django进行web后端开发。众所周知,Django是一个基于MTV的开发框架,与flask不同,Django自身带有成熟的ORM框架,因此在数据库的连接以及数据的查询等处理操作上非常方便。
但是,这两天在实现文件下载功能上却遇到了一些小问题。
这一段话说明,直接在js中进行文件的下载操作是不可行的,但是js的作用有DOM操作这种东西,通过超链接进行下载是允许的,如果利用js生成a标签再模拟点击,不就实现了下载功能?
1
def download(request):if request.method == "GET":
id = request.GET["id"]
file_path = "Themepark/static/file/" + id + ".xlsx"try:
response = FileResponse(open(file_path, "rb"))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="{}"'.format(os.path.basename(file_path))return responseexcept Exception:raise Http404elif request.method == "POST":pass
首先是Django的后端代码。根据官方的API,文件传送一共可以使用三种response对象。其中最常见的HttpResponse在大文件的传送上相对来说更加占用服务器的资源,因此在
面对大批量数据传送时通常采用StreamHttpResponse或FileResponse
。在这里我采用了FileResponse。请求方法使用的是GET请求,一切都按照官方的API编写。
function download() {var filelist = Array();
$('input:checked').each(function () {var brother = $(this).parentsUntil(".selected").next().children();var href = brother.attr("href");if (href) {
filelist.push(brother.attr("href"));
}
});var temp, link, json;for (var i = 0; i temp = filelist[i].split("?");
link = temp[0] + "/download?" + temp[1];
}
json = {url: link,type: "get",data: {},dataType: "json",success: function (resText, statusText) {console.log(resText);
},error: console.log("error")
};
$.ajax(json);
}
其次是Ajax代码。
关于Ajax异步请求,主要是为了在页面不进行刷新的情况下进行对后端服务器的请求。
由于整体采用的是jQuery框架,因此Ajax请求同样使用封装好的$.ajax()函数一步到位。回调函数的作用是正确接收返回值时在控制台输出返回值,若发生错误则直接打印error。
但就是这里出现了未知的错误:
[19/Feb/2020 05:13:08] "GET /themepark/download?id=7&csrfmiddlewaretoken=%7B%7B%20csrf_token%20%7D%7D HTTP/1.1" 301 0
[19/Feb/2020 05:13:08] "GET /themepark/download/?id=7&csrfmiddlewaretoken=%7B%7B%20csrf_token%20%7D%7D HTTP/1.1" 200 5248
后端的终端信息表明GET请求已经发送成功,然而前端的控制台却打印出error字样,说明在前后端交互过程中出现了不可预料的错误。
我在这个错误卡了几个小时,却丝毫未得到进展。
2
var temp, link;function send_ajax() {for (var i = 0; i temp = filelist[i].split("?");
link = temp[0] + "/download?" + temp[1];var xhr = new XMLHttpRequest();
xhr.open("get", link, true);
xhr.responseType = "blob";
xhr.onreadystatechange = function () {if(xhr.readyState==4 && xhr.status == 200){console.log("success");
}else{console.log("error");
}
}}
xhr.send();
}
在翻阅大量资料后问题仍未能得到解决,但这时看到一篇名为《ajax 请求二进制流 图片 文件 XMLHttpRequest 请求并处理二进制流数据 之最佳实践》的博客有点意思(https://www.cnblogs.com/cdemo/p/5225848.html)。其中作者提到,在查阅了jQuery的官方文档后发现,
jQuery所支持的dataType是没有二进制字节流的,也就是说jQuery所发起的Ajax传输的字节流都将转换为字符串。
因此想要利用Ajax进行字节流传输应该要使用原生的JavaScript。
这时我已经大概知道哪里存在问题了,在利用jQuery写Ajax请求时,我返回的dataType写的是json,然而从后端传送回来的是二进制字节流,与预期的dataType存在冲突,因此会调用error时的回调函数。既然知道问题所在,我立马将代码改成了上述利用原生js写出来的Ajax。
3
此时前端控制台确实打印出了success字样,可是又面临一个新问题:如何保存传输过来的二进制字节流?同样,在《关于Ajax无法下载文件到浏览器本地的问题》中有提到(https://blog.csdn.net/duansamve/article/details/84075021): 通过Ajax下载文件的这种方式本来就是禁止的。出于安全因素的考虑,javascript是不能够保存文件到本地的,所以ajax考虑到了这点,只是接受xml,ajax,json格式的返回值。这一段话说明,直接在js中进行文件的下载操作是不可行的,但是js的作用有DOM操作这种东西,通过超链接进行下载是允许的,如果利用js生成a标签再模拟点击,不就实现了下载功能?
var temp, link;function send_ajax() {for (var i = 0; i temp = filelist[i].split("?");
link = temp[0] + "/download?" + temp[1];var xhr = new XMLHttpRequest();
xhr.open("get", link, true);
xhr.responseType = "blob";
xhr.onreadystatechange = function () {if(xhr.readyState==4 && xhr.status == 200){var blob = new Blob([xhr.response]);var a = document.createElement('a');
a.download = 'data.xlsx';
a.href=window.URL.createObjectURL(blob);
a.click();
}else{console.log("error");
}
}}
xhr.send();
}
将接收到二进制字节流后的操作改成上述所示,就大功告成了!
试着点击下载一下,确实没毛病!因此问题关于Django + Ajax实现文件下载的问题也就都解决了。