zoukankan      html  css  js  c++  java
  • HTML文件上传与下载

    文件下载

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

    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

  • 相关阅读:
    数据结构与算法(15)——冒泡法和选择法排序
    数据结构与算法(14)——二分查找算法
    数据结构与算法(13)—顺序查找法
    数据结构与算法(12)——动态规划案例
    Object Detection的一些进展(Valse2020.4.30)
    机器学习(1)——模型评估与选择
    数据结构与算法(12)—分治策略
    数据结构与算法(11)—递归
    数据结构与算法(10)——有序表OrderedList
    数据结构与算法(9)——无序表List
  • 原文地址:https://www.cnblogs.com/V587Chinese/p/11371380.html
Copyright © 2011-2022 走看看