• Word 打包 zip 并提供下载


         该篇博客记录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 }
    View Code

    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 }
    View Code

        加入后台这段代码后,并没有达到我想要的效果,可能是我对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 }
    View Code

        到此完美解决了 Spring MVC 中zip包下载、避开后端直接写入前端下载流、妥善解决各web容器的差异性。

  • 相关阅读:
    CF1343E-Weights Distributing (最短路)
    科大讯飞杯-日期小助手(补)
    网络15软工个人作业5——软件工程总结
    软工网络15个人作业4——alpha阶段个人总结
    软件工程网络15个人作业3——案例分析
    软工网络15结对编程练习
    软工网络15个人阅读作业2——提问题
    软件工程网络15个人阅读作业1(201521123052 蓝锦明)
    Java课程设计 购物车系统(个人博客) 201521123052 蓝锦明
    201521123052 《Java程序设计》 第14周学习总结
  • 原文地址:https://www.cnblogs.com/java-class/p/4798204.html
Copyright © 2020-2023  润新知