文件上传的几种方式
1、表单上传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单上传</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="./uuid.js"></script>
</head>
<body>
<h2>表单上传</h2>
<form action="/api/upload" enctype="multipart/form-data" method="POST">
<input name="file" type="file" id="file">
<input type="submit" value="提交">
</form>
<script type="text/javascript">
</script>
</body>
</html>
【备注】
知识点:
enctype(encodetype):该属性规定在将表单数据发送到服务器之前如何对其进行编码。
application/x-www-form-urlencoded: 在发送前编码所有字符(默认)
multipart/form-data: 不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。
text/plain: 空格转换为 "+" 加号,但不对特殊字符编码。
重点:
1、请求方式必须是 POST
2、默认是对表单数据以 "application/x-www-form-urlencoded"进行编码。
3、enctype(encodetype): 编码类型
multipart/form-data: 已二进制形式上传文件,支持多种类型的文件上传
application/x-www-form-urlencoded: 只能上传文本格式文件
缺点:
1、表单提交属于同步提交,提交后整个页面都会发生跳转,用户体验很差。
2、页面之前的状态和数据都会丢失。
解决办法:采用异步上传。
2、异步上传(formData)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>formData上传</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<h2>formData上传</h2>
<input type="file" name="file" id="file">
<button id="upload">上传</button>
<script type="text/javascript">
//发送请求
$("#upload").click(upload)
function upload() {
var files = document.getElementById("file").files;
var blob = document.getElementById("file").files[0];
console.log("file: ", files);
console.log("blob: ", blob);
// 利用formData来传递数据
var formData = new FormData();
formData.append("文件名:", blob.name);
formData.append("file", blob);
// ajax 方法上传数据
$.ajax({
url: "/api/upload",
type: "POST",
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
}).done(function(res){
console.log('formData 方式上传表单成功!');
}).fail(function(res){
console.log('formData 方式上传表单失败!');
});
// XMLHttpRequest 方法上传数据
var request = new XMLHttpRequest();
request.open("POST", "/api/upload");
request.send(formData);
}
</script>
</body>
</html>
【备注】
知识点:
1、FormData 接口 是 XMLHttpRequest Level2 新加的接口。它提供了一种表单数据的键值对 key/value 的构 造方法,并且可以轻松的将数据通过 XMLHttpRequest.send() 方法发送出去。
2、XMLHttpRequest(XHR) 对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 的 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。
3、file 是文件接口,它提供了有关文件的信息,并允许网页中 JS 访问其内容。
重点:
1、formData 对象用以将数据编译成键值对,以便用 XMLHttpRequest 来发送数据。
2、formData 对象附加的文件可以是 file 类型 和 Blob 类型。
3、file 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。
4、Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream(二进制流)来用于操作数据。
5、在 formData 上传文件时,上传参数数据类型会影响请求头部 content-type 的值,所以不必设置 content- type 的值,浏览器会根据文件类型自动配置。
拓展:
目前,FromData 基本上满足我们的上传需求,但是当文件比较大时,普通的上传方式就会出现一些问题:
1、文件上传超时。
2、文件上传大小超限。
3、上传耗时久。
4、其它网络原因导致上传失败,且失败后需要从头开始。
解决办法:大文件上传。
3、大文件上传
通用解决方案:切片上传 + 秒传
切片上传:
是指将一个大文件切割为若干个小文件,分为多个请求依次上传,后台再将文件碎片拼接为一个完整的文件,即使 某个碎片上传失败,也不会影响其它文件碎片,只需要重新上传失败的部分就可以了。优点是多个请求一起发送文件, 提高了传输速度的上限。
秒传:
是指将文件再传输之前计算其内容的散列值,也就是 Hash 值,将该值传到后台,如果后台存在 Hash 值一致的文 件,认为该文件上传完成。
【文件切片】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>切片上传</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="./uuid.js"></script>
</head>
<body>
<h2>切片上传</h2>
<input type="file" name="file" id="file">
<button id="upload">上传</button>
<script type="text/javascript">
//var bytesPerPiece = 1024 * 1024; // 每个文件切片大小定为1MB .
var bytesPerPiece = 100 * 100; // 测试数据
var totalPieces; //切片总数
//发送请求
$("#upload").click(upload)
function upload() {
var blob = document.getElementById("file").files[0];
// 文件唯一标识符号,防止多个用户一起上传文件时切片混乱
var uuidfolder = uuidv1();
// var uuidfolder = 'wwwwwwwwww2222222222';
// 开始切割的位置
var start = 0;
// 切割的结束位置
var end;
// 切片的索引
var index = 0;
// 回调计数器
var count = 0;
// 文件的大小
var filesize = blob.size;
// 文件的名称
var filename = blob.name;
//计算文件切片总数
totalPieces = Math.ceil(filesize / bytesPerPiece);
// 启动while循环对文件切片
while(start < filesize) {
// 设置切片的结束位置
end = start + bytesPerPiece;
// 对最后一片数据进行处理(可以省略)
if(end > filesize) {
end = filesize;
}
// 切割文件
var chunk = blob.slice(start,end);//切割文件
// 给每一片切片设置名字,名字的值为原始名称加索引,这样做是为了让后端可以按照索引顺序合并图片。
var sliceIndex= blob.name + index;
// 利用formData来传递数据
var formData = new FormData();
formData.append("file", chunk, sliceIndex);
formData.append("uuidfolder", uuidfolder);
formData.append("imgorder", index);
$.ajax({
url: '/api/upload',
type: 'POST',
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
}).done(function(res){
count++;
if(count==totalPieces){
console.log("上传结束,请求拼接接口,将切片信息拼接完整,返回图片url");
$.post('/merge',{id:uuidfolder},function(data){
console.log(data);
})
}
}).fail(function(res) {
console.log("上传失败")
});
start = end;
index++;
}
}
</script>
</body>
</html>
【文件合并】
// 接收切片信息接口
router.post('/upload3', upload.single('file'), function (req, res, next) {
console.log(req.body)
// 接受图片唯一标识符号
let imgname = req.body.uuidfolder;
// 接受切片索引
let imgorder = req.body.imgorder;
// 建立图片存储目录
let imgpath = path.join(__dirname,'..','public/mult',imgname);
// 判断目录是否存在,存在的话直接使用并存储切片,不存在的话就新建。
if (fs.existsSync(imgpath)) {
fs.readFile(req.file.path, function (err, data) {
fs.writeFile(path.join(imgpath, imgorder), data, (err) => {
if (!err) {
res.send("写入后面的文件")
}
})
})
} else {
fs.mkdirSync(imgpath);
fs.readFile(req.file.path, function (err, data) {
fs.writeFile(path.join(imgpath, imgorder), data, (err) => {
if (!err) {
res.send("第一次写入并新建文件夹")
}
})
})
}
})
// 合并图片接口:
router.post('/merge',function(req,res){
let id = req.body.id;
let folderpath = path.join(__dirname,"..",'public/mult',id);
let destinpath = path.join(__dirname,"..",'public/img',id+'.jpg');
let dist = '/img/'+id+'.jpg'
fs.readdir(folderpath,function(err,arr){
let arr2 = arr.map(e=>path.join(folderpath,e));
concat(arr2, destinpath, function(err) {
if (err) throw err
res.send(dist);
});
})
})
// 注意:
1、后端根据唯一标识符和切片下标来拼接文件。
2、后端可以将接收和合并拆成两个接口来供前端调用,这样做的优点是节省服务端性能。
缺点:
切片上传时并不能保证每个请求条件都成功,有可能因为各种原因导致部分切片丢失
4、大文件断点续传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>断点续传</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="./uuid.js"></script>
</head>
<body>
<h2>断点续传</h2>
<input type="file" name="file" id="file">
<button id="upload">上传</button>
<script type="text/javascript">
//var bytesPerPiece = 1024 * 1024; // 每个文件切片大小定为1MB .
var bytesPerPiece = 300 * 300; // 测试数据
var totalPieces; //切片总数
//发送请求
$("#upload").click(upload)
function upload() {
var blob = document.getElementById("file").files[0];
// 文件唯一标识符号,防止多个用户一起上传文件时切片混乱
//var uuidfolder = uuidv1();
var uuidfolder = 'wwwwwwwwww2222222222';
// 开始切割的位置
var start = 0;
// 切割的结束位置
var end;
// 切片的索引
var index = 0;
// 回调计数器
var count = 0;
// 文件的大小
var filesize = blob.size;
// 文件的名称
var filename = blob.name;
//计算文件切片总数
totalPieces = Math.ceil(filesize / bytesPerPiece);
// 判断是否为断点续传
var continueNum = window.localStorage.getItem(uuidfolder)
if(continueNum){
index = continueNum
start = index * bytesPerPiece
}
// 启动while循环对文件切片
while(start < filesize) {
// 设置切片的结束位置
end = start + bytesPerPiece;
// 对最后一片数据进行处理(可以省略)
if(end > filesize) {
end = filesize;
}
// 切割文件
var chunk = blob.slice(start,end);//切割文件
// 给每一片切片设置名字,名字的值为原始名称加索引,这样做是为了让后端可以按照索引顺序合并图片。
var sliceIndex= blob.name + index;
// 利用formData来传递数据
var formData = new FormData();
formData.append("file", chunk, sliceIndex);
formData.append("uuidfolder", uuidfolder);
formData.append("imgorder", index);
// 人为制造断点
if(index === 2){
console.log("断点续传")
window.localStorage.setItem(uuidfolder, index);
return;
}
$.ajax({
url: '/api/upload',
type: 'POST',
data: formData,
processData: false, // 不处理数据
contentType: false, // 不设置内容类型
}).done(function(res){
count++;
if(count==totalPieces){
console.log("上传结束,请求拼接接口,将切片信息拼接完整,返回图片url");
$.post('/merge',{id:uuidfolder},function(data){
console.log(data);
})
}
}).fail(function(res) {
console.log("上传失败")
});
start = end;
index++;
}
}
</script>
</body>
</html>