整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

手把手教你前端的各种文件上传攻略和大文件断点续传



在前面

今年国庆假期终于可以憋在家里了不用出门了,不用出去看后脑了,真的是一种享受。这么好的光阴怎么浪费,睡觉、吃饭、打豆豆这怎么可能(耍多了也烦),完全不符合我们程序员的作风,赶紧起来把文章写完。

这篇文章比较基础,在国庆期间的业余时间写的,这几天又完善了下,力求把更多的前端所涉及到的关于文件上传的各种场景和应用都涵盖了,若有疏漏和问题还请留言斧正和补充。

自测读不读

以下是本文所涉及到的知识点,break or continue ?

  • 文件上传原理
  • 最原始的文件上传
  • 使用 koa2 作为服务端写一个文件上传接口
  • 单文件上传和上传进度
  • 多文件上传和上传进度
  • 拖拽上传
  • 剪贴板上传
  • 大文件上传之分片上传
  • 大文件上传之断点续传
  • node 端文件上传

原理概述

原理很简单,就是根据 http 协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件。

我们都知道如果要上传一个文件,需要把 form 标签的enctype设置为multipart/form-data,同时method必须为post方法。

那么multipart/form-data表示什么呢?

multipart互联网上的混合资源,就是资源由多种元素组成,form-data表示可以使用HTML Forms 和 POST 方法上传文件,具体的定义可以参考RFC 7578。

multipart/form-data 结构

看下 http 请求的消息体



  • 请求头:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次请求要上传文件,其中boundary表示分隔符,如果要上传多个表单项,就要使用boundary分割,每个表单项由———XXX开始,以———XXX结尾。

  • 消息体- Form Data 部分

每一个表单项又由Content-Type和Content-Disposition组成。

Content-Disposition: form-data 为固定值,表示一个表单元素,name 表示表单元素的 名称,回车换行后面就是name的值,如果是上传文件就是文件的二进制内容。

Content-Type:表示当前的内容的 MIME 类型,是图片还是文本还是二进制数据。

解析

客户端发送请求到服务器后,服务器会收到请求的消息体,然后对消息体进行解析,解析出哪是普通表单哪些是附件。

可能大家马上能想到通过正则或者字符串处理分割出内容,不过这样是行不通的,二进制buffer转化为string,对字符串进行截取后,其索引和字符串是不一致的,所以结果就不会正确,除非上传的就是字符串。

不过一般情况下不需要自行解析,目前已经有很成熟的三方库可以使用。

至于如何解析,这个也会占用很大篇幅,后面的文章在详细说。

最原始的文件上传

使用 form 表单上传文件

在 ie时代,如果实现一个无刷新的文件上传那可是费老劲了,大部分都是用 iframe 来实现局部刷新或者使用 flash 插件来搞定,在那个时代 ie 就是最好用的浏览器(别无选择)。

DEMO



这种方式上传文件,不需要 js ,而且没有兼容问题,所有浏览器都支持,就是体验很差,导致页面刷新,页面其他数据丢失。

HTML

 <form method="post" action="http://localhost:8100" enctype="multipart/form-data">

        选择文件:
            <input type="file" name="f1"/> input 必须设置 name 属性,否则数据无法发送<br/>
<br/>
            标题:<input type="text" name="title"/><br/><br/><br/>

        <button type="submit" id="btn-0">上 传</button>

</form>

复制代码

文件上传接口

服务端文件的保存基于现有的库koa-body结合 koa2实现服务端文件的保存和数据的返回。

在项目开发中,文件上传本身和业务无关,代码基本上都可通用。

在这里我们使用koa-body库来实现解析和文件的保存。

koa-body 会自动保存文件到系统临时目录下,也可以指定保存的文件路径。



然后在后续中间件内得到已保存的文件的信息,再做二次处理。

  • ctx.request.files.f1 得到文件信息,f1为input file 标签的 name
  • 获得文件的扩展名,重命名文件

NODE

/**
 * 服务入口
 */
var http = require('http');
var koaStatic = require('koa-static');
var path = require('path');
var koaBody = require('koa-body');//文件保存库
var fs = require('fs');
var Koa = require('koa2');

var app = new Koa();
var port = process.env.PORT || '8100';

var uploadHost= `http://localhost:${port}/uploads/`;

app.use(koaBody({
    formidable: {
        //设置文件的默认保存目录,不设置则保存在系统临时目录下  os
        uploadDir: path.resolve(__dirname, '../static/uploads')
    },
    multipart: true // 开启文件上传,默认是关闭
}));

//开启静态文件访问
app.use(koaStatic(
    path.resolve(__dirname, '../static') 
));

//文件二次处理,修改名称
app.use((ctx) => {
    var file = ctx.request.files.f1;//得道文件对象
    var path = file.path;
    var fname = file.name;//原文件名称
    var nextPath = path+fname;
    if(file.size>0 && path){
        //得到扩展名
        var extArr = fname.split('.');
        var ext = extArr[extArr.length-1];
        var nextPath = path+'.'+ext;
        //重命名文件
        fs.renameSync(path, nextPath);
    }
    //以 json 形式输出上传文件地址
    ctx.body = `{
        "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
    }`;
});

/**
 * http server
 */
var server = http.createServer(app.callback());
server.listen(port);
console.log('demo1 server start ......   ');
复制代码

CODE

https://github.com/Bigerfe/fe-learn-code/

avaScript是基于事件驱动的编程模型,当网页页面发生某种形式的交互操作或者变化时就会产生事件。举个简单的例子:用户在网页页面点击鼠标按键就会产生一个点击事件。鼠标移动则会产生鼠标移动的事件。因此事件对于JavaScript客户端脚本语言至关重要,在JavaScript编程模型中事件是实现一切前端交互操作的基础与依据。事件的学习、理解与使用成为JavaScript编程学习的重点与难点。本文主要介绍部分常用事件及事件处理。

event事件


1、事件的类型与事件处理

在面向对象程序设计语言中,事件是依附于对象的。不同的对象具有不同类型的事件类型及事件处理的方法。JavaScript中一个事件主要由事件对象、事件类型与事件处理三部分所组成。JavaScript常用事件类型主要包括键盘事件、鼠标与滚轮事件、页面事件、表单事件、设备事件等。在页面运行过程中,JavaScript通过事件类型,检测发生的事件,并对事件进行处理。事件处理程序响应事件、处理时间需要按照一定的顺序执行,事件顺序主要包括事件捕获与时间冒泡两种类型。其中事件捕获从HTML最外层开始处理事件,直到底层元素事件的处理完成。事件冒泡则与事件捕获相反,从子元素开始响应事件。在默认情况下消息响应都是从子元素开始的。

冒泡处理实例

冒泡处理实例如上图,div元素嵌套,将两个div分为父元素与子元素,同样定义onclick单击事件,首先响应的是内层子元素。

2、事件的绑定

除直接在对HTML元素直接定义事件属性之外,还可以通过事件绑定形式实现事件的注册、绑定与监听。通过调用DOM文档对象模型提供的addEventListener()方法实现为网页页面元素添加事件,该函数的原型描述如下:

addEventListenser(type,listener [,options ])
type:事件类型
listener:事件处理函数名称
options: bool类型,事件处理方式,捕获、冒泡

通过对DOM对象进行事件绑定就可以响应制定的消息,但需注意,页面元素能够响应的事件类型。事件绑定实例描述如下:

事件监听与处理

事件绑定实例如上图所示,我们给id为test的div元素绑定了click事件,并用myfun函数对事件进行处理。其中事件类型是需要编写者注意的问题,这些事件类型是定义好的,我们只需要选择即可,例如鼠标事件的定义如下表所示:

鼠标事件类型

除以上事件绑定之外,还可以通过以下简单形式绑定事件,如上例题事件可通过一下语句定义:

事件绑定的简单形式

3、事件的解除

通常情况下事件的绑定执行之后是不会移除事件的,但如果需要解除事件的绑定,则可以通过removeEventListener()方法或者对绑定事件的DOM对象对应时间名赋值为null实现事件的解除。实际案例如下所示:

事件移除操作

4、event对象

event对象是JavaScript语言中重要的对象之一,用于存储记录交互响应实现相关基本信息,event对象主要存储信息包括事件类型、事件对应的按键信息及事件发生相关坐标信息等。event对象是其他各类事件的父类,其他事件对象均为该类的子类。event对象会在事件调用过程中以隐藏的形式进行参数的传递。event事件使用描述如下图:

event事件使用实例


以上给出了JavaScript中的事件基本概念及案例分析,如有问题可在评论区讨论。本头条号长期关注编程资讯分享;编程课程、素材、代码分享及编程培训。如果您对以上方面有兴趣或代码错误、建议与意见,可以联系作者,共同探讨。期待大家关注!相关文章链接如下:

前端开发-JavaScript初学者练习题及参考答案

前端开发-CSS3动画实现焦点(图文轮播)图效果

前端开发-JavaScript DOM动态生成文本框

前端设计-教你如何快速绘制HTML5动画

前端设计-响应式页面开发基础

前端设计-Ajax技术及实例展示

var EventUtil = {
    addHandler: function(element, type, handler){
    if (element.addEventListener){
    element.addEventListener(type, handler, false);
    } else if (element.attachEvent){
    element.attachEvent("on" + type, handler);
    } else {
    element["on" + type] = handler;
    }
    },
    removeHandler: function(element, type, handler){
    if (element.removeEventListener){
    element.removeEventListener(type, handler, false);
    } else if (element.detachEvent){
    element.detachEvent("on" + type, handler);
    } else {
    element["on" + type] = null;
    }
    },
    getEvent: function(event){
    return event ? event : window.event;
    },
    getTarget: function(event){
    return event.target || event.srcElement;
    },
    preventDefault: function(event){
    if (event.preventDefault){
    event.preventDefault();
    } else {
    event.returnValue = false;
    }
    },
    stopPropagation: function(event){
    if (event.stopPropagation){
    event.stopPropagation();
    } else {
    event.cancelBubble = true;
    }
    }
};

用方法: