最近做项目,要用到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(上传后由服务器返回)
因为我们使用ajax上传,所以使用input file的change事件,每次获取选择的文件,并保存到file_list队列
- $("#fileupload2").change(function(){
- var $this=$(this);
- var files=$this[0].files;
- //①首先得明白jQuery对象只能使用jQuery对象的属性和方法,JavaScript对象只能使用JavaScript对象的属性和方法;
//②files[0]是JavaScript的属性;
//③$('xx')是jQuery对象,$('xx')[0]是将jQuery对象:$('xx')转换为JavaScript对象,这样才可以使用JavaScript对象的属性和方法;
//④我们再看这行代码的背景,HTML5支持multiple属性,即<input type="file">可能会添加multiple属性并赋值:multiple="multiple",即<input type="file" //multiple="multiple">,这样一次性可同时上传多张图片,所以获得一张图片的方法就是:$('xx')[0].files[0]
//⑤扩展:
//$('xx')[0].files[0].size可获得文件的大小,单位是字节(B),使用$('xx')[0].files[0].size可用于判断文件的大小。
//最后:第②点可能描述不准,若发现错误欢迎指正,大家共同进步。
- //需要计算上传的数据总量和数量,限制上传大小
- 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]);
- <span style="white-space:pre;"> </span>//该函数用于在上传队列预览div里创建预览条目
- createFilePreview(files[index], $("#upload_file_list"));
- }
- }
- <span style="white-space:pre;"></span>
- $("#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]);
- }
- }