Web项目将word打包zip并提供下载
该博客记录javaWeb项目将Word打包zip并提供下载功能的实现和其中遇到的坑,方便后续自己的查看的参照。
项目架构背景: JQuery + Bootstarp + SPRingMvc
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*/6publicvoidcreateZip(HttpServletRequest req,HttpServletResponse res,Map<String, Object>retmap) {7try{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 =newFile(zipFilePath);15 ZipOutputStream zipOut =newZipOutputStream(newFileOutputStream(zipFile));16
17//从配置文件获取待压缩的的文件夹路径,并生成文件夹,准备好读入流,等待进行读入文件进行压缩18 String filepath = getUploadpath("uploadword");19 File file =newFile(filepath);20 InputStream input =null;21
22//压缩包中文件zip的名称为23zipOut.setComment(file.getName());24
25//循环读入uploadword文件夹中文件,写入压缩流中,最后删除对应的文件26if(file.isDirectory()) {27 File[] files =file.listFiles();28for(inti = 0; i < files.length; ++i) {29
30 input =newFileInputStream(files[i]);31 zipOut.putNextEntry(newZipEntry(file.getName()+ File.separator +files[i].getName()));32
33inttemp = 0;34while((temp = input.read()) != -1) {35zipOut.write(temp);36}37input.close();38files[i].delete();39}40}41//关闭压缩流,并将压缩文件夹路径,压缩文件名称,当前服务器类型【tomcat,weblogic】返回给前端用于跳转下载页面时进行传参42zipOut.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) {48e.printStackTrace();49}50}51
52/**53 * @Description: 获取配置文件上传路径(为了可维护性高,路径会配置在配置文件中,然后在代码中进行读,这样会减少后边维护人员的痛苦)
54*/55publicString getUploadpath(String param) {56 Properties prop =newProperties();57 String url =this.getClass().getResource("").getPath().replaceAll("%20", " ");58 String path = url.substring(0, url.indexOf("WEB-INF")) + "WEB-INF/Config.properties";59try{60 prop.load(newFileInputStream(path));61 }catch(Exception e) {62e.printStackTrace();63}64 String content =prop.getProperty(param).trim();65returncontent;66 }
View Code
2. Sping MVC框架实现下载遇到的“坑” 可能以前做过这功能朋友的会产生疑问,为什么要跳转到前端的下载页面去,不直接在后端生成下载流,放入HttpServletResponse对象,返回给前端直接弹出下载框。当时我也是这样想的,但事实总是很残酷。
原来实现的代码是这样的:
1/**2 * @Description: 实现压缩包下载
3 *@param: TODO (入参描述)压缩文件夹的路径,生成的压缩文件的名称
4 *@return: TODO (返回类型和参数描述)
5*/6publicvoiddownFile(HttpServletResponse response, String zippath, String zipFileName) {7try{8
9 String path = zippath +zipFileName;10 File file =newFile(path);11if(file.exists()) {12 InputStream ins =newFileInputStream(path);13 BufferedInputStream bins =newBufferedInputStream(ins);//放到缓冲流里面14
15 OutputStream outs = response.getOutputStream();//获取文件输出IO流16 BufferedOutputStream bouts =newBufferedOutputStream(outs);17
18response.reset();19 response.setContentType("application/x-download");//设置response内容的类型20 response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(str, "GBK"));//设置头部信息21intbytesRead = 0;22byte[] buffer =newbyte[8192];23
24while((bytesRead = bins.read(buffer, 0, 8192)) != -1) {25 bouts.write(buffer, 0, bytesRead);26}27bouts.flush();28ins.close();29bins.close();30outs.close();31bouts.close();32 }else{33 response.sendRedirect("/error.jsp");34}35 }catch(Exception e) {36e.printStackTrace();37}38 }
View Code
加入后台这段代码后,并没有达到我想要的效果,可能是我对Spring Mvc理解的不够,还请各位看官赐教。
3. 生成zip包成功,在前端跳转下载页面 既然后端不能直接返回下载流,并弹出下载框,在前端接受返回的下载所需(zippath,zipFileName,serverName)的关键参数,跳转到下载JSP页面。
相应跳转(跳转方式多种多样,这里采用表单提交到download.jsp页面):
vartheParam ={};
theParam.zippath=data.zippath;
theParam.zipFileName=data.zipFileName;
Param.serverName=data.serverName;varformStr = "<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 import="cn.nxcc.nxwb.utils.ServerDetector"%><%@page language="java"contentType="application/x-msdownload"pageEncoding="UTF-8"%><%try {//关于文件下载时采用文件流输出的方式处理://加上response.reset(),并且所有的%>后面不要换行,包括最后一个;
response.reset();//可以加也可以不加
response.setContentType("application/x-download");Stringfiledownload=request.getParameter("zippath");Stringfiledisplay=request.getParameter("zipFileName");
response.addHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(filedisplay,"UTF-8"));
OutputStream outp=response.getOutputStream();
FileInputStream in=newFileInputStream(filedownload);byte[] b=newbyte[1024];inti=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();
}%>
View Code
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(ServletResponseImpl.java:1602) ......
虽然不影响功能的使用,还是看着不爽,百度一番,发现是自己添加的上两句的的原因(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*/5publicclassServerDetector{6privatestaticServerDetector _instance =newServerDetector();7privateString _serverId;8privateBoolean _geronimo;9privateBoolean _glassfish;10privateBoolean _jBoss;11privateBoolean _jetty;12privateBoolean _jonas;13privateBoolean _oc4j;14privateBoolean _resin;15privateBoolean _tomcat;16privateBoolean _webLogic;17privateBoolean _webSphere;18
19publicstaticfinalString GERONIMO_ID = "geronimo";20publicstaticfinalString GLASSFISH_ID = "glassfish";21publicstaticfinalString JBOSS_ID = "jboss";22publicstaticfinalString JETTY_ID = "jetty";23publicstaticfinalString JONAS_ID = "jonas";24publicstaticfinalString OC4J_ID = "oc4j";25publicstaticfinalString RESIN_ID = "resin";26publicstaticfinalString TOMCAT_ID = "tomcat";27publicstaticfinalString WEBLOGIC_ID = "weblogic";28publicstaticfinalString WEBSPHERE_ID = "websphere";29
30publicstaticString getServerId() {31 ServerDetector sd =_instance;32if(sd._serverId ==null) {33if(isGeronimo()) {34 sd._serverId = "geronimo";35 }elseif(isGlassfish()) {36 sd._serverId = "glassfish";37 }elseif(isJBoss()) {38 sd._serverId = "jboss";39 }elseif(isJOnAS()) {40 sd._serverId = "jonas";41 }elseif(isOC4J()) {42 sd._serverId = "oc4j";43 }elseif(isResin()) {44 sd._serverId = "resin";45 }elseif(isWebLogic()) {46 sd._serverId = "weblogic";47 }elseif(isWebSphere()) {48 sd._serverId = "websphere";49}50if(isJetty()) {51if(sd._serverId ==null) {52 sd._serverId = "jetty";53 }else{54 sd._serverId += "-jetty";55}56 }elseif(isTomcat()) {57if(sd._serverId ==null) {58 sd._serverId = "tomcat";59 }else{60 sd._serverId += "-tomcat";61}62}63if(sd._serverId ==null) {64thrownewRuntimeException("Server is not supported");65}66}67returnsd._serverId;68}69
70publicstaticbooleanisGeronimo() {71 ServerDetector sd =_instance;72if(sd._geronimo ==null) {73 sd._geronimo = _detect("/org/apache/geronimo/system/main/Daemon.class");74}75returnsd._geronimo.booleanValue();76}77
78publicstaticbooleanisGlassfish() {79 ServerDetector sd =_instance;80if(sd._glassfish ==null) {81 String value = System.getProperty("com.sun.aas.instanceRoot");82if(value !=null) {83 sd._glassfish =Boolean.TRUE;84 }else{85 sd._glassfish =Boolean.FALSE;86}87}88returnsd._glassfish.booleanValue();89}90
91publicstaticbooleanisJBoss() {92 ServerDetector sd =_instance;93if(sd._jBoss ==null) {94 sd._jBoss = _detect("/org/jboss/Main.class");95}96returnsd._jBoss.booleanValue();97}98
99publicstaticbooleanisJetty() {100 ServerDetector sd =_instance;101if(sd._jetty ==null) {102 sd._jetty = _detect("/org/mortbay/jetty/Server.class");103}104returnsd._jetty.booleanValue();105}106
107publicstaticbooleanisJOnAS() {108 ServerDetector sd =_instance;109if(sd._jonas ==null) {110 sd._jonas = _detect("/org/objectweb/jonas/server/Server.class");111}112returnsd._jonas.booleanValue();113}114
115publicstaticbooleanisOC4J() {116 ServerDetector sd =_instance;117if(sd._oc4j ==null) {118 sd._oc4j = _detect("Oracle.oc4j.util.ClassUtils");119}120returnsd._oc4j.booleanValue();121}122
123publicstaticbooleanisResin() {124 ServerDetector sd =_instance;125if(sd._resin ==null) {126 sd._resin = _detect("/com/caucho/server/resin/Resin.class");127}128returnsd._resin.booleanValue();129}130
131publicstaticbooleanisSupportsComet() {132returnfalse;133}134
135publicstaticbooleanisTomcat() {136 ServerDetector sd =_instance;137if(sd._tomcat ==null) {138 sd._tomcat = _detect("/org/apache/catalina/startup/Bootstrap.class");139}140if(sd._tomcat ==null) {141 sd._tomcat = _detect("/org/apache/catalina/startup/Embedded.class");142}143returnsd._tomcat.booleanValue();144}145
146publicstaticbooleanisWebLogic() {147 ServerDetector sd =_instance;148if(sd._webLogic ==null) {149 sd._webLogic = _detect("/weblogic/Server.class");150}151returnsd._webLogic.booleanValue();152}153
154publicstaticbooleanisWebSphere() {155 ServerDetector sd =_instance;156if(sd._webSphere ==null) {157 sd._webSphere = _detect("/com/ibm/websphere/product/VersionInfo.class");158}159returnsd._webSphere.booleanValue();160}161
162privatestaticBoolean _detect(String className) {163try{164ClassLoader.getSystemClassLoader().loadClass(className);165returnBoolean.TRUE;166 }catch(ClassNotFoundException cnfe) {167 ServerDetector sd =_instance;168
169 Class<?> c =sd.getClass();170if(c.getResource(className) !=null) {171returnBoolean.TRUE;172}173}174returnBoolean.FALSE;175}176 }
View Code
OK ,到此完美解决了 Spring MVC 中zip包下载、避开后端直接写入前端下载流、妥善解决各web容器的差异性。