php+html5实现无刷新上传、大文件分片上传、断点续传的方法
- 格式:pdf
- 大小:114.02 KB
- 文档页数:10
HTML5之拖放功能(多文件上传和元素拖放)HTML5提供了拖放(Drag and Drop)功能,允许用户通过将对象拖动到指定的区域或元素上来实现一些交互效果,例如元素的重新排序、文件的上传等。
本文将主要介绍HTML5中的多文件上传和元素拖放两种常见的拖放功能。
一、多文件上传实现多文件上传的步骤如下:1. 创建一个接受文件的区域,可以是一个div元素,用来接受被拖动的文件;2. 设置该区域的ondragover事件,这个事件将在文件被拖动到区域时触发,我们需要阻止默认的行为,以便能够成功接受文件;3. 设置该区域的ondrop事件,这个事件将在文件被释放到区域时触发,我们需要获取文件,并进行相应的处理。
具体代码如下:```<div id="dropArea" ondragover="event.preventDefault(" ondrop="handleDrop(event)">将文件拖到此区域进行上传</div><script>function handleDrop(event)event.preventDefault(;var files = event.dataTransfer.files;for (var i = 0; i < files.length; i++)//进行上传操作//...}}</script>```通过以上代码,当用户将文件拖动到id为dropArea的div区域时,浏览器会触发该区域的ondragover事件和ondrop事件,我们通过event.dataTransfer.files属性获取到拖动的文件,然后可以对文件进行上传操作。
二、元素拖放除了文件上传,HTML5的拖放功能还可以用于元素的拖放,即允许用户将一个元素拖动到另一个位置。
这在一些需要交换元素顺序或进行拖动排序的场景中十分有用。
html5实现多⽂件的上传⽰例代码主要⽤到的是<input>的multiple属性复制代码代码如下:<input type="file" name="multipleFileUpload" multiple />下⾯是页⾯的详细代码:复制代码代码如下:<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><title>Solution 4-5: Sending multiple files</title></head><body><form id="myForm"action="http://10.10.25.31:8080/myupload/UploadPhotoServlet" ENCTYPE="multipart/form-data" METHOD="POST"> <input type="file" name="multipleFileUpload" multiple /> <inputtype="submit" value="提交"> <input type="reset" value="重设"></form></body></html>java后台的详细代码:复制代码代码如下:import java.io.File;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.List;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import mons.fileupload.FileItem;import mons.fileupload.FileUploadException;import mons.fileupload.disk.DiskFileItemFactory;import mons.fileupload.servlet.ServletFileUpload;/*** Servlet implementation class UploadPhotoServlet*/public class UploadPhotoServlet extends HttpServlet {private static final long serialVersionUID = 1L;/*** @see HttpServlet#HttpServlet()*/public UploadPhotoServlet() {super();// TODO Auto-generated constructor stub}/*** @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)*/protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stubdoPost(request,response);}/*** @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)*/@SuppressWarnings("unchecked")protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String imagePath="c:\\uploadFile\\Image\\"+getEachDate()+"\\";//按⽇期⽣成⽂件夹File uploadPath=new File(imagePath);if(!uploadPath.exists()){uploadPath.mkdirs();}File tmp=new File("c:\\tmp\\");if(!tmp.exists()){tmp.mkdirs();}DiskFileItemFactory factory=new DiskFileItemFactory ();//创建磁盘⼯⼚factory.setRepository(tmp);//设置⽂件缓存路径factory.setSizeThreshold(10 * 1096 );//将⽂件保存在内存还是磁盘临时⽂件夹的默认临界值,值为10240,即10kb ServletFileUpload sfu=new ServletFileUpload(factory);//创建处理⼯具sfu.setSizeMax(10*1024*1024);//服务器端可以接收的最⼤⽂件⼤⼩,-1表⽰⽆上限String fileName=null;try {List<FileItem> list=sfu.parseRequest(request);//解析if(list.size()<1){return;}for(int j=0;j<list.size();j++){FileItem item=list.get(j);fileName=item.getName();if(fileName.equals("")){request.getRequestDispatcher("/com/visualizerPhoto.jsp").forward(request, response);return;}int pos=stIndexOf(".");//取图⽚⽂件格式if(pos>0){Date date=new Date();fileName=imagePath+date.getTime()+fileName.substring(pos);}System.out.println("item:"+item);item.write(new File(fileName));//写到磁盘}} catch (FileUploadException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}// 13-11-15public static String getEachDate() {Calendar cal = Calendar.getInstance();cal.add(Calendar.DATE, 0);String yesterday = new SimpleDateFormat("yyyy-MM-dd ").format(cal.getTime());String[] dates = yesterday.split("-");String realDate = dates[0].substring(2, 4) + "-" + dates[1] + "-"+ dates[2];return realDate.trim();}}下⾯是效果图:。
php⼤⽂件断点续传该项⽬核⼼就是⽂件分块上传。
前后端要⾼度配合,需要双⽅约定好⼀些数据,才能完成⼤⽂件分块,我们在项⽬中要重点解决的以下问题。
* 如何分⽚;* 如何合成⼀个⽂件;* 中断了从哪个分⽚开始。
如何分,利⽤强⼤的js库,来减轻我们的⼯作,市场上已经能有关于⼤⽂件分块的轮⼦,虽然程序员的天性曾迫使我重新造轮⼦。
但是因为时间的关系还有⼯作的关系,我只能罢休了。
最后我选择了百度的WebUploader来实现前端所需。
如何合,在合之前,我们还得先解决⼀个问题,我们如何区分分块所属那个⽂件的。
刚开始的时候,我是采⽤了前端⽣成了唯⼀uuid来做⽂件的标志,在每个分⽚请求上带上。
不过后来在做秒传的时候我放弃了,采⽤了Md5来维护分块和⽂件关系。
在服务端合并⽂件,和记录分块的问题,在这⽅⾯其实⾏业已经给了很好的解决⽅案了。
参考迅雷,你会发现,每次下载中的时候,都会有两个⽂件,⼀个⽂件主体,另外⼀个就是⽂件临时⽂件,临时⽂件存储着每个分块对应字节位的状态。
这些都是需要前后端密切联系才能做好,前端需要根据固定⼤⼩对⽂件进⾏分⽚,并且请求中要带上分⽚序号和⼤⼩。
前端发送请求顺利到达后台后,服务器只需要按照请求数据中给的分⽚序号和每⽚分块⼤⼩(分⽚⼤⼩是固定且⼀样的)算出开始位置,与读取到的⽂件⽚段数据,写⼊⽂件即可。
为了便于开发,我将服务端的业务逻辑进⾏了如下划分,分成初始化,块处理,⽂件上传完毕等。
服务端的业务逻辑模块如下功能分析:⽂件夹⽣成模块⽂件夹上传完毕后由服务端进⾏扫描代码如下要开启)等信息,服务端在接收这些信息后便可以⾮常⽅便的进⾏处理了。
⽐如将块数据保存到分布式存储系统中分块上传可以说是我们整个项⽬的基础,像断点续传、暂停这些都是需要⽤到分块。
分块这块相对来说⽐较简单。
前端是采⽤了webuploader,分块等基础功能已经封装起来,使⽤⽅便。
借助webUpload提供给我们的⽂件API,前端就显得异常简单。
php实现断点续传⼤⽂件⽰例代码⼀、断点续传原理所谓断点续传,也就是要从⽂件已经下载的地⽅开始继续下载。
在以前版本的 HTTP 协议是不⽀持断点的,HTTP/1.1 开始就⽀持了。
⼀般断点下载时才⽤到 Range 和 Content-Range 实体头。
不使⽤断点续传get /down.zip http/1.1accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*accept-language: zh-cnaccept-encoding: gzip, deflateuser-agent: mozilla/4.0 (compatible; msie 5.01; windows nt 5.0)connection: keep-alive服务器收到请求后,按要求寻找请求的⽂件,提取⽂件的信息,然后返回给浏览器,返回信息如下:HTTP/1.1 200 Okcontent-length=106786028accept-ranges=bytesdate=mon, 30 apr 2001 12:56:11 gmtetag=w/"02ca57e173c11:95b"content-type=application/octet-streamserver=microsoft-iis/5.0last-modified=mon, 30 apr 2001 12:56:11 gmt使⽤断点续传GET /down.zip HTTP/1.0User-Agent: NetFoxRANGE: bytes=2000070-Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2多了这么⼀⾏Range: bytes=2000070-这⼀⾏的意思就是告诉服务器down.zip这个⽂件从2000070字节开始传,前⾯的字节不⽤传了。
这篇文章主要介绍了Html5大文件断点续传实现方法,需要的朋友可以参考下大文件分块 一般常用的web服务器都有对向服务器端提交数据有大小限制。
超过一定大小文件服务器端将返回拒绝信息。
当然,web服务器都提供了配置文件可能修改限制的大小。
针对iis 实现大文件的上传网上也有一些通过修改web服务器限制文件大小来实现。
不过这样对web 服务器的安全带了问题。
攻击者很容易发一个大数据包,将你的web服务器直接给拖死。
现在针对大文件上传主流的实现方式,通过将大文件分块。
比如针对一个100M文件,按2M拆分为50块。
然后再将每块文件依次上传到服务器上,上传完成后再在服务器上合并文件。
在web实现大文件上传,核心主要实现文件的分块。
在Html5 File API 出现以前,要想在web上实现文件分块传输。
只有通过flash或Activex实现文件的分块。
在Html5 下,我们可以直接通过file的slice 方法来实现文件的分块。
如: XML/HTML Codefile.slice(0,1000); file.slice(1000,2000); file.slice(2000,3000); 然后再通过XMLHttpRequest异步上传到服务器。
Html5 上传文件类库 如果你有兴趣及时间,当然可以自己用html5的File API来实现。
本人在网上查找到了以下两个支持html5类库。
resumable.js 附git上的地址:https:///23/resumable.js Pludload / resumable是一个纯html5上传类库。
HTML5是一种用于构建和呈现网页的标准技术。
在现代网页开发中,视瓶已经成为不可或缺的一部分。
而HTML5对视瓶的支持使得网页开发者能够更加灵活地控制和展示视瓶内容。
1. HTML5对视瓶的支持HTML5引入了lt;videogt;标签,使得开发者能够轻松地在网页中嵌入视瓶内容。
通过简单的标签设置,即可在网页中直接播放视瓶,极大地方便了网页开发者和用户。
在HTML5中,还引入了新的视瓶格式支持,如MP4、WebM、Ogg等,这样可以更好地兼容各种浏览器和设备。
2. MP4格式MP4是一种常见的视瓶格式,在HTML5中也得到了广泛的支持。
MP4格式的视瓶文件通常采用H.264编码,具有较高的压缩率和较好的视瓶质量,因此被广泛应用于网页视瓶播放中。
3. 断点续播断点续播是指当用户在观看视瓶时,突然关闭了视瓶播放器或者刷新了网页,再次进入视瓶页面时能够从上次观看的位置继续播放。
这一功能对于用户体验来说非常重要,能够避免用户需要重新找到上次观看的位置,提高了用户的观看体验。
4. 实现原理断点续播的实现原理主要涉及到两个方面,一是记忆上次观看的位置,二是恢复视瓶播放。
4.1 记忆上次观看的位置HTML5提供了一个用于处理视瓶的API,通过这个API可以获取和设置视瓶的各种属性和状态。
其中,currentTime属性表示视瓶当前播放位置,可以通过JavaScript来获取和设置。
我们可以在用户观看视瓶时,定时记录当前播放位置,当用户关闭视瓶或者刷新页面时,将当前播放位置保存在本地存储中,以便下次恢复视瓶播放时使用。
4.2 恢复视瓶播放当用户再次进入视瓶页面时,我们可以通过读取本地存储中保存的播放位置,将视瓶的currentTime属性设置为上次保存的位置,然后调用play()方法使视瓶从上次观看的位置开始播放。
通过这样的方式,就能够实现视瓶的断点续播功能。
5. 实例演示以下是一个简单的HTML5视瓶播放器的实现示例:```html<video id="videoPlayer" src="video.mp4"></video><script>var video = document.getElementById('videoPlayer');var lastPlayPosition = localStorage.getItem('lastPlayPosition');if (lastPlayPosition) {video.currentTime = lastPlayPosition;}video.onplay = function() {setInterval(function() {localStorage.setItem('lastPlayPosition', video.currentTime); }, 1000);}</script>```在这个示例中,我们使用lt;videogt;标签来嵌入视瓶,通过JavaScript代码来实现断点续播的功能。
PHP⼤⽂件分割分⽚上传实现代码服务端为什么不能直接传⼤⽂件?跟php.ini⾥⾯的⼏个配置有关upload_max_filesize = 2M //PHP最⼤能接受的⽂件⼤⼩post_max_size = 8M //PHP能收到的最⼤POST值'memory_limit = 128M //内存上限max_execution_time = 30 //最⼤执⾏时间当然不能简单粗暴的把上⾯⼏个值调⼤,否则服务器内存资源吃光是迟早的问题。
解决思路好在HTML5开放了新的FILE API,也可以直接操作⼆进制对象,我们可以直接在浏览器端实现⽂件切割,按照以前的做法就得⽤Flash的⽅案,实现起来会⿇烦很多。
JS思路1.监听上传按钮的onchange事件2.获取⽂件的FILE对象3.把⽂件的FILE对象进⾏切割,并且附加到FORMDATA对象中4.把FORMDATA对象通过AJAX发送到服务器5.重复3、4步骤,直到⽂件发送完。
PHP思路1.建⽴上传⽂件夹2.把⽂件从上传临时⽬录移动到上传⽂件夹3.所有的⽂件块上传完成后,进⾏⽂件合成4.删除⽂件夹5.返回上传后的⽂件路径DEMO代码前端部分代码<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><style>#progress{width: 300px;height: 20px;background-color:#f7f7f7;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px;background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);}#finish{background-color: #149bdf;background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent); background-size:40px 40px;height: 100%;}form{margin-top: 50px;}</style></head><body><div id="progress"><div id="finish" style="width: 0%;" progress="0"></div></div><form action="./upload.php"><input type="file" name="file" id="file"><input type="button" value="停⽌" id="stop"></form><script>var fileForm = document.getElementById("file");var stopBtn = document.getElementById('stop');var upload = new Upload();fileForm.onchange = function(){upload.addFileAndSend(this);}stopBtn.onclick = function(){this.value = "停⽌中";upload.stop();this.value = "已停⽌";}function Upload(){var xhr = new XMLHttpRequest();var form_data = new FormData();const LENGTH = 1024 * 1024;var start = 0;var end = start + LENGTH;var blob;var blob_num = 1;var is_stop = 0//对外⽅法,传⼊⽂件对象this.addFileAndSend = function(that){var file = that.files[0];blob = cutFile(file);sendFile(blob,file);blob_num += 1;}//停⽌⽂件上传this.stop = function(){xhr.abort();is_stop = 1;}//切割⽂件function cutFile(file){var file_blob = file.slice(start,end);start = end;end = start + LENGTH;return file_blob;};//发送⽂件function sendFile(blob,file){var total_blob_num = Math.ceil(file.size / LENGTH);form_data.append('file',blob);form_data.append('blob_num',blob_num);form_data.append('total_blob_num',total_blob_num);form_data.append('file_name',);xhr.open('POST','./upload.php',false);xhr.onreadystatechange = function () {var progress;var progressObj = document.getElementById('finish');if(total_blob_num == 1){progress = '100%';}else{progress = Math.min(100,(blob_num/total_blob_num)* 100 ) +'%';}progressObj.style.width = progress;var t = setTimeout(function(){if(start < file.size && is_stop === 0){blob = cutFile(file);sendFile(blob,file);blob_num += 1;}else{setTimeout(t);}},1000);}xhr.send(form_data);}}</script></body></html>PHP部分代码<?phpclass Upload{private $filepath = './upload'; //上传⽬录private $tmpPath; //PHP⽂件临时⽬录private $blobNum; //第⼏个⽂件块private $totalBlobNum; //⽂件块总数private $fileName; //⽂件名public function __construct($tmpPath,$blobNum,$totalBlobNum,$fileName){ $this->tmpPath = $tmpPath;$this->blobNum = $blobNum;$this->totalBlobNum = $totalBlobNum;$this->fileName = $fileName;$this->moveFile();$this->fileMerge();}//判断是否是最后⼀块,如果是则进⾏⽂件合成并且删除⽂件块private function fileMerge(){if($this->blobNum == $this->totalBlobNum){$blob = '';for($i=1; $i<= $this->totalBlobNum; $i++){$blob .= file_get_contents($this->filepath.'/'. $this->fileName.'__'.$i);}file_put_contents($this->filepath.'/'. $this->fileName,$blob);$this->deleteFileBlob();}}//删除⽂件块private function deleteFileBlob(){for($i=1; $i<= $this->totalBlobNum; $i++){@unlink($this->filepath.'/'. $this->fileName.'__'.$i);}}//移动⽂件private function moveFile(){$this->touchDir();$filename = $this->filepath.'/'. $this->fileName.'__'.$this->blobNum;move_uploaded_file($this->tmpPath,$filename);}//API返回数据public function apiReturn(){if($this->blobNum == $this->totalBlobNum){if(file_exists($this->filepath.'/'. $this->fileName)){$data['code'] = 2;$data['msg'] = 'success';$data['file_path'] = 'http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['DOCUMENT_URI']).str_replace('.','',$this->filepath).'/'. $this->fileName; }}else{if(file_exists($this->filepath.'/'. $this->fileName.'__'.$this->blobNum)){$data['code'] = 1;$data['msg'] = 'waiting for all';$data['file_path'] = '';}}header('Content-type: application/json');echo json_encode($data);}//建⽴上传⽂件夹private function touchDir(){if(!file_exists($this->filepath)){return mkdir($this->filepath);}}}//实例化并获取系统变量传参$upload = new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name']);//调⽤⽅法,返回结果$upload->apiReturn();以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
php使用html5实现多文件上传实例php使用html5实现多文件上传实例在html没有出来之前,要实现php多文件上传比较麻烦,需要在form表单里面添加多个input file域。
html5发布以后,我们可以使用input file的html5属性multiple来实现多文件上传,需要的.朋友可以参考下。
首先向大家介绍一下html5中file的multiple属性定义和用法:multiple 属性规定输入字段可选择多个值。
如果使用该属性,则字段可接受多个值。
实例:<form action="demo_form.asp" method="get">Select images: <input type="file" name="img" multiple="multiple" /><input type="submit" /></form>上面实例中的input file 可接受多个文件上传字段。
了解了html5中file的multiple属性,下面我们开始讲解使用html5实现多文件上传。
实例代码:html:<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><form action="my_parser.php" method="post" enctype="multipart/form-data"><p><input name="upload[]" type="file" multiple="multiple" /></p><input type="submit" value="Upload all files"></form></body></html>php代码:for($i=0; $i<count($_FILES['upload']['name']); $i++) {//Get the temp file path$tmpFilePath = $_FILES['upload']['tmp_name'][$i];//Make sure we have a filepathif ($tmpFilePath != ""){//Setup our new file path$newFilePath = "./uploadFiles/" . $_FILES['upload']['name'][$i];//Upload the file into the temp dirif(move_uploaded_file($tmpFilePath, $newFilePath)) {//Handle other code here}}}最后,非常感谢大家的阅读,希望能帮助到大家!。
前端大文件上传的优化和断点续传的实现方法近年来,随着互联网应用的不断发展,大文件的上传需求也越来越普遍。
然而,传统的文件上传方式往往面临着诸多问题,如传输速度慢、易中断等。
为了解决这些问题,前端技术不断发展,推出了一系列的优化方法和断点续传的实现方式。
1. 分片上传优化文件分片上传是指将大文件拆分成小块进行上传,减少了单个文件的传输时间,提高了上传速度。
同时,由于分片上传可以同时上传多个小文件块,这也进一步提升了并发上传的效率。
在实现分片上传的过程中,需要注意文件拆分的策略。
一般来说,可以根据文件大小进行分片,也可以根据网络环境进行动态调整。
而在前端实现方面,可以利用HTML5的Blob对象,将文件拆分成多个小块,并使用XMLHttpRequest或fetch API发送这些小块。
2. 断点续传的实现方法断点续传是指在文件上传过程中,当上传中断或失败后,能够从中断位置继续上传,而不需要重新上传整个文件。
这对于大文件上传来说,尤为重要。
实现断点续传的方法有多种,其中比较常用的是使用服务器端的文件分块存储和前端的上传进度记录。
具体步骤如下:1)在前端,每次上传文件块之前,记录已上传的文件块信息,如文件大小、块索引等。
2)在上传过程中,不断地将已上传的文件块信息传送给服务器,以便服务器端进行记录。
3)当上传中断后,下次继续上传时,前端可以从服务器端获取已上传的文件块信息,然后根据这些信息继续上传剩余的文件块。
4)服务器端接收到上传的文件块后,根据已上传的文件块信息进行重组,最终完成文件的上传。
通过实现断点续传,可以极大地提高大文件上传的可靠性和效率。
同时,在网络不稳定或传输过程中出现问题时,断点续传也能够减少用户的重复操作。
3. 上传进度提示与优化在大文件上传过程中,对用户进行上传进度的提示,可以提升用户体验,并且对于上传过程中的异常情况也能更好地进行处理。
在前端实现中,可以通过监听上传事件,获取当前上传的进度,并实时更新进度条。
HTML5实现⽂件上传下载功能实例解析前⾔:因⾃⼰负责的项⽬(jetty内嵌启动的SpringMvc)中需要实现⽂件上传,⽽⾃⼰对java⽂件上传这⼀块未接触过,且对 Http 协议较模糊,故这次采⽤渐进的⽅式来学习⽂件上传的原理与实践。
该博客重在实践。
⼀. Http协议原理简介HTTP是⼀个属于应⽤层的⾯向对象的协议,由于其简捷、快速的⽅式,适⽤于分布式超媒体信息系统。
它于1990年提出,经过⼏年的使⽤与发展,得到不断地完善和扩展。
⽬前在WWW中使⽤的是HTTP/1.0的第六版,HTTP/1.1的规范化⼯作正在进⾏之中,⽽且HTTP-NG(Next Generation of HTTP)的建议已经提出。
简单来说,就是⼀个基于应⽤层的通信规范:双⽅要进⾏通信,⼤家都要遵守⼀个规范,这个规范就是HTTP协议。
1.特点:(1)⽀持客户/服务器模式。
(2)简单快速:客户向服务器请求服务时,只需传送请求⽅法和路径。
请求⽅法常⽤的有GET、HEAD、POST。
每种⽅法规定了客户与服务器联系的类型不同。
由于HTTP协议简单,使得HTTP服务器的程序规模⼩,因⽽通信速度很快。
(3)灵活:HTTP允许传输任意类型的数据对象。
正在传输的类型由Content-Type加以标记。
(4)⽆连接:⽆连接的含义是限制每次连接只处理⼀个请求。
服务器处理完客户的请求,并收到客户的应答后,即断开连接。
采⽤这种⽅式可以节省传输时间。
(5)⽆状态:HTTP协议是⽆状态协议。
⽆状态是指协议对于事务处理没有记忆能⼒。
缺少状态意味着如果后续处理需要前⾯的信息,则它必须重传,这样可能导致每次连接传送的数据量增⼤。
另⼀⽅⾯,在服务器不需要先前信息时它的应答就较快。
注意:其中(4)(5)是⾯试中常⽤的⾯试题。
虽然HTTP协议(应⽤层)是⽆连接,⽆状态的,但其所依赖的TCP协议(传输层)却是常连接、有状态的,⽽TCP协议(传输层)⼜依赖于IP协议(⽹络层)。
前端⼤⽂件上传断点续传解决⽅案之前仿造写了⼀个HTML5版的⽂件上传插件,没看过的朋友可以先看⼀下~得到了不少朋友的好评,我⾃⼰也⽤在了项⽬中,不论是⽤户头像上传,还是各种媒体⽂件的上传,以及各种个性的业务需求,都能得到满⾜。
⼩⼩开⼼了⼀把。
但⽆论插件再怎么灵活,也难以应付所有的需求,⽐如,你要上传⼀个2G的⽂件。
以现在我们的⽹速,恐怕再快也得传半⼩时。
要命的是,如果你在上传到90%的时候不⼩⼼关掉了浏览器,或者是⼿⼀抖摁了F5,完了,⼀切还得从头再来。
这种⽤户体验简直太糟糕了。
所以,断点续传就⼗分有必要了。
什么是续传我就不解释了,⽤QQ传⽂件这么多年,⼤家都见过了。
这⾥要说的是断点续传都有哪些技术要点。
使⽤传统的表单提交⽂件或是HTML5的FormData都是将⽂件“整块”提交,服务端取到该⽂件后再进⾏转移、重命名等操作,因此,⽆法实时保存⽂件的已上传部分。
⽽且在http协议下,我们⽆法保持浏览器与服务端的长连接,不能以⽂件流的形式来提交。
所以要解决的问题具体来讲有以下⼏点:对上传的⽂件进⾏分割,每次只上传⼀⼩⽚。
服务端接收到⽂件后追加到原来部分,最后合并成完整的⽂件。
每次上传⽂件⽚前先获取已上传的⽂件⼤⼩,确定本次应切割的位置每次上传完成后更新已上传⽂件⼤⼩的记录标识客户端和服务端的⽂件,保证不会把A⽂件的内容追加到B⽂件上在参考了张鑫旭⼤哥的后,我将学到的技术应⽤在了我的插件Huploadify中,成功的添加了断点续传功能。
在此将技术和插件都分享给⼤家。
⼯作原理/技术要点⾸先的⾸先,要明确,如果我们有⼀个10M的⽂件,每次切割上传1M,那么是需要发10次请求来完成的。
在http协议下,只能这么搞。
断点上传分三步来完成:选择⼀个⽂件后,获取该⽂件在服务器上的⼤⼩,通过本地存储或⾃定义的函数来获取。
根据已上传⼤⼩切割⽂件,发出n次请求不断向服务器提交⽂件⽚,服务端不断追加⽂件内容当已上传⽂件⼤⼩达到⽂件总⼤⼩时,上传结束⾸先是⽂件的分割,HTML5新增了Blob数据类型,并且提供了⼀个可以分割数据的⽅法:slice(),其⽤法和字符串、数组的slice()⽅法⼀样,可以截取⼀个⼆进制⽂件的⼀部分。
在网页中直接上传大文件一直是个比较头疼的问题,主要面临的问题一般包括两类:一是上传时间长中途一旦出错会导致前功尽弃;二是服务端配置复杂,要考虑接收超大表单和超时问题,如果是托管主机没准还改不了配置,默认只能接收小于4mb的附件。
比较理想的方案是能够把大文件分片,一片一片的传到服务端,再由服务端合并。
这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4mb以内,服务端不用做任何设置就可适应。
常用的解决方案是ria,以flex为例,通常是利用filereference.load方法加载文件得到bytearray,然后分片构造表单(flash的高版本不允许直接访问文件)。
不过这个load方法只能加载较小的文件,大约不超过300mb,因此适用性不是很强。
好在现在有了html5,我们可以直接构造分片了,这是一个非常喜人的进步,只可惜目前适用面不广(ie啊ie,真是恨你恨得牙痒痒)。
言归正传,来看一个demo吧,基于 mvc3,只是示例,很多问题做了简化处理。
主要是客户端,新特性都体现在这里:<%@ page language=c# inherits=system.web.mvc.viewpage<dynamic> %><!doctype html><html lang=zh-cn><head><meta charset=utf-8><title>html5大文件分片上传示例</title><script src=../scripts/jquery-1.11.1.min.js></script><script>var page = {init: function(){$(#upload).click($.proxy(this.upload, this));},upload: function(){var file = $(#file)[0].files[0], //文件对象name = , //文件名size = file.size, //总大小succeed = 0;var shardsize = 2 * 1024 * 1024, //以2mb为一个分片shardcount = math.ceil(size / shardsize); //总片数for(var i = 0;i < shardcount;++i){//计算每一片的起始与结束位置var start = i * shardsize,end = math.min(size, start + shardsize);//构造一个表单,formdata是html5新增的var form = new formdata();form.append(data, file.slice(start,end)); //slice方法用于切出文件的一部分form.append(name, name);form.append(total, shardcount); //总片数form.append(index, i + 1); //当前是第几片//ajax提交$.ajax({url: ../file/upload,type: post,data: form,async: true, //异步processdata: false, //很重要,告诉jquery不要对form进行处理contenttype: false, //很重要,指定为false才能形成正确的content-typesuccess: function(){++succeed;$(#output).text(succeed + / + shardcount);}});}}};$(function(){page.init();});</script></head><body><input type=file id=file /><button id=upload>上传</button><span id=output style=font-size:12px>等待</span></body></html>这里的slice方法和formdata都是html5之前不存在的。
PHP⼤⽂件分⽚上传的实现⽅法⼀、前⾔在⽹站开发中,经常会有上传⽂件的需求,有的⽂件size太⼤直接上传,经常会导致上传过程中耗时太久,⼤量占⽤带宽资源,因此有了分⽚上传。
分⽚上传主要是前端将⼀个较⼤的⽂件分成等分的⼏⽚,标识当前分⽚是第⼏⽚和总共⼏⽚,待所有的分⽚均上传成功的时候,在后台进⾏合成⽂件即可。
⼆、开发过程中遇到的问题1. 分⽚的时候每⽚该分多⼤size?太⼤会出现“413 request entity too large”2. 分⽚上传的时候并不是严格按照分⽚的序号顺序上传,如何判断所有的分⽚均上传成功?3. 合成⽂件的时候如何判断保证合成⼀个完整的⽂件⽽不出错?多个分⽚同时上传的时候,读写⽂件没有独占锁的时候会导致合成错误。
三、问题解决当出现413的时候,修改了 nginx.conf 和php.ini(1)nginx中添加client_max_body_size和client_body_buffer_size(2)php.ini添加post_max_size 和 upload_max_filesize重启nginx和php-fpm代码逻辑梳理和分享(1)先获取当前分⽚是第⼏⽚以及总共⼏⽚(2)创建⼀个⽂件夹⽤来存储所有的分⽚以及合成的⽂件(3)变量$done初始为true,⽤来判断是否所有的分⽚都上传完成,每个分⽚保存的时候使⽤分⽚序号作为⽂件名,然后判断所有的分⽚⽂件是否存在(4)当$done===true的时候,代表所有分⽚上传完成,合成⽂件。
$target变量代表合成后的⽂件名,file_exists判断是否已经合成成功,然后追加⽅式创建打开⽂件,循环将每个分⽚内容写⼊⼀个⽂件中。
在读取每个分⽚之前先判断当前分⽚是否存在,是为了防⽌多个进程执⾏合成⽂件代码块的时候导致某个分⽚已经写⼊删除,最后导致合成的⽂件是不完整的,此时需要删除合成的不完整的⽂件并退出exit当前进程。
其中每个分⽚最好设置独占锁,flock($in, LOCK_EX),⽤来保证读写分⽚的时候其他进程不会操作该分⽚。
HTML5上传超⼤⽂件解决⽅案⼀、概述所谓断点续传,其实只是指下载,也就是要从⽂件已经下载的地⽅开始继续下载。
在以前版本的HTTP协议是不⽀持断点的,HTTP/1.1开始就⽀持了。
⼀般断点下载时才⽤到Range和Content-Range实体头。
HTTP协议本⾝不⽀持断点上传,需要⾃⼰实现。
⼆、Range⽤于请求头中,指定第⼀个字节的位置和最后⼀个字节的位置,⼀般格式:Range:⽤于客户端到服务端的请求,可以通过改字段指定下载⽂件的某⼀段⼤⼩及其单位,字节偏移从0开始。
典型格式:Ranges: (unit=first byte pos)-[last byte pos]Ranges: bytes=4000- 下载从第4000字节开始到⽂件结束部分Ranges: bytes=0~N 下载第0-N字节范围的内容Ranges: bytes=M-N 下载第M-N字节范围的内容Ranges: bytes=-N 下载最后N字节内容1.以下⼏点需要注意:(1)这个数据区间是个闭合区间,起始值是0,所以“Range: bytes=0-1”这样⼀个请求实际上是在请求开头的2个字节。
(2)“Range: bytes=-200”,它不是表⽰请求⽂件开始位置的201个字节,⽽是表⽰要请求⽂件结尾处的200个字节。
(3)如果last byte pos⼩于first byte pos,那么这个Range请求就是⽆效请求,server需要忽略这个Range请求,然后回应⼀个200,把整个⽂件发给client。
(4)如果last byte pos⼤于等于⽂件长度,那么这个Range请求被认为是不能满⾜的,server需要回应⼀个416,Requested range not satisfiable。
2.⽰例解释:表⽰头500个字节:bytes=0-499表⽰第⼆个500字节:bytes=500-999表⽰最后500个字节:bytes=-500表⽰500字节以后的范围:bytes=500-第⼀个和最后⼀个字节:bytes=0-0,-1同时指定⼏个范围:bytes=500-600,601-999三、Content-Range⽤于响应头,指定整个实体中的⼀部分的插⼊位置,他也指⽰了整个实体的长度。
[转]前端实现⽂件的断点续传早就听说过断点续传这种东西,前端也可以实现⼀下断点续传在前端的实现主要依赖着HTML5的新特性,所以⼀般来说在⽼旧浏览器上⽀持度是不⾼的本⽂通过断点续传的简单例⼦(前端⽂件提交+后端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样式扔出来View Code(2)接下来是JS的实现解析通过FileList对象我们能获取到⽂件的⼀些信息其中的size就是⽂件的⼤⼩,⽂件的分分割分⽚需要依赖这个这⾥的size是字节数,所以在界⾯显⽰⽂件⼤⼩时,可以这样转化 // 计算⽂件⼤⼩size = file.size > 1024file.size / 1024 > 1024file.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, ).replace('{{fileType}}', file.type || .match(/\.\w+$/) + '⽂件').replace('{{fileSize}}', size).replace('{{progress}}', progress).replace('{{totalSize}}', file.size).replace('{{uploadVal}}', uploadVal));不过,在显⽰⽂件信息的时候,可能这个⽂件之前之前已经上传过了,为了断点续传,需要判断并在界⾯上做出提⽰通过查询本地看是否有相应的数据(这⾥的做法是当本地记录的是已经上传100%时,就直接是重新上传⽽不是继续上传了) // 初始通过本地记录,判断该⽂件是否曾经上传过percent = window.localStorage.getItem( + '_p');if (percent && percent !== '100.0') {progress = '已上传 ' + percent + '%';uploadVal = '继续上传';}显⽰了⽂件信息列表点击开始上传,可以上传相应的⽂件上传⽂件的时候需要就将⽂件进⾏分⽚分段⽐如这⾥配置的每段1024B,总共chunks段(⽤来判断是否为末段),第chunk段,当前已上传的百分⽐percent等需要提⼀下的是这个暂停上传的操作,其实我还没实现出来,暂停不了⽆奈ing...接下来是分段过程 // 设置分⽚的开始结尾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其实就是个参数,因为要在上⼀分段传完之后再传下⼀分段,所以这⾥的做法是在回调中继续调⽤这个上传操作接下来就是真正的⽂件上传操作了,⽤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']);}继续下⼀分段的上传时,就进⾏了递归操作,按顺序地上传下⼀分段截个图..这是完整的JS逻辑,代码有点⼉注释了应该不难看懂吧哈哈按 Ctrl+C 复制代码按 Ctrl+C 复制代码2. 后端实现这⾥的后端实现还是⽐较简单的,主要⽤依赖了 file_put_contents、file_get_contents 这两个⽅法要注意⼀下,通过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部分View Code先到这⼉~。
使⽤Html5的WebSocket在浏览器上传⽂件,⽀持多⽂件和⼤⽂件.使⽤websocket上传⽂件的简单例⼦:上篇⽂章没有解决的问题就是⼤⽂件的上传问题, ⽽且多⽂件上传问题也未协调. 所以这篇⽂章就是解决这两个问题的.如果将⼀个⼤⽂件直接读⼊内存再发送的话, 内存会吃不消, 所以我们把⼤⽂件分块传输. Html5的Fileread⽅法提供了读取⽂件部分内容Blob 的⽅法.为了保证后台接收到的分块数据的顺序不会乱掉, 我们需要后台确定写⼊分块数据后再发送下⼀块数据.在Html端:<!DOCTYPE html><html><head><title>WebSocket Chat Client</title><meta charset="utf-8"/><script type="text/javascript" src="jquery-1.6.4.min.js"></script><script type="text/javascript" src="jquery.json-2.3.min.js"></script><script type="text/javascript">$().ready(function() {// Check for the various File API support. if (window.File && window.FileReader && window.FileList&& window.Blob) {// Great success! All the File APIs are supported.} else {alert('The File APIs are not fully supported in this browser.');}});//在消息框中打印内容function log(text) {$("#log").append(text+"\n");}//全局的websocket变量var ws;var paragraph = 10485760;var fileList ;var file;var startSize,endSize = 0;var i = 0; j = 0;//连接服务器$(function() {$("#connect").click(function() {ws = new WebSocket($("#uri").val());//连接成功建⽴后响应ws.onopen = function() {log("成功连接到" + $("#uri").val());}//收到服务器消息后响应ws.onmessage = function(e) {log("服务器说" + e.data + (e.data=="ok"));if(e.data == "ok"){if(endSize < file.size){startSize = endSize;endSize += paragraph ;if (file.webkitSlice) {var blob = file.webkitSlice(startSize, endSize);} else if (file.mozSlice) {var blob = file.mozSlice(startSize, endSize);}var reader = new FileReader();reader.readAsArrayBuffer(blob);reader.onload = function loaded(evt) {var ArrayBuffer = evt.target.result;log("发送⽂件第" + (i++) + "部分");ws.send(ArrayBuffer);}}else{startSize = endSize = 0;i = 0;log("发送" + +"完毕");file = fileList[j++];if(){ws.send();}log("发送⽂件完毕");}}//连接关闭后响应ws.onclose = function() {log("关闭连接");ws = null;}return false;}});});$(function() {$("#sendFileForm").click(function() {fileList = document.getElementById("file").files;file = fileList[0];ws.send();})});$(function() {$("#reset").click(function() {$("#log").empty();return false;});});</script></head><body><span>Html5功能测试</span><span id="progress">0</span><br><input type="text" id="uri" value="ws://localhost:8887"style="width: 200px;"><input type="button" id="connect"value="Connect"><input type="button" id="disconnect"value="Disconnect" disabled="disabled"><form ><input id="file" type="file" multiple /><input type="button" id="sendFileForm" value="测试"/><input type="button" id="reset" value="清空消息框"/></form><form><textarea id="log" rows="30" cols="100"style="font-family: monospace; color: red;"></textarea></form></body></html>这⾥设置了⽂件⼤于paragraph (10M)时就会分块发送⽂件.服务器端:/*** 处理字符串消息*/public void onMessage( WebSocket conn, String message ) {System.out.println("⽂件名" + message);//将⽂件名写⼊连接对象中,(需要⼿动修改webSocket类)conn.setFileName(message);try {conn.send("ok");} catch (NotYetConnectedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** 处理⼆进制消息*/public void onMessage(WebSocket conn, byte[] message) {System.out.println("收到⼆进制流:");//将⼆进制流保存为⽂件, ⽂件名从连接对象中取出saveFileFromBytes(message, "src/" + conn.getFileName());//告诉前台可以继续发送了.try {conn.send("ok");} catch (NotYetConnectedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** 将⼆进制byte[]数组写⼊⽂件中* @param b byte[]数组* @param outputFile ⽂件位置* @return成功: true 失败: false*/public static boolean saveFileFromBytes(byte[] b, String outputFile){FileOutputStream fstream = null;File file = null;try{file = new File(outputFile);fstream = new FileOutputStream(file, true);fstream.write(b);}catch (Exception e){e.printStackTrace();return false;}finally{if (fstream != null){try{fstream.close();}catch (IOException e1){e1.printStackTrace();}}}return true;}好了, 顺序发送保证了后台写⼊的数据也是顺序的, ⽂件就不会出错了! 搞定!。
php+html5实现无刷新上传、大文件分片上传、断点续传的方法理清思路:引入了两个概念:块(block)和片(chunk)。
每个块由一到多个片组成,而一个资源则由一到多个块组成。
块是服务端的永久数据存储单位,片则只在分片上传过程中作为临时存储的单位。
服务端会以约一个月为单位周期性的清除上传后未被合并为块的数据片。
实现过程:将文件分割,分片上传,然后合并。
前端代码:<!doctype html><html><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv="X-UA-Compatible"content="ie=edge"><title>Document</title><style>#progress{width:300px;height:20px;background-color:#f7f7f7;box-shadow:inset01px2px rgba(0,0,0,0.1);border-radius:4px;background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);}#finish{background-color:#149bdf;background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent25%,transparent50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15)75%,transparent75%,transparent); background-size:40px40px;display:inline-block;height:20px;}form{margin-top:50px;}</style></head><body><p id="progress"><span id="finish"style="width:0%;"progress="0"></span></p><form action=""><input type="file"name="file"id="file"><input type="button"value="停止"id="stop"> </form><script>var fileForm=document.getElementById("file"); var stopBtn=document.getElementById('stop'); var upload=new Upload();fileForm.onchange=function(){upload.addFileAndSend(this);}stopBtn.onclick=function(){this.value="停止中";upload.stop();this.value="已停止";}function Upload(){var xhr=new XMLHttpRequest();var form_data=new FormData();const LENGTH=1024*1024;var start=0;var end=start+LENGTH;var blob;var blob_num=1;var is_stop=0//对外方法,传入文件对象this.addFileAndSend=function(that){ var file=that.files[0];blob=cutFile(file);sendFile(blob,file);blob_num+=1;}//停止文件上传this.stop=function(){xhr.abort();is_stop=1;}//切割文件function cutFile(file){var file_blob=file.slice(start,end); start=end;end=start+LENGTH;return file_blob;};//发送文件function sendFile(blob,file){var form_data=new FormData();var total_blob_num=Math.ceil(file.size/LENGTH);form_data.append('file',blob);form_data.append('blob_num',blob_num);form_data.append('total_blob_num',total_blob_num);form_data.append('file_name',);xhr.open('POST','./statics/style/index.php',false);xhr.onreadystatechange=function(){if(xhr.readyState==4&&xhr.status==200){console.log(xhr.responseText);}var progress;var progressObj=document.getElementById('finish');if(total_blob_num==1){progress='100%';}else{progress=Math.min(100,(blob_num/total_blob_num)*100)+'%'; console.log(progress);console.log('分割');}progressObj.style.width=progress; var t=setTimeout(function(){if(start<file.size&&is_stop===0){ blob=cutFile(file);sendFile(blob,file);blob_num+=1;}else{setTimeout(t);}},1000);}xhr.send(form_data);}}</script></body></html>后台PHP代码:<?phpclass Upload{private$filepath='./upload';//上传目录private$tmpPath;//PHP文件临时目录private$blobNum;//第几个文件块private$totalBlobNum;//文件块总数private$fileName;//文件名Public function __construct($tmpPath,$blobNum,$totalBlobNum,$fileName){$this->tmpPath=$tmpPath;$this->blobNum=$blobNum;$this->totalBlobNum=$totalBlobNum;//文件名编码$this->fileName=iconv('utf-8','gbk',$fileName);$this->moveFile();$this->fileMerge();}//判断是否是最后一块,如果是则进行文件合成并且删除文件块private function fileMerge(){if($this->blobNum==$this->totalBlobNum){$blob='';for($i=1;$i<=$this->totalBlobNum;$i++){$blob=file_get_contents($this->filepath.'/'.$this->fileName.'__'.$i);file_put_contents($this->filepath.'/'.$this->fileName,$blob,FILE_APPEND);}$this->deleteFileBlob();}}//删除文件块private function deleteFileBlob(){for($i=1;$i<=$this->totalBlobNum;$i++){@unlink($this->filepath.'/'.$this->fileName.'__'.$i);}}//移动文件private function moveFile(){$this->touchDir();$filename=$this->filepath.'/'.$this->fileName.'__'.$this->blobNum; move_uploaded_file($this->tmpPath,$filename);}//API返回数据public function apiReturn(){if($this->blobNum==$this->totalBlobNum){if(file_exists($this->filepath.'/'.$this->fileName)){$data['code']=2;$data['msg']='success';$data['file_path']= 'http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['SCRIPT_FILENAME'] ).str_replace('.','',$this->filepath).'/'.$this->fileName;$data['blobNum']=$this->blobNum;$data['totalBlobNum']=$this->totalBlobNum;}}else{if(file_exists($this->filepath.'/'.$this->fileName.'__'.$this->blobNum)){ $data['code']=1;$data['msg']='waiting for all';$data['file_path']='';$data['blobNum']=$this->blobNum;$data['totalBlobNum']=$this->totalBlobNum;}}header('Content-type:application/json');echo json_encode($data);}//建立上传文件夹private function touchDir(){if(!file_exists($this->filepath)){return mkdir($this->filepath);}}}if(!isset($_POST['blob_num'])){exit("error");}//实例化并获取系统变量传参$upload=new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blo b_num'],$_POST['file_name']);//调用方法,返回结果$upload->apiReturn();?>亲测有效,有兴趣的同学可以自己动手实践下。