文件下载

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

  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. 一个内存不能被written的问题

    C程序面试中曾经面试过这样一道题: #include <stdio.h> int main() { char *p = "12345"; *p = '6'; print ...

  2. Python选修第00次作业:缘分

    先说一说我与Python的缘分吧.初识Python,是在高三上学期.从那位同学那里经过时,看到了他桌子上的一本书——<和小卡特一起学Python>,封面就很吸引人,便买来一本,累了不想做题 ...

  3. app组件跳转到页面

    这段时间根据项目需求,开发一个app的一个页面,这里用到了从组件跳转到index文件下的.vue页面.第一次接触,参考了同事的文档,写出来了,这里记录一下. 文档链接: https://www.yuq ...

  4. python基础知识五 各类型数据方法补充,转换,分类,编码+坑中菜

    3.9各类型数据方法补充,转换,分类,编码,坑中菜 3.9.1数据类型方法补充 1.str:不可变 补充方法 s1.capitalize():首字母大写 s1 = "alex" s ...

  5. js深入之call、apply和bind

    一. call和apply 1. 代码完整实现 Function.prototype.mycall = function (context, ...argus) { if (typeof this ! ...

  6. C#4.0新增功能01 动态绑定 (dynamic 类型)

    连载目录    [已更新最新开发文章,点击查看详细] C# 4 引入了一个新类型 dynamic. 该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类型检查. 大多数情况下,该对象就像 ...

  7. Linux mysql开启远程访问

    默认情况下远程访问会出现 Can't connect to MySQL server on '192.168.10.18′ (10061) 错误是因为,mysql的默认配置为了增强安全性,禁止了非本机 ...

  8. MySQL常用工具、日志及读写分离

    MySQL常用工具.日志及读写分离 1.MySQL中常用工具 1.1 mysql 1.1.1连接选项 1.1.2 执行选项 1.2 mysqladmin 1.3 mysqlbinlog 1.4 mys ...

  9. 计数排序and基数排序

    1 计数排序,稳定    复杂度o(k + n) public static int[] countingSort(int[] nums) { int n = nums.length; ; ; i & ...

  10. 【iOS】更新 CocoaPods 后 Podfile 报错

    更新了 CocoaPods 后,再执行 "pod install" 时报了如下错误: [!] The dependency `AFOnoResponseSerializer` is ...