swfupload


 前言

        最近项目中要求使用HTTP做文件上传,而且要求有进度显示,在网上东找西寻了半天,解决方案倒也不少,比如使用Ajax上传,但感觉这种方式的进度提示太麻烦,所以没有采用,后来看到了SWFUpload,就找了些资料来看,觉得符合自己的要求,研究了足有两天,略有心得,忙不迭地记录下来,以防止时间长了遗忘,如果不小心帮助了别人,那就更好了,呵呵...

下面摘抄一段SWFUpload官网的介绍(别人翻译过来的):

SWFUpload 最初是由Vinterwebb.se 开发的客户端文件上传工具。它结合JavaScript和flash在浏览器中提供一个优于传统上传标签 的功能(和良好的用户体验)。

SWFUpload 的主要特性:

  • 文件浏览对话框中可以选择多个文件
  • AJAX风格的上传,不用重刷新
  • 上传过程中的各种事件.
  • 可以在客户端调节图片大小
  • 它使用的类命名空间兼容各种js库(i.e., jQuery, Prototype, 等.).
  • 支持 Flash 9 and Flash 10 (2.2.0版本后取消对flash 8的支持)

SWFUpload 的设计理念与其他基于flash的上传工具不同。SWFUpload 给开发者尽可能多的UI控制能力. 开发者可以使用 XHTML, CSS, javascript 来使它更符合自己网站的样式风格. 它提供一组简单的js事件更新上传状态,开发者可以根据这些事件来在网页中显示文件上传进度

好了,夸奖的话不多说了,既然它这么受欢迎,想必是有一定优势的。

下面记录一下我做的一个小示例,先预览一下程序运行效果:

SWFUpload文件上传效果图

有朋友索要例子源码,其实我早已放在我的资源里了,并没有设置下载积分,有需要的朋友请到这里下载:http://download.csdn.net/detail/zhangyihui1986/4538748

http://code.google.com/p/swfupload/  ,请自行下载,下载的压缩文件中含有一个文档,里面详细介绍了SWFUpload的配置参数、事件支持、支持方法等。

这里是官网上的几个例子:http://demo.swfupload.org/

网络上关于SWFUpload的博客资源很多,但很多博客质量不太高,而且转来转去,内容重复,所以我在查资料的时候没有找到太合适的资源,后来找到了这里:http://webdeveloperplus.com/jquery/multiple-file-upload-with-progress-bar-using-jquery/ ,这是一个外国人写的关于SWFUpload的小教程,UI做地也挺好,于是我就参照着做了个小例子。

不过,上面链接的教程是针对SWFUpload一个针对jQuery的插件写的,这个插件地址为:http://blogs.bigfish.tv/adam/2009/06/14/swfupload-jquery-plugin/,该插件好像仅仅是完善了一下SWFUpload的事件机制,使用它可采用jquery的链式写法,但我没有用它,而是用的原生的SWFUpload2.2版本。

view plaincopy      
  1. <link href="<%=path %>/js/ligerUI/skins/Aqua/css/ligerui-all.css" type="text/css" rel="stylesheet" />  
  2. <script type="text/javascript" src="<%=path %>/js/jquery-1.7.2.min.js">script>  
  3. <script type="text/javascript" src="<%=path %>/js/ligerUI/js/core/base.js">script>  
  4. <script type="text/javascript" src="<%=path %>/js/ligerUI/js/plugins/ligerDrag.js">script>  
  5. <script type="text/javascript" src="<%=path %>/js/ligerUI/js/plugins/ligerDialog.js">script>  
  6. <script type="text/javascript" src="<%=path %>/js/swfupload/swfupload.js">script>  
  7. <script type="text/javascript" src="<%=path %>/js/swfupload/plugins/swfupload.queue.js">script>  

在此处我用到了ligerUI框架,如果不使用它,那么仅需要引jquery和swfupload.js两个文件即可。

3)JSP文件中HTML结构

[html] view plaincopy      
  1. <div id="swfupload">    
  2.     <span id="spanButtonPlaceholder">span>    
  3.     <id="queueStatus">p>    
  4.     <ol id="logList">ol>    
  5. div>  


4)JSP文件中CSS,给文件上传进度列表赋与样式,我对CSS不够熟悉,此处的样式几乎全部来自于前面那个教程,仅作了一些小的变动。如果您觉得这还不够好,那您自己来实现漂亮的UI界面吧!

[css] view plaincopy      
  1. #logList { margin: 0; padding: 0; width: 500px }    
  2. #logList li { list-style-position: inside; margin: 2px; border: 1px solid #ccc; padding: 10px; color: #333;  
  3.             font-size: 15px; background: #FFF; position: relative; }    
  4. #logList li .progressBar { border:1px solid #333; height:5px; background:#fff; }      
  5. #logList li .progressValue { color: red; margin-left: 5px }      
  6. #logList li .progress { background:#999; width:0%; height:5px; }      
  7. #logList li p { margin:0; line-height:18px; }      
  8. #logList li.success { border:1px solid #339933; background:#ccf9b9; }    
  9. #logList li span.cancel { background:url('../images/delete.gif') no-repeat; position:absolute; top:5px;  
  10.             right:5px; width:16px; height:16px; cursor:pointer }    


5)JSP中的Javascript,主要是创建SWFUpload组件实例,并绑定监听函数,在监听函数中处理进度提示:

[javascript] view plaincopy      
  1. var contextPath;  
  2. var queueErrorArray;  
  3. $(function(){  
  4.     contextPath = $("#contextPath").val();  
  5.       
  6.     var swfUpload = new SWFUpload({  
  7.         upload_url: contextPath + '/swfupload/upload!upload.action',  
  8.         flash_url: contextPath + '/js/swfupload/Flash/swfupload.swf',  
  9.           
  10.         file_post_name: 'fileData',  
  11.         use_query_string: true,  
  12.         post_params: {  
  13.             param1: 'Hello',  
  14.             param2: encodeURI('你好',"utf-8")  
  15.         },  
  16.           
  17.         file_types: "*.rar;*.zip",  
  18.         file_types_description: '上报数据文件',  
  19.         file_size_limit: '102400',  
  20. //      file_upload_limit: 5,  
  21.         file_queue_limit: 3,  
  22.           
  23.         // handlers  
  24.         file_dialog_start_handler: fileDialogStart,  
  25.         file_queued_handler: fileQueued,  
  26.         file_queue_error_handler: fileQueueError,  
  27.         file_dialog_complete_handler: fileDialogComplete,  
  28.         upload_start_handler: uploadStart,  
  29.         upload_progress_handler: uploadProgress,  
  30.         upload_success_handler: uploadSuccess,  
  31.         upload_complete_handler: uploadComplete,  
  32.           
  33.         button_placeholder_id: 'spanButtonPlaceholder',  
  34.         button_text: '选择文件',  
  35.         button_text_style: '.whiteFont{ color: #FFFFFF; }',  
  36.         button_text_left_padding: 40,  
  37.         button_text_top_padding: 6,  
  38.         button_image_url: contextPath + '/images/button.png',  
  39.         button_width: 133,  
  40.         button_height: 33,  
  41.         button_cursor: SWFUpload.CURSOR.HAND,  
  42.         button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,  
  43.           
  44.         debug: false,  
  45.           
  46.         custom_settings: {}  
  47.     });  
  48. });  
  49.   
  50. //========================================  回调函数Handlers  ===================================  
  51.   
  52. /** 
  53. * 打开文件选择对话框时响应 
  54. */  
  55. function fileDialogStart() {  
  56.     if (queueErrorArray) {  
  57.         queueErrorArray = null;  
  58.     }  
  59. }  
  60.   
  61. /** 
  62. * 文件被加入上传队列时的回调函数,增加文件信息到列表并自动开始上传. 
  63.  
  64. * SWFUpload.startUpload(file_id)方法导致指定文件开始上传, 
  65. * 如果参数为空,则默认上传队列第一个文件; 
  66. * SWFUpload.cancelUpload(file_id,trigger_error_event)取消指定文件上传并从队列删除, 
  67. * 如果file_id为空,则删除队列第一个文件,trigger_error_event表示是否触发uploadError事件. 
  68. * @param file 加入队列的文件 
  69. */  
  70. function fileQueued(file) {  
  71.     var swfUpload = this;  
  72.     var listItem = ''">';  
  73.     listItem += '文件:' + file.name + '(' + Math.round(file.size/1024) + ' KB)';  
  74.     listItem += ''  
  75.               + '

'  
  •               + 'Pending

    '  
  •               + ' '  
  •               + '
  • ';  
  •     $("#logList").append(listItem);  
  •     $("li#" + file.id + " .cancel").click(function(e) {  
  •         swfUpload.cancelUpload(file.id);  
  •         $("li#" + file.id).slideUp('fast');  
  •     })  
  • //  swfUpload.startUpload();  
  • }  
  •   
  • /** 
  • * 文件加入上传队列失败时触发,触发原因包括: 
  • * 文件大小超出限制 
  • * 文件类型不符合 
  • * 上传队列数量限制超出等. 
  • * @param file 当前文件 
  • * @param errorCode 错误代码(参考SWFUpload.QUEUE_ERROR常量) 
  • * @param message 错误信息 
  • */  
  • function fileQueueError(file,errorCode,message) {  
  •     if (errorCode == SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) {  
  •         alert("上传队列中最多只能有3个文件等待上传.");  
  •         return;  
  •     }  
  •     if (!queueErrorArray) {  
  •         queueErrorArray = [];  
  •     }  
  •     var errorFile = {  
  •         file: file,  
  •         code: errorCode,  
  •         error: ''  
  •     };  
  •     switch (errorCode) {  
  •     case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:  
  •         errorFile.error = '文件大小超出限制.';  
  •         break;  
  •     case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE:  
  •         errorFile.error = '文件类型受限.';  
  •         break;  
  •     case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:  
  •         errorFile.error = '文件为空文件.';  
  •         break;  
  •     default:  
  •         alert('加载入队列出错.');  
  •         break;  
  •     }  
  •     queueErrorArray.push(errorFile);  
  • }  
  •   
  • /** 
  • * 选择文件对话框关闭时触发,报告所选文件个数、加入上传队列文件数及上传队列文件总数 
  • * @param numSelected 选择的文件数目 
  • * @param numQueued 加入队列的文件数目 
  • * @param numTotalInQueued 上传文件队列中文件总数 
  • */  
  • function fileDialogComplete(numSelected,numQueued,numTotalInQueued) {  
  •     var swfupload = this;  
  •     if (queueErrorArray && queueErrorArray.length) {  
  •         var table = $('
    文件大小
    ');  
  •         for(var i in queueErrorArray) {  
  •             var tr = $('');  
  •             var info = '' + queueErrorArray[i].file.name + '('   
  •                         + queueErrorArray[i].error + ')'  
  •                         + '' + queueErrorArray[i].file.size + 'bytes';  
  •             table.append(tr.append(info));  
  •         }  
  •         $.ligerDialog.open({  
  •             width: 500,  
  •             content: table,  
  •             title: '文件选择错误提示',  
  •             buttons: [{  
  •                 text: '确定',  
  •                 onclick: function(btn,dialog,index) {  
  •                     $("#queueStatus").text('选择文件: ' + numSelected   
  •                             + ' / 加入队列文件: ' + numQueued);  
  •                     swfupload.startUpload();  
  •                     dialog.close();  
  •                 }  
  •             }]  
  •         });  
  •         queueErrorArray = [];  
  •     } else {  
  •         this.startUpload();  
  •     }  
  • }  
  •   
  • /** 
  • * 文件开始上传时触发 
  • * @param file 开始上传目标文件 
  • */  
  • function uploadStart(file) {  
  •     if (file) {  
  •         $("#logList li#" + file.id).find('p.status').text('上传中...');  
  •         $("#logList li#" + file.id).find('p.progressValue').text('0%');  
  •     }  
  • }  
  •   
  • /** 
  • * 文件上传过程中定时触发,更新进度显示 
  • * @param file 上传的文件 
  • * @param bytesCompleted 已上传大小 
  • * @param bytesTotal 文件总大小 
  • */  
  • function uploadProgress(file,bytesCompleted,bytesTotal) {  
  •     var percentage = Math.round((bytesCompleted / bytesTotal) * 100);  
  •     $("#logList li#" + file.id).find('div.progress').css('width',percentage + '%');  
  •     $("#logList li#" + file.id).find('span.progressValue').text(percentage + '%');  
  • }  
  •   
  • /** 
  • * 文件上传完毕并且服务器返回200状态码时触发,此时文件的上传周期并未完成, 
  • * 不能在此事件监听函数开始下一个文件的上传 
  • * @param file 上传的文件 
  • * @param serverData 服务器在执行完接收文件方法后返回的数据 
  • * @param response Boolean类型,表示是否服务器返回数据 
  • */  
  • function uploadSuccess(file,serverData,response) {  
  •     var item = $("#logList li#" + file.id);  
  •     item.find('div.progress').css('width','100%');  
  •     item.find('span.progressValue').css('color','blue').text('100%');  
  •       
  •     item.addClass('success').find('p.status').html('上传完成!');  
  • }  
  •   
  • /** 
  • * 在一个上传周期结束后触发(uploadError及uploadSuccess均触发) 
  • * 在此可以开始下一个文件上传(通过上传组件的uploadStart()方法) 
  • * @param file 上传完成的文件对象 
  • */  
  • function uploadComplete(file) {  
  •     this.uploadStart();  
  • }  
  • view plaincopy      
    1. var swfUpload = new SWFUpload({settings});  

    我们需要传给它一个配置对象,这个配置对象的内容很多,详细介绍请参照使用说明文档,在此仅介绍几个重要一些的配置参数,以注释的形式写在代码里。

    [javascript] view plaincopy      
    1. upload_url: '',     // 上传操作后台处理URL,相当于form的action属性  
    2. flash_url: '',      // swf文件的位置,指向JS目录下的swfupload.swf文件即可  
    3.   
    4. file_post_name: '', // 提交到后台的文件的名字,相当于域的name值,默认为“Filedata”  
    5.   
    6. file_types: "*.rar;*.zip",  // 可上传的文件类型  
    7. file_types_description: '', // 可上传文件类型的描述信息  
    8. file_size_limit: '',        // 上传文件大小限制,接受值和单位,默认单位是KB,如'1024 MB'  
    9.   
    10. button_placeholder_id: 'spanButtonPlaceholder', // 设置一个HTML元素,用以渲染Flash的Button  
    11. button_image_url: '',   // 按钮图片,Flash使用,可以有多种状态(mouseout、hover等)  
    12. button_width: 270,      // 按钮的宽,必须要设置,不设置按钮无法显示  
    13. button_height: 20,      // 按钮的高,必须要设置,不设置按钮无法显示  
    14. button_cursor: SWFUpload.CURSOR.HAND,   // 鼠标移到按钮上的光标样式  
    15. button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,  // Flash剪辑的WMODE属性  

    需要注意的是按钮的高和宽一定要指定,否则flash无法显示。

    view plaincopy      
    1. // handlers:事件监听函数,请参考使用说明文档  
    2. file_dialog_start_handler: fileDialogStart,// 文件选择对话框打开时触发,传入SWFUpload定义的File参数  
    3. file_queued_handler: fileQueued,// 文件被加入队列时触发  
    4. file_queue_error_handler: fileQueueError,// 文件加入队列出错时触发,包括大小限制,类型限制,空文件等均会触发  
    5. file_dialog_complete_handler: fileDialogComplete,// 文件选择对话框在文件选择完成并关闭时触发  
    6. upload_start_handler: uploadStart,// 文件开始上传时触发  
    7. upload_progress_handler: uploadProgress,// 上传过程中定时触发,此方法在更新进度条时比较重要  
    8. upload_success_handler: uploadSuccess,// 文件上传成功时触发  
    9. upload_complete_handler: uploadComplete,// 文件上传完成时触发,包括上传成功与上传失败,此方法可以开始下一文件的上传  

    view plaincopy      
    1. this.startUpload();  

    view plaincopy      
    1. this.startUpload();  

    因为upload_complete_handler事件是在一个文件上传后触发,不管该文件是否上传成功都会触发该事件,所以我们在这里再调用一次startUpload方法是合适的,这样SWFUpload组件在前一个文件上传完成后就会自动开始下一个文件的上传。

    view plaincopy      
    1. use_query_string: true,  
    2. post_params: {  
    3.     param1: 'Hello',  
    4.     param2: '你好'  
    5. }  

    但是如果参数值中含有中文的话,那么后台会报错,也取不到值,可以这样解决:

    在JS中先用UTF-8进行中文编码

    [javascript] view plaincopy      
    1. use_query_string: true,  
    2. post_params: {  
    3.     remark: encodeURI('中文',"utf-8")  
    4. }  

    然后在后台再转回来,在Java中就体现为

    [java] view plaincopy      
    1. URLDecoder.decode(request.getParameter("remark"), "utf-8");  

    如此便可解决中文参数传递问题。

    view plaincopy      
    1. uploadSuccess(file object, server data, received response)  

            这个事件发生后会传三个参数到我们定义的监听函数中,第一个是上传完成的文件对象(关于文件对象可参看SWFUpload的文档,其实就是一个JS对象,包含一些重要的文件信息),第二个其实是服务器返回的数据,如果后台用的直接跳转,那么这里的server data就会是跳转页面的HTML结构,document.write()将其输出,也算是完成跳转了吧!如果你仅仅是向前台输出个字符串,并没有跳转,那么这里就应该是你输出的字符串,在这里就可以用location.href=“来实现跳转;第三个参数是Boolean类型,表示是否接收到服务器传回的数据。

            需要注意的是该事件在每个文件上传成功均会触发,如果同时在上传多个文件,那么第一个文件上传完成后页面就直接跳转了,后台的文件也就得不到机会上传了,所以这里需要判断一下队列里是否还有没有上传完成的文件,如果没有了再跳转,就可以了,至于如何判断队列里是否还有未上传的文件,这里用到了SWFUpload的另一个对象Stats。该对象提供了上传队列的状态信息,访问实例的getStats方法可获取此对象,该对象中有一些属性,其中有一个files_queued属性可以表示是否还有未上传的文件,如果该属性为0则表示全部上传,可以这样做

    [javascript] view plaincopy      
    1. if(this.getStats().files_queued = 0) {  
    2.     // jump code  
    3. }  

    PHP and ASP.net

            也就是说非IE浏览器下Flash Player插件发送的也是IE浏览器当前页面的cookie,并且Session是靠Cookie中保存的SessionId实现的,因此后台处理程序另新建了Session,程序也就出现了上述错误。

            找到的解决办法是手动将SessionID传到后台服务端。

            在上传路径URL里加上jsessionid变量即可,如下:

    [javascript] view plaincopy      
    1. upload_url: contextPath + '/web/user-upload!upload.action;jsessionid=<%=request.getSession().getId() %>'  

             这样就可以解决问题了,有人还说可以使用SWFUpload组件的一个插件:swfupload.cookies.js,但我没有弄好,看它的源码是分析浏览器的cookie然后将它们拼接到post_params配置项中,我手动拼上jsessionid也是不起作用,不知道是不是自己没弄对,反正是这个插件我没有成功使用。

    原文:http://blog.csdn.net/zhyh1986/article/details/7926166#