该篇博客记录Java Web项目将word打包zip并提供下载功能的实现和其中遇到的坑,方便后续自己的查看的参照。
1. 后台处理的java 方法
首先将所有的word生成到uploadword目录下面,然后指定被压缩的文件夹为uploadword,
并将生成的zip指定到uploadzip文件夹(在配置目录路径的时候记得注意几种不同的服务器路径写法),
当时也考虑过在同一个文件夹下面生成word ,然后压缩为一个 zip,但很可惜压缩出来的文件,
总是莫名奇妙的迭代了很多相同的压缩包,可能是将生成的压缩包,也作为了文件不断循环在压缩,所以果断分开文件夹。
在将文件循环压缩入压缩包中后,删除原uploadword文件夹中的文件,所以当程序正确执行完后,
服务器中的uploadword这个文件夹都是清空的(毕竟这个功能是管理员进行的操作,只有一个超级管理员,没有考虑多用户并发生成压缩包的情况)
根据 word 模版生成 word 文书的方法,我已经在前面的博客中总结过,可以看 java动态生成复杂word文件
压缩方法上代码:
1 /** 2 * @Description: 生成zip包 3 * @param: web请求对象,web返回对象,web后端返回到前端的map 4 * @return: TODO (返回类型和参数描述) 5 */ 6 public void createZip(HttpServletRequest req,HttpServletResponse res,Map<String, Object> retmap) { 7 try { 8 //从配置文件获取生成压缩包的文件夹路径,生成唯一的压缩包名称 9 String zippath = getUploadpath("uploadzip"); 10 String zipFileName = Functions.now("yyyy-MM-dd-HH-mm-ss") + ".zip"; 11 String zipFilePath = zippath + zipFileName; 12 13 //生成压缩文件,并写入压缩流中 14 File zipFile = new File(zipFilePath); 15 ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile)); 16 17 //从配置文件获取待压缩的的文件夹路径,并生成文件夹,准备好读入流,等待进行读入文件进行压缩 18 String filepath = getUploadpath("uploadword"); 19 File file = new File(filepath); 20 InputStream input = null; 21 22 // 压缩包中文件zip的名称为 23 zipOut.setComment(file.getName()); 24 25 //循环读入uploadword文件夹中文件,写入压缩流中,最后删除对应的文件 26 if (file.isDirectory()) { 27 File[] files = file.listFiles(); 28 for (int i = 0; i < files.length; ++i) { 29 30 input = new FileInputStream(files[i]); 31 zipOut.putNextEntry(new ZipEntry(file.getName()+ File.separator + files[i].getName())); 32 33 int temp = 0; 34 while ((temp = input.read()) != -1) { 35 zipOut.write(temp); 36 } 37 input.close(); 38 files[i].delete(); 39 } 40 } 41 //关闭压缩流,并将压缩文件夹路径,压缩文件名称,当前服务器类型【tomcat,weblogic】返回给前端用于跳转下载页面时进行传参 42 zipOut.close(); 43 retmap.put("zippath", zipFilePath); 44 retmap.put("zipFileName", zipFileName); 45 retmap.put("serverName", ServerDetector.getServerId().toUpperCase()); 46 retmap.put("success", true); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 } 51 52 /** 53 * @Description: 获取配置文件上传路径(为了可维护性高,路径会配置在配置文件中,然后在代码中进行读,这样会减少后边维护人员的痛苦) 54 */ 55 public String getUploadpath(String param) { 56 Properties prop = new Properties(); 57 String url = this.getClass().getResource("").getPath().replaceAll("%20", " "); 58 String path = url.substring(0, url.indexOf("WEB-INF")) + "WEB-INF/Config.properties"; 59 try { 60 prop.load(new FileInputStream(path)); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 } 64 String content = prop.getProperty(param).trim(); 65 return content; 66 }
2. Sping MVC框架实现下载遇到的“坑”
可能以前做过这功能朋友的会产生疑问,为什么要跳转到前端的下载页面去,不直接在后端生成下载流,
放入HttpServletResponse对象,返回给前端直接弹出下载框。当时我也是这样想的,但事实总是很残酷。
原来实现的代码是这样的:
1 /** 2 * @Description: 实现压缩包下载 3 * @param: TODO (入参描述)压缩文件夹的路径,生成的压缩文件的名称 4 * @return: TODO (返回类型和参数描述) 5 */ 6 public void downFile(HttpServletResponse response, String zippath, String zipFileName) { 7 try { 8 9 String path = zippath + zipFileName; 10 File file = new File(path); 11 if (file.exists()) { 12 InputStream ins = new FileInputStream(path); 13 BufferedInputStream bins = new BufferedInputStream(ins);// 放到缓冲流里面 14 15 OutputStream outs = response.getOutputStream();// 获取文件输出IO流 16 BufferedOutputStream bouts = new BufferedOutputStream(outs); 17 18 response.reset(); 19 response.setContentType("application/x-download");// 设置response内容的类型 20 response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(str, "GBK"));// 设置头部信息 21 int bytesRead = 0; 22 byte[] buffer = new byte[8192]; 23 24 while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) { 25 bouts.write(buffer, 0, bytesRead); 26 } 27 bouts.flush(); 28 ins.close(); 29 bins.close(); 30 outs.close(); 31 bouts.close(); 32 } else { 33 response.sendRedirect("/error.jsp"); 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } 38 }
加入后台这段代码后,并没有达到我想要的效果,可能是我对Spring Mvc理解的不够,还请各位看官赐教。
2016.06.17
经手后续项目时发现,是可以在后端将文件读入为字节流并放入 response 输出流当中,页面会弹出文件下载框,而且在各个浏览器都表现不错。
url = AAA + '/downloadFile?fileId='123456789';
@RequestMapping(value = "/downloadFile", method = RequestMethod.GET) public void downloadFile(@RequestParam String fileId, HttpServletResponse response) { try { UploadfileVO uploadfileVO = queryer.find(UploadfileVO.class, "id='" + fileId + "'"); if (uploadfileVO == null) { return; }
String fileName = java.net.URLEncoder.encode(uploadfileVO.getOldname(), "utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName); response.setContentType("application/x-msdownload"); long fileLength = uploadfileVO.getPath().length(); String length = String.valueOf(fileLength); response.setHeader("content_Length", length); response.setCharacterEncoding("utf-8"); OutputStream servletOutPutStream = response.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(uploadfileVO.getPath()); byte bytes[] = new byte[1024];//设置缓冲区为1024个字节,即1KB int len; while ((len = fileInputStream.read(bytes)) != -1) { servletOutPutStream.write(bytes, 0, len); } servletOutPutStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } }
3. 生成zip包成功,在前端跳转下载页面
既然后端不能直接返回下载流,并弹出下载框,在前端接受返回的下载所需(zippath,zipFileName,serverName)的关键参数,跳转到下载JSP页面。
相应跳转(跳转方式多种多样,这里采用表单提交到download.jsp页面):
var theParam = {}; theParam.zippath = data.zippath; theParam.zipFileName =data.zipFileName; Param.serverName =data.serverName; var formStr = "<form action='..../download.jsp' method='post' id='form' style='display:none'>"; $.each(theParam, function(key, value) { formStr += "<input type='hidden' name='" + key + "' value='" + value + "'/>"; }); formStr += "</form>"; $("body").append(formStr); $("#form").submit();
下载页面:
<%@page import="java.io.OutputStream"%> <%@page import="java.net.URLEncoder"%> <%@page import="java.io.FileInputStream"%> <%@page language="java" contentType="application/x-msdownload" pageEncoding="UTF-8"%> <% try { //关于文件下载时采用文件流输出的方式处理: //加上response.reset(),并且所有的%>后面不要换行,包括最后一个; response.reset();//可以加也可以不加 response.setContentType("application/x-download"); String filedownload = request.getParameter("zippath"); String filedisplay = request.getParameter("zipFileName"); response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filedisplay, "UTF-8")); OutputStream outp = response.getOutputStream(); FileInputStream in = new FileInputStream(filedownload); byte[] b = new byte[1024]; int i = 0; while ((i = in.read(b)) > 0) { outp.write(b, 0, i); } outp.flush(); outp.close(); in.close(); //Tomcat 需要添加这两局来避免 getOutputStream() 方法,已被调用的异常,weblogic做特殊判断 if(request.getParameter("serverName").equals("TOMCAT")){ out.clear(); out = pageContext.pushBody(); } } catch (Exception e) { e.printStackTrace(); } %>
4. 流在不同web容器的妥善处理
刚开始程序在tomcat 上跑时,总出现报错:
org.apache.jasper.JasperException: java.lang.IllegalStateException: getOutputStream() has already been called for this response
百度一番,发现Body()的作用是保存当前的out对象,并更新PageContext中Page范围内Out对象。
JSP容器在处理完成请求后会调用releasePageConter方法释放所有的PageContestObject,并且同时调用getWriter方法。
由于getWriter方法与在JSP页面中使用流相关的getOutputStream方法冲突,解决方法也很简单,重新生成PageContext中Page范围内Out对象。
在代码最后添加 :
out.clear();
out = pageContext.pushBody();
好了,自己本地测试不会报错了,将项目打包到weblogic,出现报错:
Servlet failed with Exception java.lang.IllegalStateException: Response already committed at weblogic.servlet.internal.ServletResponseImpl.objectIfCommitted
虽然不影响功能的使用,还是看着不爽,继续百度,发现是自己添加的上两句的的原因(tomcat 和 weblogic 容器的差异性,应该是weblogic并不会调用releasePageConter方法释放所有的PageContestObject)
这就是下载页面出现这几行代码的原因:
//Tomcat 需要添加这两局来避免 getOutputStream() 方法,已被调用的异常,weblogic做特殊判断
if(request.getParameter("serverName").equals("TOMCAT")){
out.clear();
out = pageContext.pushBody();
}
如果能判断是在哪个web容器中,然后进行特殊判断就好了,portal-kernel.jar中的类ServerDetector.java 能完美判断多达10种以上的容器类型,
但我又不想将这个jar 引入到项目中(俺就用一个类,引一个jar,太亏了),然后Jd-Gui反编译,单独拉出这个类,修改修改添加到我的项目中。
修改完的代码,不依赖任何类,可以拿来直接用:
1 /** 2 * @ClassName: ServerDetector 3 * @Description: 判断 Web 容器类型 4 */ 5 public class ServerDetector{ 6 private static ServerDetector _instance = new ServerDetector(); 7 private String _serverId; 8 private Boolean _geronimo; 9 private Boolean _glassfish; 10 private Boolean _jBoss; 11 private Boolean _jetty; 12 private Boolean _jonas; 13 private Boolean _oc4j; 14 private Boolean _resin; 15 private Boolean _tomcat; 16 private Boolean _webLogic; 17 private Boolean _webSphere; 18 19 public static final String GERONIMO_ID = "geronimo"; 20 public static final String GLASSFISH_ID = "glassfish"; 21 public static final String JBOSS_ID = "jboss"; 22 public static final String JETTY_ID = "jetty"; 23 public static final String JONAS_ID = "jonas"; 24 public static final String OC4J_ID = "oc4j"; 25 public static final String RESIN_ID = "resin"; 26 public static final String TOMCAT_ID = "tomcat"; 27 public static final String WEBLOGIC_ID = "weblogic"; 28 public static final String WEBSPHERE_ID = "websphere"; 29 30 public static String getServerId() { 31 ServerDetector sd = _instance; 32 if (sd._serverId == null) { 33 if (isGeronimo()) { 34 sd._serverId = "geronimo"; 35 } else if (isGlassfish()) { 36 sd._serverId = "glassfish"; 37 } else if (isJBoss()) { 38 sd._serverId = "jboss"; 39 } else if (isJOnAS()) { 40 sd._serverId = "jonas"; 41 } else if (isOC4J()) { 42 sd._serverId = "oc4j"; 43 } else if (isResin()) { 44 sd._serverId = "resin"; 45 } else if (isWebLogic()) { 46 sd._serverId = "weblogic"; 47 } else if (isWebSphere()) { 48 sd._serverId = "websphere"; 49 } 50 if (isJetty()) { 51 if (sd._serverId == null) { 52 sd._serverId = "jetty"; 53 } else { 54 sd._serverId += "-jetty"; 55 } 56 } else if (isTomcat()) { 57 if (sd._serverId == null) { 58 sd._serverId = "tomcat"; 59 } else { 60 sd._serverId += "-tomcat"; 61 } 62 } 63 if (sd._serverId == null) { 64 throw new RuntimeException("Server is not supported"); 65 } 66 } 67 return sd._serverId; 68 } 69 70 public static boolean isGeronimo() { 71 ServerDetector sd = _instance; 72 if (sd._geronimo == null) { 73 sd._geronimo = _detect("/org/apache/geronimo/system/main/Daemon.class"); 74 } 75 return sd._geronimo.booleanValue(); 76 } 77 78 public static boolean isGlassfish() { 79 ServerDetector sd = _instance; 80 if (sd._glassfish == null) { 81 String value = System.getProperty("com.sun.aas.instanceRoot"); 82 if (value != null) { 83 sd._glassfish = Boolean.TRUE; 84 } else { 85 sd._glassfish = Boolean.FALSE; 86 } 87 } 88 return sd._glassfish.booleanValue(); 89 } 90 91 public static boolean isJBoss() { 92 ServerDetector sd = _instance; 93 if (sd._jBoss == null) { 94 sd._jBoss = _detect("/org/jboss/Main.class"); 95 } 96 return sd._jBoss.booleanValue(); 97 } 98 99 public static boolean isJetty() { 100 ServerDetector sd = _instance; 101 if (sd._jetty == null) { 102 sd._jetty = _detect("/org/mortbay/jetty/Server.class"); 103 } 104 return sd._jetty.booleanValue(); 105 } 106 107 public static boolean isJOnAS() { 108 ServerDetector sd = _instance; 109 if (sd._jonas == null) { 110 sd._jonas = _detect("/org/objectweb/jonas/server/Server.class"); 111 } 112 return sd._jonas.booleanValue(); 113 } 114 115 public static boolean isOC4J() { 116 ServerDetector sd = _instance; 117 if (sd._oc4j == null) { 118 sd._oc4j = _detect("oracle.oc4j.util.ClassUtils"); 119 } 120 return sd._oc4j.booleanValue(); 121 } 122 123 public static boolean isResin() { 124 ServerDetector sd = _instance; 125 if (sd._resin == null) { 126 sd._resin = _detect("/com/caucho/server/resin/Resin.class"); 127 } 128 return sd._resin.booleanValue(); 129 } 130 131 public static boolean isSupportsComet() { 132 return false; 133 } 134 135 public static boolean isTomcat() { 136 ServerDetector sd = _instance; 137 if (sd._tomcat == null) { 138 sd._tomcat = _detect("/org/apache/catalina/startup/Bootstrap.class"); 139 } 140 if (sd._tomcat == null) { 141 sd._tomcat = _detect("/org/apache/catalina/startup/Embedded.class"); 142 } 143 return sd._tomcat.booleanValue(); 144 } 145 146 public static boolean isWebLogic() { 147 ServerDetector sd = _instance; 148 if (sd._webLogic == null) { 149 sd._webLogic = _detect("/weblogic/Server.class"); 150 } 151 return sd._webLogic.booleanValue(); 152 } 153 154 public static boolean isWebSphere() { 155 ServerDetector sd = _instance; 156 if (sd._webSphere == null) { 157 sd._webSphere = _detect("/com/ibm/websphere/product/VersionInfo.class"); 158 } 159 return sd._webSphere.booleanValue(); 160 } 161 162 private static Boolean _detect(String className) { 163 try { 164 ClassLoader.getSystemClassLoader().loadClass(className); 165 return Boolean.TRUE; 166 } catch (ClassNotFoundException cnfe) { 167 ServerDetector sd = _instance; 168 169 Class<?> c = sd.getClass(); 170 if (c.getResource(className) != null) { 171 return Boolean.TRUE; 172 } 173 } 174 return Boolean.FALSE; 175 } 176 }
到此完美解决了 Spring MVC 中zip包下载、避开后端直接写入前端下载流、妥善解决各web容器的差异性。