普通单文件上传:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>断点续传</title>
</head>
<body>
<div id="app">
<input type="file" id="file-input"/><button id="upload-button">上传</button>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
document.getElementById('file-input').addEventListener('change', fileSelect);
document.getElementById("upload-button").addEventListener('click', uploadFile);
var uploadConfig = {
chunkMaxSize: 1024*1024*5 //分片大小,这里是5M
}
// 选择文件时
function fileSelect(){
var file = document.getElementById("this-file").files[0]; //需要上传的文件
// 整个文件信息
selectedFile = {
md5: fileMd5, //spark-md5计算出的hash值
name: file.name //所上传文件名称
}
// 上传参数
chunkParam = {
chunk: 0, //开始上传的分片索引
isLastChunk: false, //是否是最后一个分片
chunkLength: Math.ceil(file.size/uploadConfig.chunkMaxSize) //分片个数
}
}
// 上传每一个文件前
function uploadFile(file){
$.ajax({
type: "POST",
url: '/checkFile',
data: selectedFile,
success: function(data){
if(data.state == 0){ //判断之前是否上传成功此文件,0未上传
chunksFile(); //上传文件
}else if(data.state == 1){ // 上传了部分,将查询到的已上传结果赋值给表单中的分片
chunkParam.chunk = parseInt(data.maxChuck) + 1;
chunksFile();
}else if(data.state == 2){
alert("该文件已经上传过");
}
},
error: function(data){
console.log(data);
}
});
}
// 上传每一个分片时
function chunksFile(){
//判断是不是最后一片,通知后台
(chunkParam.chunk+1) == chunkLength ? chunkParam.isLastChunk = true : chunkParam.isLastChunk = false;
//用formData通过ajax上传文件内容
var fm = new FormData();
fm.append('fileMd5', selectedFile.md5);//spark-md5计算出来的md5值
fm.append('chunk', chunkParam.chunk);
fm.append('isLastChunk ', chunkParam.isLastChunk );
fm.append('fileName ', selectedFile.fileName);
fm.append('theChunk', file.slice(chunkParam.chunk*uploadConfig.chunkMaxSize, (chunkParam.chunk+1)*uploadConfig.chunkMaxSize));
$.ajax({
type: "POST",
url: '/uploadChuncks',
data: fm,
success: function(data){
if(data.ret){
// 上传完最后一片后传输结束
if(chunkParam.isLastChunk){
console.log("上传完成");
}else{
// 上传成功,分片加一,上传下一片文件
chunkParam.chunk = parseInt(data.chunk) + 1;
//递归调用上传下一片
chunksFile();
}
}else{
console.log(data.msg);
}
},
error: function(data){
console.log(data.msg);
}
});
}
</script>
</body>
</html>
单文件上传,有进度条,可以对文件加密:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>断点续传</title>
</head>
<body>
<div id="app">
<input type="file" id="file-input"/><button id="upload-button">上传</button>
<p id="progress"></p>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
document.getElementById('file-input').addEventListener('change', fileSelect);
document.getElementById("upload-button").addEventListener('click', uploadFile);
var uploadConfig = {
chunkMaxSize: 1024*1024*5 //分片大小,这里是5M
}
// 选择文件时
function fileSelect(){
var file = document.getElementById("this-file").files[0]; //需要上传的文件
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
// 将文件存储到内存
var fileBlobFormatter = e.target.result;
// 整个文件信息
selectedFile = {
md5: fileMd5, //spark-md5计算出的hash值
name: file.name, //所上传文件名称,
fileBlobFormatter
}
// 上传参数
chunkParam = {
chunk: 0, //开始上传的分片索引
isLastChunk: false, //是否是最后一个分片
chunkLength: Math.ceil(file.size/uploadConfig.chunkMaxSize) //分片个数
}
});
}
// 上传每一个文件前
function uploadFile(file){
$.ajax({
type: "POST",
url: '/checkFile',
data: selectedFile,
success: function(data){
if(data.state == 0){ //判断之前是否上传成功此文件,0未上传
chunksFile(); //上传文件
}else if(data.state == 1){ // 上传了部分,将查询到的已上传结果赋值给表单中的分片
chunkParam.chunk = parseInt(data.maxChuck) + 1;
chunksFile();
}else if(data.state == 2){
alert("该文件已经上传过");
}
},
error: function(data){
console.log(data);
}
});
}
// 上传每一个分片时
function chunksFile(){
//判断是不是最后一片,通知后台
(chunkParam.chunk+1) == chunkLength ? chunkParam.isLastChunk = true : chunkParam.isLastChunk = false;
//用formData通过ajax上传文件内容
var fm = new FormData();
fm.append('fileMd5', selectedFile.md5);//spark-md5计算出来的md5值
fm.append('chunk', chunkParam.chunk);
fm.append('isLastChunk ', chunkParam.isLastChunk );
fm.append('fileName ', selectedFile.fileName);
fm.append('theChunk', selectedFile.fileBlobFormatter.slice(chunkParam.chunk*uploadConfig.chunkMaxSize, (chunkParam.chunk+1)*uploadConfig.chunkMaxSize));
$.ajax({
type: "POST",
url: '/uploadChuncks',
data: fm,
success: function(data){
if(data.ret){
// 上传完最后一片后传输结束
if(chunkParam.isLastChunk){
console.log("上传完成");
}else{
//计算上传进度
$("#progress").text((chunkParam.chunk+1)/chunkParam.chunkLength+"%");
//上传成功,分片加一,上传下一片文件
chunkParam.chunk = parseInt(data.chunk) + 1;
//递归调用上传下一片
chunksFile();
}
}else{
console.log(data.msg);
}
},
error: function(data){
console.log(data.msg);
}
});
}
</script>
</body>
</html>
多文件断点续传:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
body {
font-family: Arial;
}
form {
margin: 50px auto;
width: 600px;
}
input[type="button"] {
cursor: pointer;
}
table {
display: none;
margin-top: 15px;
border: 1px solid #ddd;
border-collapse: collapse;
}
table th {
color: #666;
}
table td,table th {
padding: 5px;
border: 1px solid #ddd;
text-align: center;
font-size: 14px;
}
#upload-list th, #upload-list td{
text-align: left;
}
</style>
<title>断点续传</title>
</head>
<body>
<form method="post" id="myForm" action="/fileTest.php" enctype="multipart/form-data">
<input type="file" id="myFile" multiple="" class="btn btn-primary btn-xs"/>
<!-- 上传的文件列表 -->
<table id="upload-list" class="table table-bordered">
<thead>
<tr>
<th width="35%">文件名</th>
<th width="15%">文件类型</th>
<th width="15%">文件大小</th>
<th width="20%">上传进度</th>
<th width="15%"> <input type="button" class="btn btn-primary btn-xs" id="upload-all-btn" value="全部上传" /> </th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</form>
<!-- 上传文件列表中每个文件的信息模版 -->
<script type="text/template" id="file-upload-tpl">
<tr>
<td>{{fileName}}</td>
<td>{{fileType}}</td>
<td>{{fileSize}}</td>
<td class="upload-progress">{{progress}}</td>
<td>
<input type="button" class="upload-item-btn btn btn-primary btn-xs" data-name="{{fileName}}" data-size="{{totalSize}}" data-state="default" value="{{uploadVal}}">
</td>
</tr>
</script>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.js"></script>
<script>
// 全部上传操作
$(document).on('click', '#upload-all-btn',function() {
// 未选择文件
if (!$('#myFile').val()) {
$('#myFile').focus();
}
// 模拟点击其他可上传的文件
else {
$('#upload-list .upload-item-btn').each(function() {
$(this).click();
});
}
}
);
// 选择文件,显示文件信息
$('#myFile').change(function(e) {
var file;
var uploadItem = [];
var uploadItemTpl = $('#file-upload-tpl').html();
var size;
var percent;
var progress = '未上传';
var uploadVal = '开始上传';
for (var i = 0, j = this.files.length; i < j; ++i) {
file = this.files[i];
percent = undefined;
progress = '未上传';
uploadVal = '开始上传';
// 计算文件大小
size = file.size > 1024 ? file.size / 1024 > 1024 ? file.size / (1024 * 1024) > 1024 ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB': (file.size / (1024 * 1024)).toFixed(2) + 'MB': (file.size / 1024).toFixed(2) + 'KB': (file.size).toFixed(2) + 'B';
// 初始通过本地记录,判断该文件是否曾经上传过
percent = window.localStorage.getItem(file.name + '_p');
if (percent && percent !== '100.0') {
progress = '已上传 ' + percent + '%';
uploadVal = '继续上传';
}
// 更新文件信息列表
uploadItem.push(uploadItemTpl.replace(/{{fileName}}/g, file.name).replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件').replace('{{fileSize}}', size).replace('{{progress}}', progress).replace('{{totalSize}}', file.size).replace('{{uploadVal}}', uploadVal));
}
$('#upload-list').children('tbody').html(uploadItem.join('')).end().show();
});
/**
* 上传文件时,提取相应匹配的文件项
* @param {String} fileName 需要匹配的文件名
* @return {FileList} 匹配的文件项目
*/
function findTheFile(fileName) {
var files = $('#myFile')[0].files;
var theFile;
for (var i = 0, j = files.length; i < j; ++i) {
if (files[i].name === fileName) {
theFile = files[i];
break;
}
}
return theFile ? theFile: [];
}
// 上传文件
$(document).on('click', '.upload-item-btn', function() {
var $this = $(this);
var state = $this.attr('data-state');
var msg = {
done: '上传成功',
failed: '上传失败',
in:'上传中...',
paused: '暂停中...'
};
var fileName = $this.attr('data-name');
var $progress = $this.closest('tr').find('.upload-progress');
var eachSize = 1024; //分片大小
var totalSize = $this.attr('data-size');
var chunks = Math.ceil(totalSize / eachSize); //分片数量
var percent;
var chunk; //分片序号
// 暂停上传操作
isPaused = 0;
// 进行暂停上传操作
// 未实现,这里通过动态的设置isPaused值并不能阻止下方ajax请求的调用
if (state === 'uploading') {
$this.val('继续上传').attr('data-state', 'paused');
$progress.text(msg['paused'] + percent + '%');
isPaused = 1;
console.log('暂停:', isPaused);
}
// 进行开始/继续上传操作
else if (state === 'paused' || state === 'default') {
$this.val('暂停上传').attr('data-state', 'uploading');
isPaused = 0;
}
// 第一次点击上传
startUpload('first');
// 上传操作 times: 第几次
function startUpload(times) {
// 上传之前查询是否以及上传过分片
chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
chunk = parseInt(chunk, 10);
// 判断是否为末分片
var isLastChunk = (chunk == (chunks - 1) ? 1 : 0);
// 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传
if (times === 'first' && isLastChunk === 1) {
window.localStorage.setItem(fileName + '_chunk', 0);
chunk = 0;
isLastChunk = 0;
}
// 设置分片的开始结尾
var blobFrom = chunk * eachSize; // 分段开始
var blobTo = (chunk + 1) * eachSize > totalSize ? totalSize:(chunk + 1) * eachSize; // 分段结尾
var percent = (100 * blobTo / totalSize).toFixed(1); // 已上传的百分比
var timeout = 5000; // 超时时间
fd = new FormData($('#myForm')[0]);
fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件
fd.append('fileName', fileName); // 文件名
fd.append('totalSize', totalSize); // 文件总大小
fd.append('isLastChunk', isLastChunk); // 是否为末段
fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上传)
// 上传
$.ajax({
type: 'post',
url: '/fileTest.php',
data: fd,
processData: false,
contentType: false,
timeout: timeout,
success: function(rs) {
rs = JSON.parse(rs);
// 上传成功
if (rs.status === 200) {
// 记录已经上传的百分比
window.localStorage.setItem(fileName + '_p', percent);
// 已经上传完毕
if (chunk === (chunks - 1)) {
$progress.text(msg['done']);
$this.val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {
$('#upload-all-btn').val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
}
} else {
// 记录已经上传的分片
window.localStorage.setItem(fileName + '_chunk', ++chunk);
$progress.text(msg['in'] + percent + '%');
// 这样设置可以暂停,但点击后动态的设置就暂停不了..
// if (chunk == 10) {
// isPaused = 1;
// }
console.log(isPaused);
if (!isPaused) {
startUpload();
}
}
}
// 上传失败,上传失败分很多种情况,具体按实际来设置
else if (rs.status === 500) {
$progress.text(msg['failed']);
}
},
error: function() {
$progress.text(msg['failed']);
}
});
}
});
</script>
</body>
</html>
多文件断点续传+加密:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.css" rel="stylesheet">
<style type="text/css">
body {
font-family: Arial;
}
form {
margin: 50px auto;
width: 600px;
}
input[type="button"] {
cursor: pointer;
}
table {
display: none;
margin-top: 15px;
border: 1px solid #ddd;
border-collapse: collapse;
}
table th {
color: #666;
}
table td,table th {
padding: 5px;
border: 1px solid #ddd;
text-align: center;
font-size: 14px;
}
#web-disk-file-list th, #web-disk-file-list td{
text-align: left;
}
</style>
<title>断点续传</title>
</head>
<body>
<form method="post" id="web-disk-file-form" action="/fileTest.php" enctype="multipart/form-data">
<input type="file" id="web-disk-file-input" multiple="" class="btn btn-primary btn-xs"/>
<!-- 上传的文件列表 -->
<table id="web-disk-file-list" class="table table-bordered">
<thead>
<tr>
<th width="35%">文件名</th>
<th width="15%">文件类型</th>
<th width="15%">文件大小</th>
<th width="20%">上传进度</th>
<th width="15%"> <input type="button" class="btn btn-primary btn-xs" id="web-disk-upload-all-btn" value="全部上传" /> </th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</form>
<!-- 上传文件列表中每个文件的信息模版 -->
<script type="text/template" id="web-disk-file-upload-tpl">
<tr>
<td>{{fileName}}</td>
<td>{{fileType}}</td>
<td>{{fileSize}}</td>
<td class="upload-progress">{{progress}}</td>
<td>
<input type="button" class="web-disk-upload-item-btn btn btn-primary btn-xs" data-name="{{fileName}}" data-size="{{totalSize}}" data-state="default" value="{{uploadVal}}">
</td>
</tr>
</script>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.js"></script>
<script>
// 全部上传操作
$(document).on('click', '#web-disk-upload-all-btn',function() {
// 未选择文件
if (!$('#web-disk-file-input').val()) {
$('#web-disk-file-input').focus();
}
// 模拟点击其他可上传的文件
else {
$('#web-disk-file-list .web-disk-upload-item-btn').each(function() {
$(this).click();
});
}
}
);
// 选择文件,显示文件信息
$('#web-disk-file-input').change(function(e) {
var file;
var uploadItem = [];
var uploadItemTpl = $('#web-disk-file-upload-tpl').html();
var size;
var percent;
var progress = '未上传';
var uploadVal = '开始上传';
for (var i = 0, j = this.files.length; i < j; ++i) {
file = this.files[i];
percent = undefined;
progress = '未上传';
uploadVal = '开始上传';
// 计算文件大小
size = file.size > 1024 ? file.size / 1024 > 1024 ? file.size / (1024 * 1024) > 1024 ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB': (file.size / (1024 * 1024)).toFixed(2) + 'MB': (file.size / 1024).toFixed(2) + 'KB': (file.size).toFixed(2) + 'B';
// 初始通过本地记录,判断该文件是否曾经上传过
percent = window.localStorage.getItem(file.name + '_p');
if (percent && percent !== '100.0') {
progress = '已上传 ' + percent + '%';
uploadVal = '继续上传';
}
// 更新文件信息列表
uploadItem.push(uploadItemTpl.replace(/{{fileName}}/g, file.name).replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件').replace('{{fileSize}}', size).replace('{{progress}}', progress).replace('{{totalSize}}', file.size).replace('{{uploadVal}}', uploadVal));
}
$('#web-disk-file-list').children('tbody').html(uploadItem.join('')).end().show();
});
/**
* 上传文件时,提取相应匹配的文件项
* @param {String} fileName 需要匹配的文件名
* @return {FileList} 匹配的文件项目
*/
function findTheFile(fileName) {
var files = $('#web-disk-file-input')[0].files;
var theFile;
for (var i = 0, j = files.length; i < j; ++i) {
if (files[i].name === fileName) {
theFile = files[i];
break;
}
}
return theFile ? theFile: [];
}
// 上传文件
$(document).on('click', '.web-disk-upload-item-btn', function() {
var $this = $(this);
var state = $this.attr('data-state');
var msg = {
done: '上传成功',
failed: '上传失败',
in:'上传中...',
paused: '暂停中...'
};
var fileName = $this.attr('data-name');
var $progress = $this.closest('tr').find('.upload-progress');
var eachSize = 1024; //分片大小
var totalSize = $this.attr('data-size');
var chunks = Math.ceil(totalSize / eachSize); //分片数量
var percent;
var chunk; //分片序号
// 暂停上传操作
isPaused = 0;
// 进行暂停上传操作
// 未实现,这里通过动态的设置isPaused值并不能阻止下方ajax请求的调用
if (state === 'uploading') {
$this.val('继续上传').attr('data-state', 'paused');
$progress.text(msg['paused'] + percent + '%');
isPaused = 1;
console.log('暂停:', isPaused);
}
// 进行开始/继续上传操作
else if (state === 'paused' || state === 'default') {
$this.val('暂停上传').attr('data-state', 'uploading');
isPaused = 0;
}
// 第一次点击上传
startUpload('first');
// 上传操作 times: 第几次
function startUpload(times) {
// 上传之前查询是否以及上传过分片
chunk = window.localStorage.getItem(fileName + '_chunk') || 0; //上传到第几片?
chunk = parseInt(chunk, 10);
// 判断是否为末分片
var isLastChunk = (chunk == (chunks - 1) ? 1 : 0);
// 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传
if (times === 'first' && isLastChunk === 1) {
window.localStorage.setItem(fileName + '_chunk', 0);
chunk = 0;
isLastChunk = 0;
}
// 设置分片的开始结尾
var blobFrom = chunk * eachSize; // 分段开始
var blobTo = (chunk + 1) * eachSize > totalSize ? totalSize:(chunk + 1) * eachSize; // 分段结尾
var percent = (100 * blobTo / totalSize).toFixed(1); // 已上传的百分比
var timeout = 5000; // 超时时间
var reader = new FileReader();
var readerSliceBlob = findTheFile(fileName).slice(blobFrom, blobTo); //分好段的文件
reader.readAsArrayBuffer(readerSliceBlob);
reader.addEventListener("load", function(e) {
// 将文件存储到内存
var fileBlobFormatter = e.target.result; // 可在此处对文件加密
// 上传操作 begin===============================================
fd = new FormData($('#web-disk-file-form')[0]);
fd.append('theFile', fileBlobFormatter); // 加密分好段的文件
fd.append('fileName', fileName); // 文件名
fd.append('totalSize', totalSize); // 文件总大小
fd.append('isLastChunk', isLastChunk); // 是否为末段
fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上传)
// 上传
$.ajax({
type: 'post',
url: '/fileTest.php',
data: fd,
processData: false,
contentType: false,
timeout: timeout,
success: function(rs) {
rs = JSON.parse(rs);
// 上传成功
if (rs.status === 200) {
// 记录已经上传的百分比
window.localStorage.setItem(fileName + '_p', percent);
// 已经上传完毕
if (chunk === (chunks - 1)) {
$progress.text(msg['done']);
$this.val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
if (!$('#web-disk-file-list').find('.web-disk-upload-item-btn:not(:disabled)').length) {
$('#web-disk-upload-all-btn').val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
}
} else {
// 记录已经上传的分片
window.localStorage.setItem(fileName + '_chunk', ++chunk);
$progress.text(msg['in'] + percent + '%');
// 这样设置可以暂停,但点击后动态的设置就暂停不了..
// if (chunk == 10) {
// isPaused = 1;
// }
console.log(isPaused);
if (!isPaused) {
startUpload();
}
}
}
// 上传失败,上传失败分很多种情况,具体按实际来设置
else if (rs.status === 500) {
$progress.text(msg['failed']);
}
},
error: function() {
$progress.text(msg['failed']);
}
});
// 上传操作 end===============================================
});
}
});
</script>
</body>
</html>