web文件上传

一、引入

随着web应用的发展,越来越多的web应用变得丰富起来,文件操作成为了软件的刚需。如何使用浏览器来进行多种方式的文件上传,并通过nodejs来处理上传的文件呢?下面我们就文件上传展开分析。

二、选择文件

首先要想上传一个文件,最起码得让浏览器知道,上传的文件究竟是哪一个。

两种选择文件的方式

1.input文件选择

在浏览器的input标签中当type属性为file时,当点击触发了这个标签后,浏览器会打开系统的文件选择器,这时就可以进行文件选择。

<input type="file" id="file" multiple="multiple">
file = document.getElementById('file');
file.onclick = function(e){
    e.target.value=""
}
file.onchange = function(e){
	console.log(e.target.files)
    console.log(e.target.value)
}
  • 一般通过设置input标签的change事件来监控文件变化,只要input所选文件变化之后,就会重新触发该事件。
  • 在事件回调中,e.target记录了触发的元素,即input标签。标签的value属性记录了所选文件的具体位置,标签的files属相记录了文件的名称大小类型等信息。
  • 一般使用文件选择的时候,默认只允许选择一个文件,如果涉及到了多个文件,可以在input的html中添加multiple=“multiple”。表示允许选择器可以进行多个文件的选择。
    • 需要注意的是:在选择多个文件时,files数组中存储的内容,为多个文件的数组。但是value属性只限制按照字符规则排序的第一个文件。
    • 在文件选择的时候只能选择同一路径下的文件,不能跨文件夹选择文件。
  • 还存在一些问题,当我们选择同一个文件两次的时候,会因为文件没有被改变,导致change事件根本没有被触发。或是点击取消按钮,value值置空了,事件异常触发了。
    • 这时我们可以绑定点击事件,当文件点击的时候清除它的value属性。
    • 之后当打开文件选择器之后,不论选择什么文件之前有没有选择过都会触发事件。
    • 之后点击取消文按钮,由于value之前就被置空了,也不会触发change更新。

2.js文件拖拽选择

在新版w3c规范中,浏览器支持了原生的拖拽事件,这样我们就可以使用拖拽功能来选择文件。

要想使用拖拽功能,我们首先得了解一下,有哪些拖拽事件:

  1. 被拖对象:dragstart事件,被拖动的元素,开始拖放触发
  2. 被拖对象:drag事件,被拖放的元素,拖放过程中
  3. 被拖对象:dragend事件,拖放的对象元素,拖放操作结束
  4. 经过对象:dragenter事件,拖放过程中鼠标经过的元素,被拖放的元素“开始”进入其它元素范围内(刚进入)
  5. 经过对象:dragover事件,拖放过程中鼠标经过的元素,被拖放的元素正在本元素范围内移动(一直)
  6. 经过对象:dragleave事件,拖放过程中鼠标经过的元素,被拖放的元素离开本元素范围
  7. 目标地点:drop事件,拖放的目标元素,其他元素被拖放到本元素中
var oBox = document.getElementById('box');
//需要放入的盒子
oBox.ondragenter = function() {
    oBox.innerHTML = '请释放鼠标';
};
oBox.ondragleave = function() {
    oBox.innerHTML = '请将文件拖拽到此区域';
};
oBox.ondrop = function(ev) {
    console.log(ev.dataTransfer.files);
};
  • 注意:当ondrop事件触发的时候,ev.dataTransfer.files中会包含选择的文件,但是由于事件回调参数为引用类型,只有在回调函数中才可以拿到相关的文件信息,之后通过引用指向访问到的文件列表会为空。
  • 如果没有将文件拖拽到指定的位置,这时会触发浏览器的默认事件,导致浏览器打开新的页面,直接执行程序,而不是上传文件。这时需要同时给document添加ondragover,ondrop事件,并禁止掉他们的默认事件。这样就可以保证浏览器,不会打开新的页面。

三、文件上传

经过了之前的文件选择,确定了上传的目标之后,就可以进行文件的具体上传了。

文件提交

ajax文件的上传需要使用FormData这个浏览器原生对象,通过对象的原型方法append,将文件内容转化为一个模拟表单。之后通过ajax的post方法向后台进行提交。

var form = new FormData()
form.append('files', oFile);//这里的oFile就是上一章节选择文件数组的具体一项
console.log(form)//打印看不出来有文件被添加了

url = 'http://localhost:1949/upload', //服务器上传地址
var xhr = new XMLHttpRequest();
xhr.open("post", url, true);
xhr.send(form); //开始上传

注意:生成的form的实例,控制台打印看不出来有文件被添加了,看上去就是一个空对象。这是因为绑定的文件是以二进制来书写的,控制台无法进行数据解析,所以表面看上起就是一个空数组。

post抓包分析

这里需要注意的是,在使用post请求发送之前,首先会进行一次options连接,当确定连接可以建立,才会发起真正的post请求。下面是Fiddler抓取的本地测试的post数据包。

POST http://localhost:1949/upload HTTP/1.1
Host: localhost:1949
Connection: keep-alive
Content-Length: 432100
Pragma: no-cache
Cache-Control: no-cache
Sec-Fetch-Mode: cors
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4X2oHxnVyC3gbFyk
Accept: */*
Sec-Fetch-Site: cross-site
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

------WebKitFormBoundary4X2oHxnVyC3gbFyk
Content-Disposition: form-data; name="file"; filename="Global方法.png"
Content-Type: image/png
具体文件数据信息...
*** FIDDLER: RawDisplay truncated at 262144 characters. Right-click to disable truncation. ***
  • 其中Content-Type:部分的内容 multipart/form-data; boundary=----WebKitFormBoundary4X2oHxnVyC3gbFyk

    这里的:multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。boundary字符串来分割请求头与请求体的

  • 二进制数据的最后面有字符的长度信息

  • 当遇到多个文件的上传问题时boundary会分为若干部分,如下所示。

Content-Type: multipart/form-data; boundary=${boundary} 

--${boundary}
具体文件数据信息...
--${boundary}--

--${boundary}
具体文件数据信息...
--${boundary}--

监控进度

可以使用xhr.upload添加progress事件,在回调的参数中,result.loaded / result.total分别表示已上传大小和全部大小。可以通过设计定时器,计算上传速率,可视化数据上传进度。

xhr.upload.addEventListener("progress", function(result) {
    console.log(result)
    console.log(result.loaded / result.total)
}, false);

四、后台处理

文件发送到了后台,紧接着就要进行文件的接收了。这里我们使用express和multer第三方模块处理网络请求,和解析文件。

const multer = require('multer')
var objMulter = multer({dest: './www/upload/'});//设置文件所在位置
server.use(objMulter.any());//使用中间件

//中间件处理的req.files表示解析的文件

经过中间件处理,文件会接收为二进制存储在指定的目录,文件会生成一个不会重复的散列值作为文件名,防止多文件上传中出现文件的冲突。但是我们为了保证我们文件具有可访问性,需要使用fs模块,对文件名称的后缀进行修改。可以使用 path.parse功能分析req.files的文件格式。

server.post('/upload', function(req, res) {
	console.log(pathLib.parse(req.files[0].originalname))
	var newName = req.files[0].path + pathLib.parse(req.files[0].originalname).ext;

	// 使用fs模块的rename重命名方法重名字保存的文件,才能正常使用
	//rename('旧文件名,新文件, 回调 ')
	fs.rename(req.files[0].path, newName, function(err) {
		if (err) {
			res.send('上传失败')
		} else {
			res.send('上传成功')
		}
		res.end();
	})
})

五、文件的再访问

我们将文件上传到了服务器后,可能也需要进行文件的再次访问。可以将文件的服务器路径在文件上传成功后,返回给前端。同时在服务器配置nginx或其他文件资源服务器,保证对应路径内容可以被访问到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值