文件下载

传统的文件下载有两种方法:

  1. 使用<a/>标签,href属性直接连接到服务器的文件路径
  2. window.location.href="url"

这两种方法效果一样。但有个很大的问题,如果下载出现异常(连接路径失效、文件不存在、网络问题等),会导致原本的页面被覆盖掉,显示404等错误信息。

大致的优化思路如下:

  1. 使用<a/>标签HTML5新的属性download。
  2. 使用<iframe><iframe/>元素进行下载。
  3. 使用ajax、axios、fetch等方法异步下载。
  4. 使用websocket下载。

我们来逐一分析:

  1.  <a/>标签的download属性,需要和href一起用,download的作用是为下载的文件赋文件名。

    • 如果服务端没有指定文件名,就以此属性规定的名称命名。
    • 如果下载出现异常,该属性的存在能够保证页面不会出问题。
    • 如果服务端返回的不是文件、而是字符,如果download=‘’error.txt”,能够通过打开此文件查看到返回的文本信息。
  2. <iframe>标签可以做到在现有的页面下,内嵌一个子页面。当用户点击文件下载时,将隐藏的iframe元素的src属性指向文件下载路径。
    • 如果没有异常,文件将会直接下载。
    • 如果出现异常,iframe子页面会报错,父页面不会受任何影响。
  3. 使用异步请求进行下载。
    • 在网上看了看,大致的流程是:发送异步请求时设置responseType为blob,即接收流数据为blob对象保存在内存中。接收完成后,生成链接地址(1.通过FileReader对象将blob对象生成base64编码 2.通过URL.createObjectURL生成指向文件内存的链接),写入<a/>标签的href属性,然后模拟点击<a/>按标签实现下载。
    • 此方法最大的问题是,因无法直接操作磁盘,故接收的文件必须先存放在内存中(且只有传输完成后才能构建blob对象),才能转化成文件。因此,大文件的下载可能会把你的浏览器挤爆。
  4. 使用websocket下载。
    • 需要额外开启websocket服务,此方法未做实践。

总结以上方法,最推荐前两种,方便简单。

附上后端Django代码(适用于前两种方法):

def syncDownLoad(request):
"文件下载"
print("同步下载文件")
startTime = time.time() def file_iterator(file, chunk_size=1024):
with open(file, "rb") as f:
while True:
c = f.read(chunk_size)
if c:
yield c
else:
endTime = time.time()
print("传输时间", endTime - startTime)
break fileRoute = "/static/files/2018/12/18/第四章(1)学习动机概述.mp4"
fileName = "第四章(1)学习动机概述.mp4"
route = os.path.dirname(os.path.dirname(__file__)) + fileRoute
if os.path.exists(route): # 如果存在文件
response = StreamingHttpResponse(file_iterator(route))
# response['Content-Type'] = 'application/octet-stream'
response['Content-Type'] = 'text/html'
response['Content-Disposition'] = 'attachment;filename="{0}"'.format(fileName).encode("utf-8")
return response
else:
return HttpResponse("cannot find file")

参考链接:

https://scarletsky.github.io/2016/07/03/download-file-using-javascript/

https://my.oschina.net/watcher/blog/1525962

文件上传

概述

文件上传需要处理的问题有:

1.多文件上传  2.异步上传  3.拖拽上传  4.上传限制(限制大小、类型) 5.显示上传进度、上传速度、中途取消上传  6.预览文件

HTML DEMO

<input type="file" id="file" name="myfile" onchange="onchanges()" multiple="multiple"/>
<input type="button" onclick="SerialUploadFile()" value="上传"/>

一、多文件上传

<input type="file" id="file" name="myfile" multiple="multiple"/> <!-- multiple属性 -->

二、异步上传

通过ajax等方式异步上传,FormData对象支持传输文件。

function UploadFile() {
var fileObj = document.getElementById("file").files; // js 获取文件对象(FileList对象) // FormData 对象
var form = new FormData();
form.append("author", "xueba"); // 可以增加表单数据
for (let i = 0; i < fileObj.length; i++)
{
form.append("file", fileObj[i]); // 文件对象
} $.ajax({
url: "/file_upload/",
type: "POST",
async: true, // 异步上传
data: form,
contentType: false, // 必须false才会自动加上正确的Content-Type
processData: false, // 必须false才会避开jQuery对 formdata 的默认处理。XMLHttpRequest会对 formdata 进行正确的处理
success: function (data) {
data = JSON.parse(data);
data.forEach((i)=>{
console.log(i.code,i.file_url);
});
},
error: function () {
alert("aaa上传失败!");
},
}); }

三、拖拽上传

默认文本、图像和链接可以被拖动。其它的元素想要被拖动,只需为标签加一个draggable="true"属性

<div draggable="true"><div/>

HTML5 API drag 和 drop

被拖动元素发生的事件
dragstart 被拖动元素开始拖动时
drag 正在被拖动时
dragend 取消拖拽时 目标元素发生的事件(当某元素被绑定以下事件就变成了目标元素)
dragenter 拖动元素进入目标上触发
dragover 拖动元素在目标元素上移动触发
dragleave 拖动元素离开目标时触发
drop 拖动元素在目标上释放触发,这时不会触发dragleave 注意:
1.目标元素默认不能够被拖放drop,要在dragover事件中取消默认事件(e.preventDefault())
2.有些元素(img)被拖放后,默认以链接形式打开,要在drop事件中取消默认事件(e.preventDefault())
【火狐浏览器可能不顶用,需要再加event.stopPropagation()】 dataTransfer(事件对象属性(对象))
数据交换:只是简单的拖拽没有意义,我们还需要数据交换,即被拖动元素和目标元素之间的数据交换。
方法:
setData(key,value) 设置数据(key和value都必须是string类型)
getData(key) 获取数据
clearData() 清除数据(不传参清空所有数据)
setDragImage(imgElement,x,y) 设置元素移动过程中的图像(参数:图像元素,xy表示图像内的偏移量)
属性:
dropEffect 表示被拖动元素可以执行哪一种放置行为(一般在dragover事件内设置)
none禁止放置(默认值)
move移动到新的位置
copy复制到新的位置
link
effectAllowed 用来指定拖动时被允许的行为(一般无需设置)
copy,move,link,copyLink,copyMove,linkMove,all,none,uninitialized默认值,相当于all.
files FileList对象。如果拖动的不是文件,此为空列表
items 返回DataTransferItems对象,该对象代表了拖动数据。
types 返回一个DOMStringList对象,该对象包括了存入dataTransfer中数据的所有类型。 注意:
1.如果拖拽了文本,浏览器会自动调用setData(),设置对应文本数据

该功能没有Demo

参考链接:

https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API

https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer

https://www.zhangxinxu.com/wordpress/2018/09/drag-drop-datatransfer-js/

http://www.sohu.com/a/198973397_291052

四、上传限制

<input type="file"  accept="image/*" /> 接收全部格式的图片

此外,获取到的File对象中有type属性可以得知文件类型,size属性的得知文件大小

五、上传进度、上传速度、中途取消上传

原生API

xhr.onload = function(e){};//上传请求完成
xhr.onerror = function(e){};//上传异常
xhr.upload.onloadstart = function(e){};//开始上传
xhr.upload.onprogress =function(e){};//上传进度 这个方法会在文件每上传一定字节时调用 e.loaded//表示已经上传了多少byte的文件大小
e.total//表示文件总大小为多少byte
通过这两个关键的属性就可以去计算 上传进度与速度 xhr.onreadystatechange = function(){}//当xhr的状态(上传开始,结束,失败)变化时会调用 该方法可以用来接收服务器返回的数据 中途取消上传 xhr.abort();

单文件上传 或 多文件串行上传 Demo:(该Demo只会有一个进度条,显示上传总进度。对应“异步上传”的代码)

xhr.upload.addEventListener("progess",progessSFunction,false); // 上传过程中显示进度和速度

function progressSFunction(e) {
var progressBar = document.getElementById(`pro`);
var percentageDiv = document.getElementById(`per`);
if (e.lengthComputable) // lengthComputable表示进度信息是否可用
{
progressBar.max = e.total;
progressBar.value = e.loaded;
let speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) + " bytes/s";
let percent = Math.round(e.loaded / e.total * 100) + "%";
progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp;
percentageDiv.innerHTML = percent + " " + speed;
}
}

多文件并行上传进度显示:(多个进度条,分别上传)

// 多文件并行上传
function ParallelUploadFile() {
last_laoded = 0;
last_time = (new Date()).getTime(); var fileObj = document.getElementById("file").files; // js 获取文件对象
for (let k = 0; k < fileObj.length; k++)
{ let domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字节
<progress class='progressBar' id='pro${k}' value='' max=''></progress>
<span class='percentage' id='per${k}'></span>
</div>`;
$("body").append(domStr); // FormData 对象
var form = new FormData();
form.append("author", "xueba"); // 可以增加表单数据
form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val());
form.append("file", fileObj[k]); // XMLHttpRequest 对象
{#var xhr = new XMLHttpRequest();#}
{#xhr.open("post", "/file_upload/", true);#}
{#xhr.onload = function () {#}
{# alert("上传完成!");#}
{# };#}
{#xhr.upload.addEventListener("progress", progressFunction, false);#}
{#xhr.send(form);#} // jQuery ajax
$.ajax({
url: "/file_upload/",
type: "POST",
async: true, // 异步上传
data: form,
contentType: false, // 必须false才会自动加上正确的Content-Type
processData: false, // 必须false才会避开jQuery对 formdata 的默认处理。XMLHttpRequest会对 formdata 进行正确的处理
xhr: function () {
let xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)}, false);
xhr.upload.onloadstart = (e) => {
progress[k] = {
last_laoded: 0,
last_time: e.timeStamp,
};
};
xhr.upload.onloadend = () => {
delete progress[k];
};
return xhr;
},
success: function (data) {
data = JSON.parse(data);
data.forEach((i) => {
console.log(i.code, i.file_url);
});
},
error: function () {
alert("aaa上传失败!");
},
});
} }

六、预览文件

预览图片

function onchanges() { // input file绑定onchange事件
let files = document.getElementById("file").files;
if(files[0].type.indexOf("image")>-1)
{
let read = new FileReader();
read.onload = function(e) { // 读取操作完成时触发
let img = new Image();
img.src = e.target.result; // 将base64编码赋给src属性
   $("body")[0].appendChild(img);
};
read.readAsDataURL(files[0]); // 读取文件转化成base64编码
}

七、前后端汇总Demo

前端

HTML

<input type="file" id="file" name="myfile" onchange="onchanges()" multiple="multiple"/>
<input type="button" onclick="SerialUploadFile()" value="上传"/>

JavaScript

   let progress = {};
let last_laoded;
let last_time; function onchanges() {
let files = document.getElementById("file").files;
console.log(`共${files.length}个文件`);
let countSize = 0;
for (let i = 0; i < files.length; i++) {
console.log(`${files[i].name} 大小${files[i].size}`);
countSize += files[i].size;
}
console.log(`共计占用${countSize}字节`);
if (files[0].type.indexOf("image") > -1)
{
let read = new FileReader();
read.onload = function (e) { // 读取操作完成时触发
let img = new Image();
img.src = e.target.result; // 将base64编码赋给src属性
$("body")[0].appendChild(img); };
read.readAsDataURL(files[0]); // 读取文件转化成base64编码
}
} // 多文件并行上传
function ParallelUploadFile() {
last_laoded = 0;
last_time = (new Date()).getTime(); var fileObj = document.getElementById("file").files; // js 获取文件对象
for (let k = 0; k < fileObj.length; k++)
{ let domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字节
<progress class='progressBar' id='pro${k}' value='' max=''></progress>
<span class='percentage' id='per${k}'></span>
</div>`;
$("body").append(domStr); // FormData 对象
var form = new FormData();
form.append("author", "xueba"); // 可以增加表单数据
form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val());
form.append("file", fileObj[k]); // XMLHttpRequest 对象
{#var xhr = new XMLHttpRequest();#}
{#xhr.open("post", "/file_upload/", true);#}
{#xhr.onload = function () {#}
{# alert("上传完成!");#}
{# };#}
{#xhr.upload.addEventListener("progress", progressFunction, false);#}
{#xhr.send(form);#} // jQuery ajax
$.ajax({
url: "/file_upload/",
type: "POST",
async: true, // 异步上传
data: form,
contentType: false, // 必须false才会自动加上正确的Content-Type
processData: false, // 必须false才会避开jQuery对 formdata 的默认处理。XMLHttpRequest会对 formdata 进行正确的处理
xhr: function () {
let xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)}, false);
xhr.upload.onloadstart = (e) => {
progress[k] = {
last_laoded: 0,
last_time: e.timeStamp,
};
};
xhr.upload.onloadend = () => {
delete progress[k];
};
return xhr;
},
success: function (data) {
data = JSON.parse(data);
data.forEach((i) => {
console.log(i.code, i.file_url);
});
},
error: function () {
alert("aaa上传失败!");
},
});
} } // 多文件串行上传
function SerialUploadFile() { var fileObj = document.getElementById("file").files; // js 获取文件对象 let domStr = `<div>
<progress class='progressBar' id='pro' value='' max=''></progress>
<span class='percentage' id='per'></span>
</div>`;
$("body").append(domStr); // FormData 对象
var form = new FormData();
form.append("author", "xueba"); // 可以增加表单数据
for (let i = 0; i < fileObj.length; i++)
{
form.append("file", fileObj[i]); // 文件对象
} // jQuery ajax
$.ajax({
url: "/file_upload/",
type: "POST",
async: true, // 异步上传
data: form,
contentType: false, // 必须false才会自动加上正确的Content-Type
processData: false, // 必须false才会避开jQuery对 formdata 的默认处理。XMLHttpRequest会对 formdata 进行正确的处理
xhr: function () {
let xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener("progress", progressSFunction, false);
xhr.upload.onloadstart = (e) => {
progress[0] = {
last_laoded: 0,
last_time: e.timeStamp,
};
console.log("开始上传",progress);
};
xhr.upload.onloadend = () => {
delete progress[0];
console.log("结束上传",progress);
};
return xhr;
},
success: function (data) {
data = JSON.parse(data);
data.forEach((i) => {
console.log(i.code, i.file_url);
});
},
error: function () {
alert("aaa上传失败!");
},
}); } // jQuery版本进度条
function Progressbar(e) {
var bar = $("#progressBar"); // 进度条
var num = $("#percentage"); // 百分比
if (e.lengthComputable) {
bar.attr("max", e.total);
bar.attr("value", e.loaded);
num.text(Math.round(e.loaded / e.total * 100) + "%");
} } // 原生js版 并行进度条
function progressPFunction(e, k) {
var progressBar = document.getElementById(`pro${k}`);
var percentageDiv = document.getElementById(`per${k}`);
if (e.lengthComputable) {
progressBar.max = e.total;
progressBar.value = e.loaded;
let speed = (e.loaded - progress[k].last_laoded) / (e.timeStamp - progress[k].last_time) + " bytes/s";
let percent = Math.round(e.loaded / e.total * 100) + "%";
progress[k].last_laoded = e.loaded, progress[k].last_time = e.timeStamp;
percentageDiv.innerHTML = percent + " " + speed;
console.log(speed);
}
} // 原生js 串行进度条
function progressSFunction(e) {
var progressBar = document.getElementById(`pro`);
var percentageDiv = document.getElementById(`per`);
if (e.lengthComputable) // lengthComputable表示进度信息是否可用
{
progressBar.max = e.total;
progressBar.value = e.loaded;
let speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) + " bytes/s";
let percent = Math.round(e.loaded / e.total * 100) + "%";
progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp;
percentageDiv.innerHTML = percent + " " + speed;
}
}

Django后端

def file_upload(request):
"ajax文件上传功能"
resList, fileList = [], request.FILES.getlist("file")
dir_path = 'static/files/{0}/{1}/{2}'.format(time.strftime("%Y"),time.strftime("%m"),time.strftime("%d"))
if os.path.exists(dir_path) is False:
os.makedirs(dir_path)
for file in fileList:
file_path = '%s/%s' % (dir_path, file.name)
file_url = '/%s/%s' % (dir_path, file.name)
res = {"code": 0, "file_url": ""}
with open(file_path, 'wb') as f:
if f == False:
res['code'] = 1
for chunk in file.chunks(): # chunks()代替read(),如果文件很大,可以保证不会拖慢系统内存
f.write(chunk)
res['file_url'] = file_url
resList.append(res)
return HttpResponse(json.dumps(resList))

参考:

https://www.cnblogs.com/potatog/p/9342448.html

https://www.w3cmm.com/ajax/progress-events.html

HTML文件上传与下载的更多相关文章

  1. java web学习总结(二十四) -------------------Servlet文件上传和下载的实现

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  2. (转载)JavaWeb学习总结(五十)——文件上传和下载

    源地址:http://www.cnblogs.com/xdp-gacl/p/4200090.html 在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传 ...

  3. JavaWeb学习总结,文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  4. java文件上传和下载

    简介 文件上传和下载是java web中常见的操作,文件上传主要是将文件通过IO流传放到服务器的某一个特定的文件夹下,而文件下载则是与文件上传相反,将文件从服务器的特定的文件夹下的文件通过IO流下载到 ...

  5. 使用jsp/servlet简单实现文件上传与下载

    使用JSP/Servlet简单实现文件上传与下载    通过学习黑马jsp教学视频,我学会了使用jsp与servlet简单地实现web的文件的上传与下载,首先感谢黑马.好了,下面来简单了解如何通过使用 ...

  6. JavaWeb学习总结(五十)——文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  7. 文件上传和下载(可批量上传)——Spring(三)

    在文件上传和下载(可批量上传)——Spring(二)的基础上,发现了文件下载时,只有在Chrome浏览器下文件名正常显示,还有发布到服务器后,不能上传到指定的文件夹目录,如上传20160310.txt ...

  8. 文件上传和下载(可批量上传)——Spring(二)

    针对SpringMVC的文件上传和下载.下载用之前“文件上传和下载——基础(一)”的依然可以,但是上传功能要修改,这是因为springMVC 都为我们封装好成自己的文件对象了,转换的过程就在我们所配置 ...

  9. Struts2 之 实现文件上传和下载

    Struts2  之 实现文件上传和下载 必须要引入的jar commons-fileupload-1.3.1.jar commons-io-2.2.jar 01.文件上传需要分别在struts.xm ...

  10. 基于jsp的文件上传和下载

    参考: 一.JavaWeb学习总结(五十)--文件上传和下载 此文极好,不过有几点要注意: 1.直接按照作者的代码极有可能listfile.jsp文件中 <%@taglib prefix=&qu ...

随机推荐

  1. Jenkins+SVN+Maven+shell 自动化部署实践

      JAVA环境中利用Jenkins+svn+maven进行自动化部署实践   一. 前言2 1.介绍jenkins2 1.本地项目打包2 2.通过secureCRT工具,手动传输到服务器2 3.然后 ...

  2. 自定义ApplicationContextInitializer接口实现

    简介 ApplicationContextInitializer是Spring框架提供的接口, 该接口的主要功能就是在接口ConfigurableApplicationContext刷新之前,允许用户 ...

  3. 【模拟】(正解树状数组)-校长的问题-C++-计蒜客

    描述 学校中有 n 名学生,学号分别为 1 - n.再一次考试过后,学校按照学生的分数排了一个名次(分数一样,按照名字的字典序排序).你是一名老师,你明天要和校长汇报这次考试的考试情况,校长询问的方式 ...

  4. java调用新浪接口根据Ip查询所属地区

    import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import ...

  5. .NET CORE 微信小程序消息验证的坑

    进入微信小程序,点击开发->选择消息推送->扫码授权,填写必要参数 进入接口开发: /// <summary> /// 验证小程序 /// </summary> / ...

  6. JAVA面试题 StringBuffer和StringBuilder的区别,从源码角度分析?

    面试官Q1:请问StringBuffer和StringBuilder有什么区别? 这是一个老生常谈的话题,笔者前几年每次面试都会被问到,作为基础面试题,被问到的概率百分之八九十.下面我们从面试需要答到 ...

  7. Kafka集群部署以及使用

    Kafka集群部署 部署步骤 hadoop102 hadoop103 hadoop104 zk zk zk kafka kafka kafka http://kafka.apache.org/down ...

  8. Python在office开发中的应用

    Python with Excel 有几个很好的Python模块能够方便地操作Excel的数据,包括读与写,不要求本地安装Excel.例如pandas, openpyxl, xlrd, xlutils ...

  9. TensorFlow(1)-基础知识点总结

    1. tensorflow简介 Tensorflow 是 google 开源的机器学习工具,在2015年11月其实现正式开源,开源协议Apache 2.0. Tensorflow采用数据流图(data ...

  10. 《VR入门系列教程》之8---GearVR

    高端移动虚拟现实设备---三星GearVR     Oculus Rift也许是虚拟现实头显的典范,但是它还是存在许多问题.首先,它需要基于一个具有强大图形计算能力的计算机,而使用一般的笔记本.苹果A ...