最近工程里总是遇到这种表单里面带上传图片的,这里最初的做法是提供一个“file”标签,选择图片后也没有任何提示,最后点击提交按钮保存到后台,这样做有个十分明显的缺点,表单提交需要刷新界面,假如我框架里面默认显示的是界面A,而在界面B里做上传图片的操作,一旦进行这项操作会跳转到A界面这种大大降低用户体验。所以这里必须使用无刷新的机制实现,但这里又有个难题,我们知道ajax是无法实现文件上传的,从网上查找资料说使用iframe这种方法,但是笔者试过总觉得效果不能达到自己想实现的那种,于是决定用心的去实现这里文章题目所说的机制。
这里的想法是将上传图片的工作和提交表单的工作分离出来,用户点击上传图片后先把这个图片流传到后台保存下来,点击提交按钮的时候只需要拿到图片的名字,那么在下次加载的时候就可以读到这个图片了。
上传图片的方法或者插件有很多,笔者这里使用了ext插件实现文件上传,因为这个插件的上传功能比较强大,格式检测,大小检测错误提示以及上传的动态效果都十分优秀。
首先给个上传的前台界面代码如下
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="/struts-tags" prefix="s"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <link href="${pageContext.request.contextPath}/style/ext-all.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="${pageContext.request.contextPath}/js/ext-base.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/ext-core.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/ext-all.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/user-uploadpic.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/Ext.ux.UploadDialog.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/ext-lang-zh_CN.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/ru.utf-8_zh.js"></script> </head> <body> <table> <tr> <td rowspan="7" style=" 200px;"> <div class="centre7_fixed" > <span class="centre4_font"> <img id="show_imag" src="${pageContext.request.contextPath}/photo/${perinf.id}/${perinf.photo}" style=" 145px;height: 180px" /> </span> </div> <div class="centre8_fixed"> <div class="button_uploading"> <div class="whitebutton1"> <div class="whitebutton2"> <div class="whitebutton3"> <span> </span> </div> <div class="whitebutton4" id="upload_image_btn">上传</div> </div> </div> </div> </div> </td> </tr> </table> </body> </html>
这里剪掉了其他的内容只保留了上传文件的这个东西,当然表单的其他属性也可以加上,因为它们是分开的所以不会相互影响,需要引入的文件直接找ext下载里面全都有,需要自己写的其实只有“user-uploadpic.js”下面对这个js代码作说明。“upload_image_btn” 对应的是你要点击的控件id,控件可以使任意如div,button,text等等,只要可以监听单击的空间都可以, 这里需要自己改的是url 和一个回调函数onUploadSuccess里面的东西 url对应的是action名字,其配置应该在你的struts.xml里,当然如果想传递参数可以在后面使用?参数=的形式,这个url对应后台javabean待会再讲,回调函数的参数resp_data为后台传递过来的你上传的图片的路径名称,这里的路径一般是目录加图片名字,适合放到你前台显示图片的img src里面,但如果你插入数据库中的只是图片的名字则还需要分割处理后赋值给前台的一个隐藏域,等前台提交整个表单的时候保存到数据里面,再次加载的时候就可以看到。
1 Ext.onReady(function(){ 2 3 //创建上传组件 4 $('#upload_image_btn').click(function() 5 { 6 dialog = new Ext.ux.UploadDialog.Dialog({ 7 title: '上传头像' , 8 url:'uploadpic' , 9 post_var_name:'uploadFiles', 10 width : 450, 11 height : 300, 12 minWidth : 450, 13 minHeight : 300, 14 draggable : true , 15 resizable : true , 16 //autoCreate: true, 17 constraintoviewport: true , 18 permitted_extensions:[ 'JPG' , 'jpg' , 'jpeg' , 'JPEG' , 'GIF' , 'gif' , 'png' , 'PNG' ], 19 modal: true , 20 reset_on_hide: false , 21 allow_close_on_upload: false , //关闭上传窗口是否仍然上传文件 22 upload_autostart: true //是否自动上传文件 23 // upload_autostart: false 24 }); 25 26 dialog.show(); //'show-button' 27 dialog.on( 'uploadsuccess' , onUploadSuccess); 28 dialog.on( 'uploadfailed' , onUploadFailed); 29 dialog.on( 'uploaderror' , onUploadFailed); 31 dialog.on( 'uploadcomplete' , onUploadComplete); 32 }); 33 34 //文件上传成功后的回调函数 35 onUploadSuccess = function(dialog, filename, resp_data, record){ 36 39 system_image_path = resp_data.data; 40 $("#perpic").val(system_image_path.substring(system_image_path.lastIndexOf("/")+1,system_image_path.length));
//仅将图片名复制给前台隐藏域 41 $("#show_imag").attr({src:system_image_path});//图片更新显示代码 42 43 } 44 45 //文件上传失败后的回调函数 46 onUploadFailed = function(dialog, filename, resp_data, record){ 47 bbs.ErrorAlert(resp_data.data); 48 } 49 50 //文件上传完成后的回调函数 51 onUploadComplete = function(dialog){ 52 dialog.hide(); 53 } 54 55 })
接下来是后台代码UploadAction.java
1 package com.hrbourse.application.action; 2 import java.io.File; 3 import java.io.PrintWriter; 4 import java.util.Date; 5 import java.util.Vector; 6 import org.apache.commons.io.FileUtils; 7 import org.apache.struts2.ServletActionContext; 8 import org.apache.struts2.json.JSONException; 9 import org.apache.struts2.json.JSONUtil; 10 11 import com.hrbourse.application.model.User; 12 13 /** 14 * UploadAction 15 * 16 * @author 17 * 18 */ 19 public class UploadAction extends BaseAction{ 20 21 private static final long serialVersionUID = 4795147622620740907L; 22 23 /** 24 * UserService引用 25 */ 26 27 private File[] uploadFiles; 28 29 private String[] uploadFileNames; 30 31 private String[] uploadContentTypes; 32 private Vector<String> errMessage; 33 public File[] getUploadFiles() { 34 return uploadFiles; 35 } 36 37 public void setUploadFiles(File[] uploadFiles) { 38 this.uploadFiles = uploadFiles; 39 } 40 41 public String[] getUploadFileNames() { 42 return uploadFileNames; 43 } 44 45 public void setUploadFileNames(String[] uploadFileNames) { 46 this.uploadFileNames = uploadFileNames; 47 } 48 49 public String[] getUploadContentTypes() { 50 return uploadContentTypes; 51 } 52 53 public void setUploadContentTypes(String[] uploadContentTypes) { 54 this.uploadContentTypes = uploadContentTypes; 55 } 56 57 /** 58 * 上传照片 59 */ 60 public void uploadImage() { 61 User tempVO = (User) session.get("user"); 62 if(tempVO!=null){ 63 // 图片上传后存放的路径目录 64 String fileBasePath = ServletActionContext.getServletContext().getRealPath("/photo"); 65 String destpath=fileBasePath+"/"+tempVO.getId()+"/"; 66 File images = new File(destpath); 67 if(!images.exists()){ 68 images.mkdirs(); 69 } 70 File files[] = images.listFiles(); 71 for (int i = 0; i < files.length; i++) { 72 if (files[i].isDirectory()){ 73 continue; 74 } 75 else 76 files[i].delete(); 77 } 78 for(int i = 0,size = uploadFiles.length;i<size;i++){ 79 File file = uploadFiles[i]; 80 if(file.length()>1024000){ 81 writeJson("上传失败:文件太大,文件大小不能超过1M", false); 82 }else{ 83 try { 84 String imageName = tempVO.getId()+new Date().getTime()+ ".png";//图片名字 85 // 86 FileUtils.copyFile(file, new File(destpath,imageName));//将文件复制到所保存的目录下 98 writeJson("photo/"+tempVO.getId()+"/"+imageName,true);// 99 100 } catch (Exception e) { 101 writeJson("上传失败:系统发生错误,请与管理员联系", false); 102 e.printStackTrace(); 103 } 104 } 105 } 106 }else{ 107 writeJson("上传失败", false); 108 } 109 isInvalid(); 110 } 111 112 public void writeJson(Object obj, boolean isSuccess) { 113 try { 114 response.setContentType("text/html;charset=utf-8"); 115 PrintWriter writer = response.getWriter(); 116 if (isSuccess) { 117 writer.write("{success:true,data:"); 118 } else { 119 writer.write("{success:false,data:"); 120 } 121 writer.write(JSONUtil.serialize(obj)); 122 writer.write("}"); 123 writer.flush(); 124 writer.close(); 125 } catch (java.io.IOException exc) { 126 exc.printStackTrace(); 127 } catch (JSONException e) { 128 e.printStackTrace(); 129 } 130 } 131 132 public boolean isInvalid() { 133 if (errMessage != null && errMessage.size() > 0) { 134 writeError(); 135 return true; 136 } 137 return false; 138 } 139 public void writeError() { 140 try { 141 response.setContentType("text/html;charset=utf-8"); 142 PrintWriter writer = response.getWriter(); 143 writer.write("{success:false,msg:'"); 144 for (String msg : errMessage) { 145 writer.write(msg); 146 writer.write("<br/>"); 147 } 148 errMessage.clear(); 149 writer.write("'}"); 150 writer.flush(); 151 writer.close(); 152 } catch (java.io.IOException exc) { 153 exc.printStackTrace(); 154 } 155 } 156 }
这里是url “upload”所对应的后台的java类首先看属性uploadFiles,该值是post_var_name:'uploadFiles',赋值来的就是把你选中的图片传给这个值,这里写成数组是支持多文件上传的功能,但是我这里只需要一张图片轮番替换而已,所以70-77行是文件路径下的文件清除工作,每次插入的时候先清除再插入这样只能保留最新的一样核心函数是86行,这个函数把我上传的临时文件赋值到我所给定的路径,这里笔者也有番困惑就是传递至后台的文件都是.temp的临时文件,而非源文件格式这样的话必须自己给定文件格式这样做不合理,还希望有大神指点迷津。最后注意下writeJson这个方法,这个方法是创建PrintWriter对象写会响应,这里要注意所写内容必须经过JSONUtil.serialize(obj) json序列化,否则前台会报错。最后给上封装的BaseAction类的代码
1 package com.hrbourse.application.action; 2 3 import java.util.Map; 4 import javax.servlet.ServletContext; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import org.apache.struts2.interceptor.ServletRequestAware; 8 import org.apache.struts2.interceptor.ServletResponseAware; 9 import org.apache.struts2.interceptor.SessionAware; 10 import org.apache.struts2.util.ServletContextAware; 11 import com.opensymphony.xwork2.ActionSupport; 12 13 public class BaseAction extends ActionSupport 14 implements ServletRequestAware, ServletContextAware, ServletResponseAware, SessionAware 15 { 16 17 public BaseAction() 18 { 19 } 20 21 public void setServletRequest(HttpServletRequest request) 22 { 23 this.request = request; 24 } 25 26 public void setServletContext(ServletContext application) 27 { 28 this.application = application; 29 } 30 31 public void setServletResponse(HttpServletResponse response) 32 { 33 this.response = response; 34 } 35 36 public void setSession(Map session) 37 { 38 this.session = session; 39 } 40 41 private static final long serialVersionUID = 1L; 42 public HttpServletRequest request; 43 public HttpServletResponse response; 44 public Map session; 45 public ServletContext application; 46 }