这篇文章主要讲断点续传的上传原理。
断点续传指的是在文件传输时将文件进行切分,每个部分的传输都是独立的,并且在遇到网络故障时,可以在网络通畅之后继续传输的未曾传输的部分,并不需要重新传输。
根据以上描述,Web 端的文件上传步骤如下。为了思路的清晰,以下代码十分简陋,没有进行相应的错误处理,而且是非并发安全的,千万不要在实际项目中这么写!
读取文件
var fileEle = document.querySelector('#file');
var readerFile;
var file;
fileEle.onchange = function() {
var rd = new FileReader();
file = this.files[0]; // 这里默认只有一个文件,多文件的话可以自行扩展
rd.onload = function(e) {
readerFile = e.target.result;
}
rd.readAsDataURL(file);
}
切分文件
var start = 0; // 切片头部偏移量
var count = 0; // 切片序号
var step = 10240; // 切片大小
var piece;
function getPiece() {
piece = readerFile.slice(start, start + step);
return piece;
}
上传文件
function upload() {
var fm = new FormData();
fm.append('name', file.name);
fm.append('cap', file.size);
fm.append('len', step);
fm.append('seq_' + String(count), getPiece());
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
if (e.target.status == 200 && e.target.readyState == 4) {
count ++;
start += step;
if (start < readerFile.length) {
// 这里自己决定怎么续传
setTimeout(function() {
upload();
}, 1000);
} else {
alert('上传成功');
}
}
}
xhr.open('post', 'http://localhost:8888/upload');
xhr.send(fm);
}
上传完成,合并文件
将前端传来的seq_num
(num 为序列号)保存到数据库中,可以根据文件的大小来判断是否上传完毕,待确认文件上传完毕,即可进行合并。以下代码将这些复杂的步骤进行了简化,将每一段切片都直接追回到同一个临时文件中,并以一个特别的请求表示文件已经上传完毕。
<?php
foreach ($_POST as $k => $v) {
if (preg_match('/\d+$/', $k) != 0) {
if (preg_match('/[^\d]0$/', $k)) {
$fd = fopen('test.txt', "wb+");
$v = preg_replace('/data:.*?,/', '', $v);
} else {
$fd = fopen('test.txt', "ab+");
}
fwrite($fd, $v);
fclose($fd);
}
if ($k == 'end') {
$content = file_get_contents('test.txt');
$content = base64_decode($content);
file_put_contents('test.jpg', $content);
header('Content-type: image/jpeg');
echo $content;
}
}
完整的前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="">
<input type="file" id="file">
<button type="button" id="sm">提交</button>
</form>
<script>
var fileEle = document.querySelector('#file');
var readerFile;
var file;
var start = 0; // 切片偏移量
var count = 0; // 切片序号
var step = 10240; // 切片大小
var piece; // 切片内容
var url = 'http://localhost:8888/upload';
fileEle.onchange = function () {
var rd = new FileReader();
file = this.files[0]; // 这里默认只有一个文件,多文件的话可以自行扩展
rd.onload = function (e) {
readerFile = e.target.result;
}
rd.readAsDataURL(file);
}
function getPiece() {
piece = readerFile.slice(start, start + step);
return piece;
}
function upload() {
var fm = new FormData();
fm.append('name', file.name);
fm.append('cap', file.size);
fm.append('len', step);
fm.append('seq_' + String(count), getPiece());
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
if (e.target.status == 200 && e.target.readyState == 4) {
count++;
start += step;
if (start < readerFile.length) {
// 这里自己决定怎么续传
setTimeout(function () {
upload();
}, 1000);
} else {
end();
alert('上传成功');
}
}
}
xhr.open('post', url);
xhr.send(fm);
}
function end() {
var fm = new FormData();
fm.append('end', 'end');
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
if (e.target.status == 200 && e.target.readyState == 4) {
}
};
xhr.open('post', url);
xhr.send(fm);
}
var sm = document.querySelector('#sm');
sm.onclick = function () {
count = 0;
start = 0;
upload();
}
</script>
</body>
</html>