详解Ext + Struts2 文件上传
作者:战斗机 http://zpball.iteye.com/blog/1534503
other : http://www.360doc.com/content/11/1228/23/2812544_175707427.shtml
前阵子项目里面需要实现文件上传的功能,前后换了包括我在内的三个人来搞,搞了很长时间才搞好,因为一些小问题耽误了很长时间。趁着手热,写下来贴出来给大家分享,希望查到本文的人可以少走一些弯路。
项目中用到的技术是ExtJs3.3和SSH等,与文件上传相关的主要是Ext和Struts2。
最开始是一同事搞的,使用了Ext示例包里的FileUploadField,需要在页面中引用两个文件:/ext/examples/ux/fileuploadfield/css/fileuploadfield.css和/ext/examples/ux/fileuploadfield/FileUploadField.js。
废话少说,直接上代码,中间各种曲折的过程都省略。文件上传的弹框是一个Window,里面内嵌了一个FormPanel。
然后,在后台处理的action里面,无论你返回什么值都可以,SUCCESS、NONE、甚至自定义的一个字符串都行。注意这里所说的“返回”是指action里面的处理函数自身的返回,我相信你懂的……。只要你从action向前台返回的json数据中,不包含{success:false}这个值,success:true也可以省略不写,Ext都会认为文件上传操作成功,即会执行submit方法中success属性定义的回调函数。
struts配置文件中的部分内容如下:
注意ContentType要设定为text/html类型,否则浏览器不能正确处理,可能会将服务器端的响应作为文件提示用户进行下载。要想给前台正确响应数据,可以有多种方法(笔者目前发现了三种):
1、可以直接指定action返回json格式的数据,如上面这样result name="success"的返回类型所示。如果采用这种方式,最好在相应的Action类里包含三个属性:success,filename和msg。其中success的类型可以是String,也可以是boolean,另外两个属性是String类型的。如果Action的方法返回的时候,success的值是false,就可以表示文件上传失败,然后msg属性就可以指明文件上传失败的原因;filename可以用来保存文件的名字。
注意无论哪种方式,contentType的类型一定要设置为text/html,否则浏览器可能会把服务器端的响应当成文件,提示让你下载。
2、可以在一个文本文件里包含一个json格式的字符串,用作服务器端对客户端的反应。如上面result name="test1"时的配置所示。其中指定了Action的返回值为”test1”时,向客户端返回test.jsp页面里包含的内容,而这个页面里面可能只写了一句:{success:true},就相当于服务器端向客户端返回了这个json响应。
不过这种方法可能会比较死,不如第一种方法灵活方便,尤其是需要考虑文件上传(或上传后的解析)失败的情况,错误信息不太方便返回。
3、可以直接在Action里面,使用HttpServletResponse对客户端进行响应。例如专门写一个写客户端响应的函数如下:
这样,把要返回给客户端的json内容按照类似{success:true}的标准格式传给sendMsg函数即可。笔者建议在此处调用Writer的flush和notify函数,sendMsg函数发送的数据会立即发送到客户端。所以,你的Action的返回值就可以随心所欲了,即使是INPUT或者ERROR也没有关系。跟第一种方法一样灵活,不过还是不如第一种方法方便。
是不是很简单?其实第一个同事搞的时候,就差不多搞好了,主要的问题应该就是他在success回调函数里使用了例子程序中的一个自定义函数msg,而没有把msg函数定义的代码引入进来,结果是即使文件上传成功,执行到success回调函数中的时候,也必然会报错,不能正常关闭上传对话框。
另外的原因是,项目中有一个判断“重复登录”的全局函数,使用了Ajax请求从服务器得到的response,用到了response.getResponseHeader()函数做判断。文件上传结束之后,这里总是报错,说是response中没有getResponseHeader()这个方法。最开始我搞的时候,没什么经验,没有太在意这个问题,以为这个跟文件上传无关,就把精力全部集中在解决文件上传本身之上了,结果耽误了很多时间。后来仔细查看API文档的时候,在FormPanel的基类、类Ext.form.BasicForm的描述中,发现了这样一句:
The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing aresponseText property in order to conform to the requirements of event handlers and callbacks.
即Ext使用FormPanel进行的文件上传时,Ajax请求返回的是一个伪响应(fake XMLHttpRequest),不能当做一般的response来使用。顿时茅塞顿开,修改了该全局函数,使得对于文件上传的response,跳过调用getResponseHeader()函数的判断。问题终于搞定!
血的教训!原来所有的Javascript文件中,只要有错误语法错误发生,就可能影响整个页面的所有js的执行啊!不过,在这整个过程中,对稍微Ext有了一点深入的认识,不再是刚进公司的时候,拿来个任务,直接参照着原有的代码进行简单增加和修改了。还了解了什么事Ajax,也学会了使用调试工具等等。
Ext还没有捂热,本人就听从项目需要,转arcgis了,可能还得学Flex等,学校期末也临近,要学的东西太多了……不废话咯~
后记:有人问,如果项目中每个ajax请求都会执行某方法,其中用到了getResponseHeader(),那么如何跳过文件上传的Ajax请求的判断呢?例如,我们的项目中Ajax请求返回的数据都是JSON格式的,我使用的方法是,判断如果response是JSON数据,就解析里面有没有我们所上传的文件(找名字即可),这样就可以判断这个ajax请求是不是用于文件上传的,然后决定是否跳过:
项目中用到的技术是ExtJs3.3和SSH等,与文件上传相关的主要是Ext和Struts2。
最开始是一同事搞的,使用了Ext示例包里的FileUploadField,需要在页面中引用两个文件:/ext/examples/ux/fileuploadfield/css/fileuploadfield.css和/ext/examples/ux/fileuploadfield/FileUploadField.js。
废话少说,直接上代码,中间各种曲折的过程都省略。文件上传的弹框是一个Window,里面内嵌了一个FormPanel。
- var fp = new Ext.FormPanel( {
- renderTo : Ext.getBody(),
- fileUpload : true,
- width : 523,
- frame : true,
- autoHeight : true,
- bodyStyle : 'padding: 10px 10px 0 10px;',
- labelWidth : 50,
- defaults : {
- anchor : '95%',
- allowBlank : false,
- msgTarget : 'side'
- },
- items : [
- new Ext.form.FileUploadField( {
- buttonText: '浏览...',
- emptyText: '请选择一个xls文件',
- name : 'xlsFile',
- width : 500,
- buttonCfg: {
- 40,
- iconCls: 'upload-icon'
- }
- })
- ],
- buttons : [ {
- text : '上传',
- handler : function() {
- if (fp.getForm().isValid()) {
- fp.getForm().submit( {
- method : 'post',
- url :'uploadEmployee.action',// 后台处理的action
- waitMsg : '操作处理中,请稍等...',
- waitTitle:'提示',
- success :function(fp,action){
- Ext.Msg.alert('Success', 'The value of success is: "'+action.result.success+'" on the server');
- excelWindow.destroy();
- },
- failure : function(fp, action) {
- var msg = action.response.responseText;
- var obj = Ext.decode( msg );
- Ext.Msg.alert("提示", "Sorry,操作失败,原因:" + obj.message);
- excelWindow.destroy();
- }
- });
- }
- }
- }]
- });
- var excelWindow = new Ext.Window( {
- renderTo : Ext.getBody(),
- closeAction : "hide",
- plain : true,
- width : 540,
- title : "批量导入员工信息",
- modal : true,
- items:[fp]
- });
- excelWindow.show();
然后,在后台处理的action里面,无论你返回什么值都可以,SUCCESS、NONE、甚至自定义的一个字符串都行。注意这里所说的“返回”是指action里面的处理函数自身的返回,我相信你懂的……。只要你从action向前台返回的json数据中,不包含{success:false}这个值,success:true也可以省略不写,Ext都会认为文件上传操作成功,即会执行submit方法中success属性定义的回调函数。
struts配置文件中的部分内容如下:
- <action name="uploadEmployee" class="uploadEmployeeService">
- <interceptor-ref name="fileUpload">
- <param name="maximumSize">10485760</param>
- <param name="allowedTypes">application/vnd.ms-excel</param>
- </interceptor-ref>
- <interceptor-ref name="defaultStack"/>
- <result name="success" type="json">
- <param name="contentType">text/html</param>
- </result>
- <result name="test1">/employee/jsp/test.jsp
- <param name="contentType">text/html</param>
- </result>
- </action>
注意ContentType要设定为text/html类型,否则浏览器不能正确处理,可能会将服务器端的响应作为文件提示用户进行下载。要想给前台正确响应数据,可以有多种方法(笔者目前发现了三种):
1、可以直接指定action返回json格式的数据,如上面这样result name="success"的返回类型所示。如果采用这种方式,最好在相应的Action类里包含三个属性:success,filename和msg。其中success的类型可以是String,也可以是boolean,另外两个属性是String类型的。如果Action的方法返回的时候,success的值是false,就可以表示文件上传失败,然后msg属性就可以指明文件上传失败的原因;filename可以用来保存文件的名字。
注意无论哪种方式,contentType的类型一定要设置为text/html,否则浏览器可能会把服务器端的响应当成文件,提示让你下载。
2、可以在一个文本文件里包含一个json格式的字符串,用作服务器端对客户端的反应。如上面result name="test1"时的配置所示。其中指定了Action的返回值为”test1”时,向客户端返回test.jsp页面里包含的内容,而这个页面里面可能只写了一句:{success:true},就相当于服务器端向客户端返回了这个json响应。
不过这种方法可能会比较死,不如第一种方法灵活方便,尤其是需要考虑文件上传(或上传后的解析)失败的情况,错误信息不太方便返回。
3、可以直接在Action里面,使用HttpServletResponse对客户端进行响应。例如专门写一个写客户端响应的函数如下:
- privatevoid sendMsg(String content) throws IOException{
- HttpServletResponse response = ServletActionContext.getResponse();
- response.setCharacterEncoding("utf-8");
- response.setContentType("text/html");
- response.getWriter().write(content);
- response.getWriter().flush();
- response.getWriter().notify();
- }
这样,把要返回给客户端的json内容按照类似{success:true}的标准格式传给sendMsg函数即可。笔者建议在此处调用Writer的flush和notify函数,sendMsg函数发送的数据会立即发送到客户端。所以,你的Action的返回值就可以随心所欲了,即使是INPUT或者ERROR也没有关系。跟第一种方法一样灵活,不过还是不如第一种方法方便。
是不是很简单?其实第一个同事搞的时候,就差不多搞好了,主要的问题应该就是他在success回调函数里使用了例子程序中的一个自定义函数msg,而没有把msg函数定义的代码引入进来,结果是即使文件上传成功,执行到success回调函数中的时候,也必然会报错,不能正常关闭上传对话框。
另外的原因是,项目中有一个判断“重复登录”的全局函数,使用了Ajax请求从服务器得到的response,用到了response.getResponseHeader()函数做判断。文件上传结束之后,这里总是报错,说是response中没有getResponseHeader()这个方法。最开始我搞的时候,没什么经验,没有太在意这个问题,以为这个跟文件上传无关,就把精力全部集中在解决文件上传本身之上了,结果耽误了很多时间。后来仔细查看API文档的时候,在FormPanel的基类、类Ext.form.BasicForm的描述中,发现了这样一句:
The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing aresponseText property in order to conform to the requirements of event handlers and callbacks.
即Ext使用FormPanel进行的文件上传时,Ajax请求返回的是一个伪响应(fake XMLHttpRequest),不能当做一般的response来使用。顿时茅塞顿开,修改了该全局函数,使得对于文件上传的response,跳过调用getResponseHeader()函数的判断。问题终于搞定!
血的教训!原来所有的Javascript文件中,只要有错误语法错误发生,就可能影响整个页面的所有js的执行啊!不过,在这整个过程中,对稍微Ext有了一点深入的认识,不再是刚进公司的时候,拿来个任务,直接参照着原有的代码进行简单增加和修改了。还了解了什么事Ajax,也学会了使用调试工具等等。
Ext还没有捂热,本人就听从项目需要,转arcgis了,可能还得学Flex等,学校期末也临近,要学的东西太多了……不废话咯~
后记:有人问,如果项目中每个ajax请求都会执行某方法,其中用到了getResponseHeader(),那么如何跳过文件上传的Ajax请求的判断呢?例如,我们的项目中Ajax请求返回的数据都是JSON格式的,我使用的方法是,判断如果response是JSON数据,就解析里面有没有我们所上传的文件(找名字即可),这样就可以判断这个ajax请求是不是用于文件上传的,然后决定是否跳过:
- var t = response.responseText;
- if(t.charAt(0)=="{"){
- if( Ext.decode(t).xlsFileFileName ){
- return;
- }
- }