整理了一下jsp的上传下载,由客户端到服务端,包括一些常规的业务
一客户端
先看最基本的情况
1 在表单设置multipart/form-data,通过提交键把数据和附件一次性提交的。服务器更多的是对应这个例子。
但有以下问题
一般的Ajax无法把附件数据发过去
附件往往涉及比较多问题,比如上传需要时间(要有个进度),上传也有可能不成功等,上传需要校验的东西就比较多。
2于是就有把上传分开来自己执行的。上传项在浏览文件后就开始上传,其他表单数据在提交表单时再提交。
更详细地说是:上传在表单中有自成一体的界面,浏览后点击上传开始上传(也可以是其他形式),以ajax方式发送。发送在界面显示上传状态,并把file类型的控件的值置空。在提交表单时,仅提交其他数据。
3在表单一次性的提交的,但是用了ajax。这种我没做通,但想想这种在现实中很少需要就没做了。
之后就说说一种可行的实现,使用了jquery file Upload。
Jquery file Upload(鉴于官方文档不是一般的乱,整理了一下关键的页面):
Hello world:http://bbs.9ria.com/thread-245293-1-1.html
官方文档:https://github.com/blueimp/jQuery-File-Upload/wiki
Option:https://github.com/blueimp/jQuery-File-Upload/wiki/Options
关于使用思路:
首先我比较关注上传的js部分,所以没去了解界面的部分。jquery file Upload应该是界面做得最好的。就上传插件而言,基本应该是配置,之后触发文件上传(怎么触发就是掌握不同插件的关键)
jquery file Upload在这一点比较奇葩(不关注jquery file Upload可以跳过),它是默认点击input就自动上传的。它可以把上传触发绑定到一个事件上,通过add这个option来配置。但这其实是有问题的,比如表单有两个上传域,希望通过某按键的click事件触发上传,click事件的处理就会被调用两次。结论很简单,用jquery file Upload,不要把多个文件的上传绑在一起,让他们各自完成。
jquery file Upload每次只会像服务器发一个文件,开始我也非常疑惑。但后来发现其他插件也是这样的。还是上面的一条,让每个上传各自完成。
其他好用的上传插件还有
Plupload:功能上最强,有校验,上传多个,拖拽上传等功能。(file类型域不是自己写的,通过指定一个button,由它的点击事件生成,通过调函数来开始上传)
文档:http://www.cnblogs.com/2050/p/3913184.html#plupload_doc4
Demo:http://chaping.github.io/plupload/demo/index.html
二服务端
客户端怎么做在服务端的接口都是一样的。都是面对着这么一个表单,表单里有一些字段,还有一个到多个的上传附件。
纵观附件的管理,业务流程是这样的
上传要处理的问题:
附件重名处理
上传类型检查、大小等限制
上传文件的位置规划
文件夹及安全性
附件数据库
编码问题
上传方案
1最基本,用request的inputstream读写
2用组件库,common-fileupload(例子里用了这个),这些组件库对request进行封装。
3用struct提供的上传功能
例子中的处理过程
用common-uploadfile,需要以下包。自己写了一个工具类,改进自
http://lohasle.iteye.com/blog/1604428
上传文件夹放在网站根目录,可以用工具类把所有附件都传到一个目录,也可以按表单filedname配置上传的位置。
上传的文件会以一个随机的名字保存,避免了附件重名。
用MineType检查文件类型,查询minetype可点击
http://www.cnblogs.com/newcj/archive/2011/08/10/2134305.html
先把文件传到一个临时文件夹,用fileItem对象读取文件信息,再重命名为随机名,传到指定文件夹。
fileUpload几个要注意的问题
fileUpload关键就是使用FileItem类,可以查文档
文件名,内容乱码:http://zsw4522006.iteye.com/blog/1470949
首先文件在上传和下载过程中是按字节传的,编码由文件本身保存所以不会乱码。
但文件名会有乱码,fileItem的getString也会有乱码,则主要是fileUpload默认的和现实的编码不一样。
common-fileupload对request进行封装,一旦我们按流的方式打开http请求,就不能用getParameter方法,而要用common-fileupload提供的接口访问表单
下载、删除和使用
当把“上传文件的位置规划”和“数据库存储”解决,后面的问题就差不多了。毕竟文件已经在那里了,它的信息也详细记录在数据库。实现详见代码
最后上代码
无ajax的附件表单,UploadAddInput.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 11 <title>上传文件</title> 12 </head> 13 14 <body> 15 16 <form name="upform" action="UploadAdd.jsp" method="POST" enctype="multipart/form-data"> 17 <input type ="text" name="name"/><br/> 18 <input type ="file" name="file1"/><br/> 19 <input type ="file" name="file2"/><br/> 20 <input type="submit" value="Submit" /><br/> 21 <input type="reset" /> 22 </form> 23 24 </body> 25 </html>
Upload实体类,Upload.java
1 package com.xcat.upload; 2 3 import java.util.Date; 4 5 public class Upload { 6 private int id; 7 private String fileName; 8 private String description; 9 private String loc; 10 private String mineType; 11 //单位KB; 12 private int fileSize; 13 private Date upTime; 14 private int owner; 15 public int getId() { 16 return id; 17 } 18 public void setId(int id) { 19 this.id = id; 20 } 21 public String getFileName() { 22 return fileName; 23 } 24 public void setFileName(String fileName) { 25 this.fileName = fileName; 26 } 27 public String getDescription() { 28 return description; 29 } 30 public void setDescription(String description) { 31 this.description = description; 32 } 33 public String getLoc() { 34 return loc; 35 } 36 public void setLoc(String loc) { 37 this.loc = loc; 38 } 39 public String getMineType() { 40 return mineType; 41 } 42 public void setMineType(String mineType) { 43 this.mineType = mineType; 44 } 45 public int getFileSize() { 46 return fileSize; 47 } 48 public void setFileSize(int fileSize) { 49 this.fileSize = fileSize; 50 } 51 public Date getUpTime() { 52 return upTime; 53 } 54 public void setUpTime(Date upTime) { 55 this.upTime = upTime; 56 } 57 public int getOwner() { 58 return owner; 59 } 60 public void setOwner(int owner) { 61 this.owner = owner; 62 } 63 64 }
上传工具类,参考http://lohasle.iteye.com/blog/1604428,UploadUtil.java
1 package com.xcat.upload; 2 3 import java.io.File; 4 import java.util.Date; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Random; 9 10 import org.apache.commons.fileupload.servlet.ServletFileUpload; 11 import org.apache.commons.fileupload.FileItem; 12 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 13 import javax.servlet.http.HttpServletRequest; 14 15 public class UploadUtil { 16 public static class PosConfig{ 17 private String defaultPos; 18 private Map<String,String> pos =new HashMap<String,String>(); 19 public String getDefaultPos() { 20 return defaultPos; 21 } 22 public void setDefaultPos(String defaultPos) { 23 this.defaultPos = defaultPos; 24 } 25 public Map<String, String> getPos() { 26 return pos; 27 } 28 public void setPos(Map<String, String> pos) { 29 this.pos = pos; 30 } 31 32 } 33 /* 34 * 常量 35 */ 36 //一兆的大小 37 public static final long TRILLION = 1024 * 1024; 38 //文件限制性的大小(默认的5M) 39 public static final long DEFAULT_SIZE = TRILLION * 5; 40 //试探随机文件名的最大次数 41 public static final int RANDOM_NAME_TIMES =2000; 42 43 /* 44 * 参数 45 */ 46 //限制的文件类型 47 private String[] allowMineType; 48 // 限制的文件大小,默认5M,-1就是无限制大小 49 private long fileMaxSize = DEFAULT_SIZE; 50 // 设置缓冲区文件夹 51 private String cachePathString; 52 // 设置缓冲区大小,默认5M 53 private long cacheSize = DEFAULT_SIZE; 54 // 客户端文件系统字符编码 55 private String clientCharset = "utf-8"; 56 // request表单的字符编码 57 private String formCharset = "utf-8"; 58 59 //上传位置配置 60 private PosConfig posConfig; 61 62 63 /* 64 * 变量 65 */ 66 //request 67 private HttpServletRequest request; 68 // 文件上传处理类 69 private ServletFileUpload sfu; 70 // 磁盘工厂 71 private DiskFileItemFactory factory = new DiskFileItemFactory(); 72 // 表单域的值 73 private Map<String, String> formMap = new HashMap<String, String>(); 74 // 上传文件信息 75 private Map<String, Upload> fileMap = new HashMap<String, Upload>(); 76 //上传的附件是否传到多个位置 77 private boolean multiPos = false; 78 79 public UploadUtil() { 80 } 81 82 public Map<String, String> getFormMap() { 83 return formMap; 84 } 85 86 public void setFormMap(Map<String, String> formMap) { 87 this.formMap = formMap; 88 } 89 90 public String getCachePathString() { 91 return cachePathString; 92 } 93 94 public void setCachePathString(String cachePathString) throws Exception { 95 this.cachePathString = cachePathString; 96 } 97 98 public long getCacheSize() { 99 return cacheSize; 100 } 101 102 public void setCacheSize(long cacheSize) { 103 this.cacheSize = cacheSize; 104 } 105 106 107 public long getFileSize() { 108 return fileMaxSize; 109 } 110 111 public void setFileSize(long fileSize) { 112 this.fileMaxSize = fileSize; 113 } 114 115 public String[] getAllowMineType() { 116 return allowMineType; 117 } 118 119 public void setAllowMineType(String[] allowMineType) { 120 this.allowMineType = allowMineType; 121 } 122 123 public HttpServletRequest getRequest() { 124 return request; 125 } 126 127 public void setRequest(HttpServletRequest request) { 128 this.request = request; 129 } 130 131 public PosConfig getPosConfig() { 132 return posConfig; 133 } 134 135 public void setPosConfig(PosConfig posConfig) { 136 this.posConfig = posConfig; 137 } 138 139 public String getClientCharset() { 140 return clientCharset; 141 } 142 143 public void setClientCharset(String clientCharset) { 144 this.clientCharset = clientCharset; 145 } 146 147 public String getFormCharset() { 148 return formCharset; 149 } 150 151 public void setFormCharset(String formCharset) { 152 this.formCharset = formCharset; 153 } 154 155 public Map<String, Upload> getFileMap() { 156 return fileMap; 157 } 158 159 public void setFileMap(Map<String, Upload> fileMap) { 160 this.fileMap = fileMap; 161 } 162 163 public ServletFileUpload getServletFileUpload() { 164 if (sfu == null) { 165 return sfu = new ServletFileUpload(factory); 166 } else { 167 return sfu; 168 } 169 } 170 171 /** 172 * // 得到用户ip地址 173 * 174 * 175 * @return ip地址 176 */ 177 public String getIpAddress() { 178 String ip = request.getHeader("x-forwarded-for"); 179 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 180 ip = request.getHeader("Proxy-Client-IP"); 181 } 182 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 183 ip = request.getHeader("WL-Proxy-Client-IP"); 184 } 185 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 186 ip = request.getHeader("HTTP_CLIENT_IP"); 187 } 188 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 189 ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 190 } 191 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 192 ip = request.getRemoteAddr(); 193 } 194 return ip; 195 } 196 /** 197 * // 得到一个随机文件名 198 * 199 * 200 * @return 文件名 201 */ 202 public String getRandomFileName(){ 203 String str=""; 204 205 String[] ips=getIpAddress().split("."); 206 int ipv=0; 207 for(int i=0;i<ips.length;i++) ipv+= Integer.parseInt(ips[i]) << (8*i); 208 209 str+=String.format("%010d", ipv); 210 str+=new Date().getTime(); 211 Random r = new Random(); 212 str+=String.format("%03d", r.nextInt(999)); 213 return str; 214 } 215 216 /** 217 * // 保证目录存在 218 * 219 * 220 * @return 221 * @throws Exception 222 */ 223 public void ensureDir(String dir) throws Exception{ 224 File file =new File(dir); 225 //如果文件夹不存在则创建 226 if(!file.exists() && !file.isDirectory()) 227 if(!file.mkdirs()) throw new Exception("创建文件夹"+dir+"失败"); 228 } 229 230 /** 231 * // 得到文件类型 232 * 233 * @param file 234 * @return 文件后缀 235 */ 236 private String getFileExt(File file) { 237 return getFileExt(file.getName()); 238 } 239 240 /** 241 * // 得到文件类型 242 * 243 * @param fileName 244 * @return 文件后缀 245 */ 246 private String getFileExt(String fileName) { 247 return fileName.substring(fileName.lastIndexOf(".") + 1); 248 } 249 250 /* 251 * //根据路径和扩展名,在改路径下获得一个可用的文件名 252 * @param dir:路径,ext:扩展名 253 * @return 文件对象 254 * 255 */ 256 public File getRandomFile(String dir,String ext) throws Exception{ 257 File file; 258 for(int i=0; i< RANDOM_NAME_TIMES ;i++){ 259 file = new File(dir,getRandomFileName()+"."+ext); 260 if (!file.exists()) return file; 261 } 262 throw new Exception("上传繁忙"); 263 } 264 265 /* 266 * //判断mineType是不是在allowMineType中 267 * @param mineType 268 * @return 269 * 270 */ 271 public boolean validateMineType(String mineType) { 272 if (allowMineType == null) { 273 return true; 274 } 275 for (int i = 0, len = allowMineType.length; i < len; i++) { 276 if (allowMineType[i].equalsIgnoreCase(mineType)) { 277 return true; 278 } 279 } 280 return false; 281 } 282 283 /* 284 * //所有表单附件都上传到dir 285 * @param dir 286 * @return 287 * 288 */ 289 public void uploadFiles(String dir) throws Exception { 290 multiPos=false; 291 posConfig =new PosConfig(); 292 posConfig.defaultPos=dir; 293 uploadFiles(); 294 } 295 296 /* 297 * //按照路径配置来上传 298 * @param posConfig 299 * @return 300 * 301 */ 302 public void uploadFiles(PosConfig posConfig) throws Exception { 303 multiPos=true; 304 this.posConfig=posConfig; 305 uploadFiles(); 306 } 307 308 /** 309 * 文件上传 参数urlString是具体指定的目录 如果该对象属性值为空 310 * 将不使用缓存,无文件类型限制,上传大小默认为5M,目录规则默认为没有目录递归创建 相同文件名将覆盖源文件 311 * 此方法如文件上传错误或者文件类型不匹配将抛出异常 312 * 313 * @param request 314 * 当前请求 315 * @param urlString 316 * urlString是具体指定的目录 317 */ 318 private void uploadFiles() throws Exception { 319 if (!ServletFileUpload.isMultipartContent(request)) return; 320 ensureDir(cachePathString); 321 if(posConfig.defaultPos!=null) 322 ensureDir(posConfig.defaultPos); 323 324 ServletFileUpload sfu = getServletFileUpload(); 325 sfu.setHeaderEncoding(clientCharset); 326 sfu.setFileSizeMax(fileMaxSize); 327 if (cachePathString != null) { 328 factory.setRepository(new File(cachePathString)); 329 factory.setSizeThreshold((int) cacheSize); 330 } 331 List<FileItem> items = sfu.parseRequest(request); 332 333 for (FileItem ft : items) { 334 if (!ft.isFormField()) { 335 if(allowMineType!=null && !validateMineType(ft.getContentType())) 336 throw new Exception("上传文件类型错误"); 337 else{ 338 String dir; 339 if(multiPos && posConfig.pos.containsKey(ft.getFieldName())) { 340 dir=posConfig.pos.get(ft.getFieldName()); 341 ensureDir(dir); 342 } 343 else dir= posConfig.defaultPos; 344 File file = getRandomFile(dir,getFileExt(ft.getName())); 345 ft.write(file); 346 347 Upload upload=new Upload(); 348 upload.setFileName(ft.getName()); 349 upload.setLoc(file.getAbsolutePath()); 350 upload.setMineType(ft.getContentType()); 351 upload.setFileSize((int)(file.length()/1024)); 352 upload.setUpTime(new Date()); 353 fileMap.put(ft.getFieldName(), upload); 354 } 355 ft.delete(); 356 357 } else { 358 formMap.put(ft.getFieldName(), ft.getString(formCharset)); 359 } 360 } 361 } 362 }
上传的业务,UploadAdd.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <%@ page import="com.xcat.upload.UploadUtil" %> 3 <%@ page import="com.xcat.upload.Upload" %> 4 <%@ page import="com.xcat.upload.UploadDao" %> 5 <% 6 out.clear(); 7 UploadUtil fu = new UploadUtil(); 8 fu.setRequest(request); 9 fu.setCachePathString(request.getSession().getServletContext().getRealPath("/images/temp")); 10 fu.setAllowMineType(new String[]{"image/jpeg","image/gif","text/plain","application/msword"}); 11 try { 12 //全部上传到同一个目录 13 //fu.uploadFiles(request.getSession().getServletContext().getRealPath("/images/test")); 14 UploadUtil.PosConfig posConfig= new UploadUtil.PosConfig(); 15 posConfig.setDefaultPos(request.getSession().getServletContext().getRealPath("/images/default")); 16 posConfig.getPos().put("file1", request.getSession().getServletContext().getRealPath("/images/test1")); 17 posConfig.getPos().put("file2", request.getSession().getServletContext().getRealPath("/images/test2")); 18 fu.uploadFiles(posConfig); 19 } catch (Exception e1) { 20 e1.printStackTrace(); 21 } 22 23 24 Map<String,String> fieldMap = fu.getFormMap(); 25 Set<String> key1 = fieldMap.keySet(); 26 for(String s:key1){ 27 System.out.print("表单名称:"+s + "; "); 28 System.out.println("表单值:"+fieldMap.get(s)); 29 } 30 31 UploadDao uploadDao=new UploadDao(); 32 Map<String,Upload> fileMap = fu.getFileMap(); 33 Set<String> key2 = fileMap.keySet(); 34 for(String s:key2){ 35 36 System.out.print("表单名称:"+s + "; "); 37 System.out.println("文件名:"+fileMap.get(s).getFileName() + "; "+ 38 "文件路径:"+fileMap.get(s).getLoc() + "; "+ 39 "文件类型:"+fileMap.get(s).getMineType() + "; "); 40 41 Upload upload=fileMap.get(s); 42 43 upload.setDescription(""); 44 upload.setOwner(0); 45 uploadDao.add(upload); 46 } 47 48 49 out.print("[{\"name\":\"file.jpg\"}]"); 50 %>
下载,DownLoad.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <%@ page import="com.xcat.upload.UploadDao" %> 3 <%@ page import="com.xcat.upload.Upload" %> 4 <%@ page import="java.io.File" %> 5 <%@ page import="java.io.OutputStream" %> 6 <%@ page import="java.io.FileInputStream" %> 7 <%@ page import="java.net.URLEncoder" %> 8 <% 9 int id=Integer.parseInt(request.getParameter("id")); 10 UploadDao uploadDao=new UploadDao(); 11 Upload upload=uploadDao.loadById(id); 12 if(upload==null) System.out.print("下载文件不存在"); 13 File file=new File(upload.getLoc()); 14 15 response.reset(); 16 response.setContentType(upload.getMineType()); 17 String fileLoc = upload.getLoc(); 18 String fileName = upload.getFileName(); 19 fileName = URLEncoder.encode(fileName,"UTF-8"); 20 response.addHeader("Content-Disposition","attachment;filename=" + fileName); 21 response.setHeader("Content_length", String.valueOf(file.length())); 22 23 OutputStream outp = null; 24 FileInputStream in = null; 25 try 26 { 27 outp = response.getOutputStream(); 28 in = new FileInputStream(fileLoc); 29 30 byte[] b = new byte[1024]; 31 int i = 0; 32 33 while((i = in.read(b)) > 0) 34 { 35 outp.write(b, 0, i); 36 } 37 38 outp.flush(); 39 response.flushBuffer(); 40 out.clear(); 41 out = pageContext.pushBody(); 42 } 43 catch(Exception e) 44 { 45 System.out.println("下载过程中出现读写错误!"); 46 e.printStackTrace(); 47 } 48 finally 49 { 50 if(in != null) 51 { 52 in.close(); 53 in = null; 54 } 55 if(outp != null) 56 { 57 outp.close(); 58 outp = null; 59 } 60 } 61 62 %>
ajax上传客户端代码,UploadAddInputAjax.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 11 <title>上传文件</title> 12 <script src="style/js/jquery_1.9.1.js"></script> 13 <script src="style/js/jquery.ui.widget.js"></script> 14 <script src="style/js/jquery.iframe-transport.js"></script> 15 <script src="style/js/jquery.fileupload.js"></script> 16 </head> 17 18 <body> 19 20 <form name="upform" action="UploadAdd.jsp" method="POST" enctype="multipart/form-data"> 21 姓名:<input type ="text" name="name"/><br/> 22 年龄:<input type ="text" name="age"/><br/> 23 上传1:<input type ="text" id="file1_ct"/><input type ="file" name="file1" id="file1"/><br/> 24 上传2:<input type ="text" id="file2_ct"/><input type ="file" name="file2" id="file2"/><br/> 25 <input type="submit" value="Submit" /><br/> 26 <input type="reset" /> 27 </form> 28 <script type="text/javascript"> 29 $("input[type=file]").fileupload({ 30 url:"UploadAdd.jsp",//文件上传地址,当然也可以直接写在input的data-url属性内 31 dataType: ‘json‘, 32 formData: null, 33 multi: true, 34 done:function(e,result){ 35 //返回的数据在result.result中,假设我们服务器返回了一个json对象 36 //console.log(JSON.stringify(result.result)); 37 $("#"+$(this).attr("id")+"_ct").val(result.result[0].name); 38 } 39 }); 40 41 </script> 42 43 </body> 44 </html>
参考文章
fileUpload×2:http://developer.51cto.com/art/200907/133797.htm
http://lohasle.iteye.com/blog/1604428
smartUpload:http://www.cnblogs.com/elleniou/archive/2012/09/24/2700583.html
文件夹操作:http://www.open-open.com/lib/view/open1384162223821.html