前端实现文件的断点续传(前端文件提交+后端PHP文件接收)


Posted in Javascript onNovember 04, 2016

早就听说过断点续传这种东西,前端也可以实现一下。断点续传在前端的实现主要依赖着HTML5的新特性,所以一般来说在老旧浏览器上支持度是不高的。

本文通过断点续传的简单例子(前端文件提交+后端PHP文件接收),理解其大致的实现过程

还是先以图片为例,看看最后的样子

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

一、一些知识准备

断点续传,既然有断,那就应该有文件分割的过程,一段一段的传。

以前文件无法分割,但随着HTML5新特性的引入,类似普通字符串、数组的分割,我们可以可以使用slice方法来分割文件。

所以断点续传的最基本实现也就是:前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。

而我们需要对FileList对象进行修改再提交,在 之前的文章 中知晓了这种提交的一些注意点,因为FileList对象不能直接更改,所以不能直接通过表单的.submit()方法上传提交,需要结合FormData对象生成一个新的数据,通过Ajax进行上传操作。

二、实现过程

这个例子实现了文件断点续传的基本功能,不过手动的“暂停上传”操作还未实现成功,可以在上传过程中刷新页面来模拟上传的中断,体验“断点续传”、

有可能还有其他一些小bug,但基本逻辑大致如此。

1. 前端实现

首先选择文件,列出选中的文件列表信息,然后可以自定义的做上传操作

(1)所以先设置好页面DOM结构

<!-- 上传的表单 -->
<form method="post" id="myForm" action="/fileTest.php" enctype="multipart/form-data">
<input type="file" id="myFile" multiple>
<!-- 上传的文件列表 -->
<table id="upload-list">
<thead>
<tr>
<th width="35%">文件名</th>
<th width="15%">文件类型</th>
<th width="15%">文件大小</th>
<th width="20%">上传进度</th>
<th width="15%">
<input type="button" 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" data-name="{{fileName}}" data-size="{{totalSize}}" data-state="default" value="{{uploadVal}}"> </td> </tr> </script>

这里一并将CSS样式扔出来

<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; }
</style>

(2)接下来是JS的实现解析

通过FileList对象我们能获取到文件的一些信息

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

其中的size就是文件的大小,文件的分分割分片需要依赖这个

这里的size是字节数,所以在界面显示文件大小时,可以这样转化

// 计算文件大小 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';

选择文件后显示文件的信息,在模版中替换一下数据

// 更新文件信息列表 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)
);

不过,在显示文件信息的时候,可能这个文件之前之前已经上传过了,为了断点续传,需要判断并在界面上做出提示。

通过查询本地看是否有相应的数据(这里的做法是当本地记录的是已经上传100%时,就直接是重新上传而不是继续上传了)

// 初始通过本地记录,判断该文件是否曾经上传过 percent = window.localStorage.getItem(file.name + '_p'); if (percent && percent !== '100.0') {
progress = '已上传 ' + percent + '%';
uploadVal = '继续上传';
}

显示了文件信息列表

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

点击开始上传,可以上传相应的文件

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

上传文件的时候需要就将文件进行分片分段。

比如这里配置的每段1024B,总共chunks段(用来判断是否为末段),第chunk段,当前已上传的百分比percent等。
需要提一下的是这个暂停上传的操作,其实我还没实现出来,暂停不了无奈ing...

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

接下来是分段过程

// 设置分片的开始结尾 var blobFrom = chunk * eachSize, // 分段开始 blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段结尾 percent = (100 * blobTo / totalSize).toFixed(1), // 已上传的百分比 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); // 是否是第一段(第一次上传)
// 上传之前查询是否以及上传过分片 chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
chunk = parseInt(chunk, 10);

文件应该支持覆盖上传,所以如果文件以及上传完了,现在再上传,应该重置数据以支持覆盖(不然后端就直接追加 blob数据了)

// 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传 if (times === 'first' && isLastChunk === 1) { window.localStorage.setItem(fileName + '_chunk', 0);
chunk = 0;
isLastChunk = 0;
}

这个 times 其实就是个参数,因为要在上一分段传完之后再传下一分段,所以这里的做法是在回调中继续调用这个上传操作

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

接下来就是真正的文件上传操作了,用Ajax上传,因为用到了FormData对象,所以不要忘了在$.ajax({}加上这个配置processData: false

上传了一个分段,通过返回的结果判断是否上传完毕,是否继续上传

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']);
}

继续下一分段的上传时,就进行了递归操作,按顺序地上传下一分段

截个图..

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

这是完整的JS逻辑,代码有点儿注释了应该不难看懂吧哈哈

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

前端实现文件的断点续传(前端文件提交+后端PHP文件接收)

2. 后端实现

这里的后端实现还是比较简单的,主要用依赖了 file_put_contents、file_get_contents 这两个方法

前端实现文件的断点续传(前端文件提交+后端PHP文件接收) 

要注意一下,通过FormData对象上传的文件对象,在PHP中也是通过$_FILES全局对象获取的,还有为了避免上传后文件中文的乱码,用一下iconv
断点续传支持文件的覆盖,所以如果已经存在完整的文件,就将其删除

// 如果第一次上传的时候,该文件已经存在,则删除文件重新上传 if ($isFirstUpload == '1' && file_exists('upload/'. $fileName) && filesize('upload/'. $fileName) == $totalSize) {
unlink('upload/'. $fileName);
}

使用上述的两个方法,进行文件信息的追加,别忘了加上 FILE_APPEND 这个参数~

// 继续追加文件数据 if (!file_put_contents('upload/'. $fileName, file_get_contents($_FILES['theFile']['tmp_name']), FILE_APPEND)) {
$status = 501;
} else { // 在上传的最后片段时,检测文件是否完整(大小是否一致) if ($isLastChunk === '1') { if (filesize('upload/'. $fileName) == $totalSize) {
$status = 200;
} else {
$status = 502;
}
} else {
$status = 200;
}
}

一般在传完后都需要进行文件的校验吧,所以这里简单校验了文件大小是否一致。

根据实际需求的不同有不同的错误处理方法,这里就先不多处理了

完整的PHP部分

<?php
header('Content-type: text/plain; charset=utf-8'); 
$files = $_FILES['theFile'];
$fileName = iconv('utf-8', 'gbk', $_REQUEST['fileName']);
$totalSize = $_REQUEST['totalSize'];
$isLastChunk = $_REQUEST['isLastChunk'];
$isFirstUpload = $_REQUEST['isFirstUpload']; if ($_FILES['theFile']['error'] > 0) {
$status = 500;
} else { // 此处为一般的文件上传操作 // if (!move_uploaded_file($_FILES['theFile']['tmp_name'], 'upload/'. $_FILES['theFile']['name'])) { // $status = 501; // } else { // $status = 200; // } // 以下部分为文件断点续传操作 // 如果第一次上传的时候,该文件已经存在,则删除文件重新上传 if ($isFirstUpload == '1' && file_exists('upload/'. $fileName) && filesize('upload/'. $fileName) == $totalSize) {
unlink('upload/'. $fileName);
} // 否则继续追加文件数据 if (!file_put_contents('upload/'. $fileName, file_get_contents($_FILES['theFile']['tmp_name']), FILE_APPEND)) {
$status = 501;
} else { // 在上传的最后片段时,检测文件是否完整(大小是否一致) if ($isLastChunk === '1') { if (filesize('upload/'. $fileName) == $totalSize) { $status = 200;
} else {
$status = 502;
}
} else { 
$status = 200;
}
}
} echo json_encode(array( 'status' => $status, 'totalSize' => filesize('upload/'. $fileName), 'isLastChunk' => $isLastChunk
)); ?>
Javascript 相关文章推荐
Javascript 继承实现例子
Aug 12 Javascript
javascript学习笔记之10个原生技巧
May 21 Javascript
jQuery Migrate 1.1.0 Released 注意事项
Jun 14 Javascript
Extjs Label的 fieldLabel和html属性值对齐的方法
Jun 15 Javascript
网页下载文件期间如何防止用户对网页进行其他操作
Jun 27 Javascript
jQuery实现时尚漂亮的弹出式对话框实例
Aug 07 Javascript
jQuery实现下拉框选择图片功能实例
Aug 08 Javascript
复杂的javascript窗口分帧解析
Feb 19 Javascript
提升页面加载速度的插件InstantClick
Sep 12 Javascript
seajs中最常用的7个功能、配置示例
Oct 10 Javascript
vue点击按钮动态创建与删除组件功能
Dec 29 Javascript
解决vue-cli输入命令vue ui没效果的问题
Nov 17 Javascript
AngularJS中watch监听用法分析
Nov 04 #Javascript
AngularJS中的DOM操作用法分析
Nov 04 #Javascript
JS对大量数据进行多重过滤的方法
Nov 04 #Javascript
AngularJS模板加载用法详解
Nov 04 #Javascript
jQuery 遍历map()方法详解
Nov 04 #Javascript
Sortable.js拖拽排序使用方法解析
Nov 04 #Javascript
jQuery图片加载显示loading效果
Nov 04 #Javascript
You might like
《五等分的花嫁》漫画完结!2020年10月第2期TV动画制作组换血!
2020/03/06 日漫
php入门学习知识点一 PHP与MYSql连接与查询
2011/07/14 PHP
PHP面向对象之旅:深入理解static变量与方法
2014/01/06 PHP
php中file_get_contents与curl性能比较分析
2014/11/08 PHP
Symfony2 session用法实例分析
2016/02/04 PHP
PHP延迟静态绑定的深入讲解
2018/04/02 PHP
PhpSpreadsheet设置单元格常用操作汇总
2020/11/13 PHP
ExtJS 工具栏 分页事件参数
2010/03/05 Javascript
jQuery代码优化 选择符篇
2011/11/01 Javascript
jQuery图片滚动图片的效果(另类实现)
2013/06/02 Javascript
jQuery之DOM对象和jQuery对象的转换与区别分析
2015/01/08 Javascript
解决WordPress使用CDN后博文无法评论的错误
2015/12/15 Javascript
JS绘制微信小程序画布时钟
2016/12/24 Javascript
angular bootstrap timepicker TypeError提示怎么办
2017/06/13 Javascript
浅谈KOA2 Restful方式路由初探
2019/03/14 Javascript
浅谈vue加载优化策略
2019/03/19 Javascript
vue 解决addRoutes多次添加路由重复的操作
2020/08/04 Javascript
[52:07]完美世界DOTA2联赛PWL S3 LBZS vs access 第二场 12.10
2020/12/13 DOTA
在Python的Flask框架下收发电子邮件的教程
2015/04/21 Python
Python实现建立SSH连接的方法
2015/06/03 Python
Flask入门之上传文件到服务器的方法示例
2018/07/18 Python
Python实现的爬取小说爬虫功能示例
2019/03/30 Python
python+openCV调用摄像头拍摄和处理图片的实现
2019/08/06 Python
Django中modelform组件实例用法总结
2020/02/10 Python
jupyter notebook oepncv 显示一张图像的实现
2020/04/24 Python
Pycharm创建文件时自动生成文件头注释(自定义设置作者日期)
2020/11/24 Python
Joules官网:女士、男士和儿童服装和鞋类
2018/10/23 全球购物
2015年度党员自我评价范文
2015/03/03 职场文书
2015年医院后勤工作总结
2015/05/20 职场文书
院系推荐意见
2015/06/05 职场文书
银行大堂经理培训心得体会
2016/01/09 职场文书
2019年家电促销广告语集锦
2019/10/21 职场文书
MySQL基础(二)
2021/04/05 MySQL
详细谈谈MYSQL中的COLLATE是什么
2021/06/11 MySQL
MySQL Innodb索引机制详细介绍
2021/11/23 MySQL
分析SQL窗口函数之排名窗口函数
2022/04/21 Oracle