最近做项目,要用到js上传大文件插件,网上找了很多上传插件,最后试着用了jquery file upload插件,然后确实很简单就可以上传了,demo也可以直接用,但是具体的实现逻辑本封装了,而我木有去看js源码(有点懒,源码太多了),而且php服务器的源码也太大,完全看不过来,于是自己搜了搜ajax文件上传的基本逻辑,结合一些资料,自己鼓捣了一个简单文件上传插件,麻雀虽小,但是五脏俱全。
先说说ajax上传文件的基本逻辑:
首先是选择文件,使用fileinput标签,会得到一个待上传文件的队列:file_list
然后,选择提交(submit)表单,或者把文件队列加到formData里,作为ajax的data部分,文件会先被上传到指定服务器的文件缓存文件夹,存为缓存文件(外部不可访问),缓存文件夹是服务器配置的,例如php是在php.in里面配置缓存文件夹。
一直到前一步,服务器都是默认操作,直到客户端进度条走完,也就是上传完毕,自己写的服务器脚本开始运行,指定的服务器脚本会获取ajax提交的文件信息和缓存的文件路径,这时候,我们就可以在脚本里把缓存文件存到自己文件夹下,存为自定义的文件名,
然后,服务器返回客户端json数据,于是客户端ajax异步调用完成,调用回调函数处理服务器返回数据
1、首先:html基本元素
<div class="container"> <form action="uploadtemp.php" method="post" enctype="multipart/form-data"> <span class="btn btn-success fileinput-button"> <i class="glyphicon glyphicon-plus"></i> <span>Add files...</span> <!-- The file input field used as target for the file upload widget --> <input id="fileupload2" type="file" name="file_list[]" multiple> </span> </form> <div style="width: 100%;float:left"> <ul id="upload_file_list" class="file_list_ul"> <li class="file_list_li"> <img class="image_min_preview" src="http://placeholdit.imgix.net/~text?txtsize=50&bg=grey&txt=实例&w=300&h=300"> <div class="file_list_file_name">I am file name</div> <div class="file_list_file_msg">This is a return msg</div> <button class="file_list_file_cancel_btn btn btn-primary">This is cancel btn</button> </li> </ul> </div> <button id="start_upload_file_list" class="btn btn-primary" >开始上传</button> <button id="abort_upload_file_list" class="btn btn-primary" >取消上传</button> <div style="margin:10px 0px"> <div id="file_list_progress" class="_progress"> <div class="_progress-bar"></div> </div> </div> <div style="width: 100%;float:left"> <ul id="download_file_list" class="file_list_ul"> <li class="file_list_li"> <img class="image_min_preview" src="http://placeholdit.imgix.net/~text?txtsize=50&bg=grey&txt=实例&w=300&h=300"> <div class="file_list_file_name">I am download file name</div> <div class="file_list_file_msg">This is a return msg</div> <button class="file_list_file_cancel_btn btn btn-primary">This is cancel btn</button> </li> </ul> </div> </div>
因为我用了bootstrap,只是为了界面好看点,必须元素就三个 #fileupload2文件选择控件,然后上传队列预览div,上传按钮,取消上传按钮,进度条,还有下载队列div(上传后由服务器返回)
2、获取上传文件队列
因为我们使用ajax上传,所以使用input file的change事件,每次获取选择的文件,并保存到file_list队列
$("#fileupload2").change(function(){
var $this=$(this);
var files=$this[0].files;
//需要计算上传的数据总量和数量,限制上传大小
for(index in files){
//在list中列出所有待上传文件\
if(!isEmptyObject(files[index])){
//添加到上传队列
if($.inArray(files[index].name,file_list.file_name_list)!=-1){
alert("this file : "+files[index].name+" is already in the file list");
continue;
}
file_list.push(files[index]);
//该函数用于在上传队列预览div里创建预览条目
createFilePreview(files[index], $("#upload_file_list"));
}
}
$("#fileupload2").val('');
}
3、给上传按钮绑定点击事件
点击按钮即实现文件上传:
$("#start_upload_file_list").click(function(){
//alert("max_upload_file_num:"+max_upload_file_num);
//alert("max_upload_file_total_size:"+max_upload_file_total_size);
var $this=$(this);
$('#file_list_progress .progress-bar').css('width','0%');
//设置按钮不可用
$("li.file_list_li button").addClass("disabled").text("上传后不可用");
//提交上传
var formdata=new FormData();
for(index in file_list){
if(!isEmptyObject(file_list[index])){
file_num=file_num+1;
file_total_size=file_total_size+file_list[index].size;
formdata.append(file_list_name,file_list[index]);
}
}
//p判定是否满足上传限制
if(file_total_size>max_upload_file_total_size || file_num>max_upload_file_num){
alert("上传文件数不能超过"+max_upload_file_num+
"\r\n"+"上传文件总大小不能超过"+max_upload_file_total_size_M+"M"+
"\r\n"+"您的上传文件错处,请重新选择上传队列");
return;
}
$this.button('loading');
//ajax请求
var upload_ajax_request=$.ajax({
url: 'uploadtemp.php',
type: 'POST',
data: formdata,
cache: false,
processData: false,
contentType: false,
//这里我们先拿到jQuery产生的 XMLHttpRequest对象,为其增加 progress 事件绑定,然后再返回交给ajax使用
xhr: function(){
var xhr = $.ajaxSettings.xhr();
if(onprogress && xhr.upload) {
xhr.upload.addEventListener("progress" , onprogress, false);
return xhr;
}
},
success:function(data,status){
//alert(data);
var obj;
try {
obj = JSON.parse(data);
}catch (e){
alert("error:"+data);
//$this.button('上传出错');
}
//ShowObjProperty(obj);
for(file_name in obj){
//index
if(obj[file_name].success){
//上传成功
//上传成功从文件上传队列移除
$("li[name='upload_"+file_name+"']").slideUp('slow',function(){
$("li[name='upload_"+file_name+"']").remove();
});
createFileUploadSuccessView(obj[file_name],$("#download_file_list"));
//从队列里面删除返回成功的file
var index=$.inArray(file_name,file_list.file_name_list);
if(index!=-1){
file_list.file_name_list.splice(index,1);
file_list.splice(index,1);
}
$("li[name='upload_"+file_name+"'] div[name='msg']").html("<a href='"+obj[file_name].url+"'><font color='green'>上传成功</font></a>").fadeIn();
}else{
//上传失败
$("li[name='upload_"+file_name+"'] div[name='msg']").html("<font color='color'>"+obj[file_name].msg+"</font>").fadeIn();
$("li[name='upload_"+file_name+"'] button").removeClass("disabled").text("移除");
}
}
if(file_list.length==0){
$("#file_list_progress").slideUp();
$("#start_upload_file_list").slideUp();
}
$("#abort_upload_file_list").fadeOut();
$this.button('reset');
//alert(status);
},
error:function(xhr,error,exception){
alert(error);
},
complete:function(XHR, TS){
},
beforeSend:function(xhr){
}
});
//显示 取消上穿 按钮
$("#abort_upload_file_list").fadeIn().click(function(){
upload_ajax_request.abort();
$("#abort_upload_file_list").fadeOut();
$this.button('reset');
});
});
注意,在点击确定上传后,在将上传队列文件加载到formData对象里,再将formData作为数据data上传到服务器端
也可以看到ajax的回调函数可以处理服务器返回值
4、服务器端的实现:
简单地实现php的文件上传就可以了,注意规定好前后台通信的json格式就好了
<?php
/**
* Created by PhpStorm.
* User: GongCheng
* Date: 2017/03/16
* Time: 03:32 PM
*/
require_once("php/strTools.php");
$file_name='file_list';
if(isset($_FILES[$file_name])) {
//echo "files2<br/>";
$count = count($_FILES[$file_name]['name']);
$return = array();
for($i=0;$i<$count;$i++){
$file=array(
'name'=>$_FILES[$file_name]['name'][$i],
'type'=>$_FILES[$file_name]['type'][$i],
'size'=>$_FILES[$file_name]['size'][$i],
'tmp_name'=>$_FILES[$file_name]['tmp_name'][$i],
'error'=>$_FILES[$file_name]['error'][$i],
);
//var_dump($file);
$return[$file['name']]=jquery_save_img($file);
//echo $file['name']." : <br/>";
//var_dump($return[$file['name']]);
}
//echo json_encode($return);
echo ch_json_encode($return);
//echo json_last_error();
//var_dump($return);
//jquery_save_img($file_name);
}else{
echo json_encode(array(false=>array('msg'=>'can not find file_list name')));
}
function jquery_save_img($file)
{
$arrType=array('image/jpg','image/gif','image/png','image/bmp','image/pjpeg','image/jpeg',
'application/octet-stream','application/x-zip-compressed',
'video/mp4');
$max_size='500000000000'; // 最大文件限制(单位:byte)
$upfile='./upload_files'; //图片目录路径
//$file=$_FILES[$file_name];
//if(isset($_FILES["file"])
/*
echo 'filename:'.$file['tmp_name'].';<br />';
echo 'size:'.$file['size'].';<br />';
echo 'type:'.$file['type'].';<br />';
echo 'name:'.$file['name'].';<br />';
*/
if($_SERVER['REQUEST_METHOD']=='POST'){ //判断提交方式是否为POST
//clearstatcache();
//$file['tmp_name'] = str_replace('', '//', $file['tmp_name']);
if(!is_uploaded_file($file['tmp_name'])){ //判断上传文件是否存在
//echo $file['tmp_name']." -> ".$upfile."/".mb_convert_encoding($file['name'],"gbk", "utf-8");;
return (array(
'success'=>false,
'msg'=>'文件不存在!'
));
}
//echo "我是:haha";
if($file['size']>$max_size){ //判断文件大小是否大于500000字节
return (array(
'success'=>false,
'msg'=>'上传文件太大!'
));
}
if(!in_array($file['type'],$arrType)){ //判断图片文件的格式
return (array(
'success'=>false,
'msg'=>'上传文件格式不对!xxx:'.$file['type']
));
}
if(!file_exists($upfile)){ // 判断存放文件目录是否存在
mkdir($upfile,0777,true);
}
$imageSize=getimagesize($file['tmp_name']);
$img=$imageSize[0].'*'.$imageSize[1];
$fname=$file['name'];
$ftype=explode('.',$fname);
//因为在windows下为gbk编码,而php默认utf8编码,如果不转换编码,在中文时容易出现编码错误而不能存储
//mb_convert_encoding函数:转换编码
//mb_convert_encoding('待转化字符串','想要转换的编码','本身的编码')
$picName=$upfile."/".mb_convert_encoding($fname,"gbk", "utf-8");
//echo $picName;
if(file_exists($picName)){
return (array(
'success'=>false,
'msg'=>'同文件名已存在!'.$fname
));
}
if(!move_uploaded_file($file['tmp_name'],$picName)){
return (array(
'success'=>false,
'msg'=>'移动文件出错!'.$picName
));
//echo "<font color='#FF0000'>移动文件出错!</font>";
}
else{
/*
echo "<font color='#FF0000'>图片文件上传成功!</font><br/>";
echo "<font color='#0000FF'>图片大小:$img</font><br/>";
echo "图片预览:<br><div style='border:#F00 1px solid; width:200px;height:200px'>
<img src=\"".$picName."\" width=200px height=200px>".$fname."</div>";
*/
return (array(
'success'=>true,
'url'=>$upfile."/".$fname,
'name'=>$fname,
'size'=>$file['size'],
'type'=>$file['type']
));
//echo '{"imgurl":"'.$picName.'"}';
}
}
}
?>
最后:开发难点:
1、因为xhr对象已经预置了上传过程中的各种参数,比如上传进度回调函数,所以直接利用xhr对象实现进度条
但是jquery的ajax函数不提供原始的xhr对象,而只有在open函数前设置progress进度函数才起作用,所以无法
直接实现,但是jqeury的ajax函数可以使用自己建立的xhr对象,于是在ajax函数之前创建自己的xhr函数并设置
进度显示函数:onprogress
var upload_ajax_request=$.ajax({ url: 'uploadtemp.php', type: 'POST', data: formdata, cache: false, processData: false, contentType: false, //这里我们先拿到jQuery产生的 XMLHttpRequest对象,为其增加 progress 事件绑定,然后再返回交给ajax使用 xhr: function(){ var xhr = $.ajaxSettings.xhr(); if(onprogress && xhr.upload) { xhr.upload.addEventListener("progress" , onprogress, false); return xhr; } }, success:function(data,status){ //alert(data); var obj; try { obj = JSON.parse(data); }catch (e){ alert("error:"+data); //$this.button('上传出错'); } //ShowObjProperty(obj); for(file_name in obj){ //index if(obj[file_name].success){ //上传成功 //上传成功从文件上传队列移除 $("li[name='upload_"+file_name+"']").slideUp('slow',function(){ $("li[name='upload_"+file_name+"']").remove(); }); createFileUploadSuccessView(obj[file_name],$("#download_file_list")); //从队列里面删除返回成功的file var index=$.inArray(file_name,file_list.file_name_list); if(index!=-1){ file_list.file_name_list.splice(index,1); file_list.splice(index,1); } $("li[name='upload_"+file_name+"'] div[name='msg']").html("<a href='"+obj[file_name].url+"'><font color='green'>上传成功</font></a>").fadeIn(); }else{ //上传失败 $("li[name='upload_"+file_name+"'] div[name='msg']").html("<font color='color'>"+obj[file_name].msg+"</font>").fadeIn(); $("li[name='upload_"+file_name+"'] button").removeClass("disabled").text("移除"); } } if(file_list.length==0){ $("#file_list_progress").slideUp(); $("#start_upload_file_list").slideUp(); } $("#abort_upload_file_list").fadeOut(); $this.button('reset'); //alert(status); }, error:function(xhr,error,exception){ alert(error); }, complete:function(XHR, TS){ }, beforeSend:function(xhr){ } });
2、通过每次file input对象的每次change事件,记录选择的文件,将其记录到file_list数组,并将file对象的value值置null,不然每次选择同样的文件会不响应change函数
$("#fileupload2").change(function(){ some scripts;//必须先把处理代码卸了 //解决下次选择同样数据不响应,直接将本次数据清空 $("#fileupload2").val(''); }
3、在上传按钮的点击事件中,再将file_list文件队列安置到formData对象,然后利用ajax向后台发送文件
var formdata=new FormData(); for(index in file_list){ if(!isEmptyObject(file_list[index])){ file_num=file_num+1; file_total_size=file_total_size+file_list[index].size; formdata.append(file_list_name,file_list[index]); } }