该篇博客记录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容器的差异性。

Word 打包 zip 并提供下载的更多相关文章

  1. 打包zip下载

    //首先引入的文件为org.apache的切记不是jdk的import org.apache.tools.zip.ZipOutputStream;import org.apache.tools.zip ...

  2. iOS开发UI篇—推荐两个好用的Xcode插件(提供下载链接)

    iOS开发UI篇—推荐两个好用的Xcode插件(提供下载链接) 这里推荐两款好用的Xcode插件,并提供下载链接. 一.插件和使用如下: 1.两款插件 对项目中图片提供自动提示功能的插件:KSImag ...

  3. web系统数据导出功能设计实现(导出excel2003/2007 word pdf zip等)

    web系统数据导出功能设计实现(导出excel2003/2007 word pdf zip等) 前言 我们在做web系统中,导出也是很常用的一个功能,如果每一个数据列表都要对应写一个导出的方法不太现实 ...

  4. 推荐两个好用的Xcode插件(提供下载链接)

    这里推荐两款好用的Xcode插件,并提供下载链接. 一.插件和使用如下: 1.两款插件 对项目中图片提供自动提示功能的插件:KSImageNamed-Xcode-master 提供快速创建自动注释:V ...

  5. RDIFramework.NET平台代码生成器V3.0版本全新发布-更新于20160518(提供下载)

    最新版本请转到:RDIFramework.NET平台代码生成器V3.1版本全新发布-更新于2016-10-08(提供下载) RDIFramework.NET代码生成器V3.0版本修改了针对3.0版本的 ...

  6. 导出DBF,并且提供下载 [转]

    导出DBF,并且提供下载 #region Declare string mFilePath = MapPath("../DataTmp/");                str ...

  7. php导出csv数据在浏览器中输出提供下载或保存到文件的示例

    来源:http://www.jb51.net/article/49313.htm 1.在浏览器输出提供下载 /** * 导出数据到CSV文件 * @param array $data 数据 * @pa ...

  8. RDIFramework.NET平台代码生成器V2.8发布-更新于2014-12-31(提供下载)

    RDIFramework.NET平台代码生成器V2.8发布  更新于2014-12-31 注:已经发布了新版本,请转新版本下载: RDIFramework.NET平台代码生成器V3.0版本全新发布-更 ...

  9. RDIFramework.NET平台代码生成器V1.0发布(提供下载)

    RDIFramework.NET平台代码生成器V1.0发布(提供下载)   RDIFramework.NET(.NET快速开发整合框架)框架做为信息化系统快速开发.整合的框架,其目的一至是给用户和开发 ...

随机推荐

  1. GY编辑平台产品总结

    产品亮点一.实时直播流的关键帧识别并展示选择频道的实时流并播放后,会在窗口中自动展示关键帧图片:配对选择关键帧的截图即确定了素材的入点,出点:编辑平台图如下所示:二.广告自动识别与监测方案1. 制作样 ...

  2. C# 多线程,论多核时代爱恨情仇

    为什么要学习多线程? 2010年1月21日是10年某市公务员考试的报名截止日.因从下午2点开始,用于报名的北京市人事考试网瘫痪,原定于昨天下午5点截止的报名时间延迟至今天上午11点. 2011年3月1 ...

  3. Python时间处理之time模块

    1.time模块简介 time模块提供各种操作时间的函数  说明:一般有两种表示时间的方式:       第一种是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一 ...

  4. 烂泥:Windows下安装与配置Nginx web服务器

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 前几篇文章,我们使用nginx都是在linux环境下,今天由于工作的需要.需要在windows环境也使用nginx搭建web服务器. 下面记录下有关ng ...

  5. php魔术方法罗列

    ##__sleep() 和 __wakeup() 当序列化(serialize)对象时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep() .__sleep() 方法常用于提交未提交 ...

  6. Android 中PopupWindow使用 (转)

    参考学习后遇到问题: 要引用:有好几个,可以用错误提示解决: import android.widget.PopupWindow; import android.widget.Toast; Activ ...

  7. ES6箭头函数(Arrow Functions)

    ES6可以使用“箭头”(=>)定义函数,注意是函数,不要使用这种方式定义类(构造器). 一.语法 1. 具有一个参数的简单函数 var single = a => a single('he ...

  8. 在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  9. HashMap的key可以是可变的对象吗???

    大家都知道,HashMap的是key-value(键值对)组成的,这个key既可以是基本数据类型对象,如Integer,Float,同时也可以是自己编写的对象,那么问题来了,这个作为key的对象是否能 ...

  10. mysql中文乱码问题总结

    起因:此次开发工作由于电脑不能正常使用sqlserver所以改用mysql. 问题描述:mysql使用中文产生乱码 详细问题: 1.问题1:在dos界面登录数据库存入数据时如果存在中文那么不会显示. ...