问题:
请问如何上传比较大的文件?
服务端为什么不能直接上传大文件?跟php.ini 里面几个配置有很大的关系。
upload_max_filesize
post_max_size
memory_limit
max_execute_time
当然不能简单粗暴的将这几个参数调大,否则服务器会出现内存资源吃光是早晚的的问题。
解决思路
好在HTML5开放了新的FILE API,也可以直接操作二进制对象,我们可以直接在浏览器端实现文件切割,
按照以前的做法就得用Flash的方案,实现起来会麻烦很多。
JS思路
1.监听上传按钮的onchange事件
2.获取文件的FILE对象
3.把文件的FILE对象进行切割,并且附加到FORMDATA对象中
4.把FORMDATA对象通过AJAX发送到服务器
5.重复3、4步骤,直到文件发送完。
PHP思路
1.建立上传文件夹
2.把文件从上传临时目录移动到上传文件夹//移动文件
privatefunctionmoveFile(){
$this->touchDir();
$filename=$this->filepath.'/'.$this->fileName.'__'.$this->blobNum;
move_uploaded_file($this->tmpPath,$filename);
}
3.所有的文件块上传完成后,进行文件合成privatefunctionfileMerge(){
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();
}
}
4.删除文件夹//删除文件块
privatefunctiondeleteFileBlob(){
for($i=1;$i<=$this->totalBlobNum;$i++){
@unlink($this->filepath.'/'.$this->fileName.'__'.$i);
}
}
5.返回上传后的文件路径//API返回数据
publicfunctionapiReturn(){
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']='waitingforall';
$data['file_path']='';
}
}
header('Content-type:application/json');
echojson_encode($data);
}
upload.htmlhtml>
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
Document
#progress{
width:300px;
height:20px;
background-color:#f7f7f7;
box-shadow:inset01px2pxrgba(0,0,0,0.1);
border-radius:4px;
background-image:linear-gradient(tobottom,#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;
height:100%;
}
form{
margin-top:50px;
}
varfileForm=document.getElementById("file");
varstopBtn=document.getElementById('stop');
varupload=newUpload();
fileForm.onchange=function(){
upload.addFileAndSend(this);
}
stopBtn.onclick=function(){
this.value="停止中";
upload.stop();
this.value="已停止";
}
functionUpload(){
varxhr=newXMLHttpRequest();
varform_data=newFormData();
//constLENGTH=1024*1024;
constLENGTH=10*10;
varstart=0;
varend=start+LENGTH;
varblob;
varblob_num=1;
varis_stop=0
//对外方法,传入文件对象
this.addFileAndSend=function(that){
varfile=that.files[0];
blob=cutFile(file);
sendFile(blob,file);
blob_num+=1;
}
//停止文件上传
this.stop=function(){
xhr.abort();
is_stop=1;
}
//切割文件
functioncutFile(file){
varfile_blob=file.slice(start,end);
start=end;
end=start+LENGTH;
returnfile_blob;
};
//发送文件
functionsendFile(blob,file){
vartotal_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',file.name);
xhr.open('POST','./upload.php',false);
xhr.onreadystatechange=function(){
varprogress;
varprogressObj=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;
vart=setTimeout(function(){
if(start
blob=cutFile(file);
sendFile(blob,file);
blob_num+=1;
}else{
setTimeout(t);
}
},1000);
}
xhr.send(form_data);
}
}
upload.php<?php
classUpload{
private$filepath='./upload';//上传目录
private$tmpPath;//PHP文件临时目录
private$blobNum;//第几个文件块
private$totalBlobNum;//文件块总数
private$fileName;//文件名
publicfunction__construct($tmpPath,$blobNum,$totalBlobNum,$fileName){
$this->tmpPath=$tmpPath;
$this->blobNum=$blobNum;
$this->totalBlobNum=$totalBlobNum;
$this->fileName=$fileName;
$this->moveFile();
$this->fileMerge();
}
//判断是否是最后一块,如果是则进行文件合成并且删除文件块
privatefunctionfileMerge(){
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();
}
}
//删除文件块
privatefunctiondeleteFileBlob(){
for($i=1;$i<=$this->totalBlobNum;$i++){
@unlink($this->filepath.'/'.$this->fileName.'__'.$i);
}
}
//移动文件
privatefunctionmoveFile(){
$this->touchDir();
$filename=$this->filepath.'/'.$this->fileName.'__'.$this->blobNum;
move_uploaded_file($this->tmpPath,$filename);
}
//API返回数据
publicfunctionapiReturn(){
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']='waitingforall';
$data['file_path']='';
}
}
header('Content-type:application/json');
echojson_encode($data);
}
//建立上传文件夹
privatefunctiontouchDir(){
if(!file_exists($this->filepath)){
returnmkdir($this->filepath);
}
}
}
//实例化并获取系统变量传参
$upload=newUpload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name']);
//调用方法,返回结果
$upload->apiReturn();
项目版本二:
何为大文件切割上传?
原理:利用HTML5的新特性,将文件内容切割成分段的二进制信息,然后每次向服务器上传一段,而服务器,只需要把我们每次上传的二进制信息整合存储到一个文件中,那么最后这个文件就是所上传的文件。
由于php.ini默认的上传大小为2M,如果每批都上传2M,我测试的时候时间比较长,这里我将其限制大小该为20M,最大post数据为28M,便于测试。如果实际开发中不能修改其参数,则每批上传的数据不能超过最大限制。
post_max_size=28M
upload_max_filesize=20M
我的是在nginx里面上传所以还需要修改Nginx配置文件/etc/nginx/nginx.con
//在http{} 里面加即可
client_max_body_size50m#客户端最大上传大小50M
JavaScript中的File对象
在之前的文章中我们已经使用到了这个Api,File对象中保存了文件的大小、名称、类型等信息
JavaScript中的Blob对象
Blob对象是二进制对象,也是File对象的父类,Blob对象中有一个很重要的方法:slice() 方法,利用这个方法我们可以把文件内容切割成二进制信息。slice() 方法接受三个参数,起始偏移量,结束偏移量,还有可选的 mime 类型。如果 mime 类型,没有设置,那么新的 Blob 对象的 mime 类型和父级一样。html>
大文件切割上传
functionselfile(){
constLENGTH=1024*1024*10;//每次上传的大小
varfile=document.getElementsByName('video')[0].files[0];//文件对象
varfilename=document.getElementsByName('video')[0].files[0].name;
vartotalSize=file.size;//文件总大小
varstart=0;//每次上传的开始字节
varend=start+LENGTH;//每次上传的结尾字节
varfd=null//创建表单数据对象
varblob=null;//二进制对象
varxhr=null;//xhr对象
while(start
fd=newFormData();//每一次需要重新创建
xhr=newXMLHttpRequest();//需要每次创建并设置参数
xhr.open('POST','upload.php',false);
blob=file.slice(start,end);//根据长度截取每次需要上传的数据
fd.append('video',blob);//添加数据到fd对象中
fd.append('filename',filename);//获取文件的名称
xhr.send(fd);//将fd数据上传
//重新设置开始和结尾
start=end;
end=start+LENGTH;
}
}
大文件切割上传
接收数据页面upload.php:
/**
*大文件切割上传,把每次上传的数据合并成一个文件
*/
$filename='./uploads/'.$_POST['filename'];//确定上传的文件名
//第一次上传时没有文件,就创建文件,此后上传只需要把数据追加到此文件中
if(!file_exists($filename)){
move_uploaded_file($_FILES['video']['tmp_name'],$filename);
}else{
file_put_contents($filename,file_get_contents($_FILES['video']['tmp_name']),FILE_APPEND);
}
?>
测试之前,先创建uploads文件夹
如果是linux系统的话 切记给uploads给权限
sudo chmod -R 777 uoloads/ //赋予uploads文件夹 777 权限 -R 递归子文件