【问题标题】:Download zip InputStream Struts2 Java下载 zip InputStream Struts2 Java
【发布时间】:2013-12-10 07:08:31
【问题描述】:

首先,对不起英语。 我想知道你能不能帮我解决这个问题。 我正在尝试下载我使用 Struts2 从多个字节 [] 创建的 zip 并流式传输结果。

我使用 ZipOutputStream 并设法从中创建一个文件并使用 FileInputStream 读取和下载它,但我的问题是我不想创建一个文件。我只想将 ZipOutputStream 转换为 InputStream(例如转换为 ZipIntputStream)并下载该 ZipInputStream。 为此,我使用以下代码:

public void downloadZip() {
    contentType = "application/octet-stream";
    filename="myZip.zip";
    ByteArrayOutputStream baos = new ByteArrayOutputStream();       
    byte[] bytes;
    try {
        ZipOutputStream zos = new ZipOutputStream(baos);
        ZipEntry ze;
        bytes = otherClass.getBytes("File1");
        ze = new ZipEntry("File1.pdf");
        zos.putNextEntry(ze);
        zos.write(bytes);
        zos.closeEntry();

        bytes = otherClass.getBytes("File2");
        ze = new ZipEntry("File2.pdf");
        zos.putNextEntry(ze);
        zos.write(bytes);
        zos.closeEntry();

        zos.flush();
        inputStream = new ZipInputStream(new ByteArrayInputStream(baos.toByteArray()));
        zos.close();
}
catch(Exception e){...}
}

我的动作 struts.xml

<action...>
    <result name="success" type="stream">
        <param name="contentType">${contentType}</param>
        <param name="inputName">inputStream</param>
        <param name="contentDisposition">attachment;filename="${filename}"</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

问题是浏览器向我显示一条消息,告诉我该文件不是有效的 zip,其大小为 0 字节。

希望我解释清楚,提前非常感谢。

编辑:正如我所评论的,终于我得到了解决方案,它与 leonbloy 的回复非常相似。除了返回 ByteArrayInputStream 我应该在创建 ByteArrayInputStream 之前关闭 ZipOutputStream。这是结果代码,也许对其他人有用:

    ...
    zos.closeEntry();                       
    zos.close();            
    inputStream = new ByteArrayInputStream(baos.toByteArray());
}
catch(Exception e){...}
}

感谢您的帮助。

【问题讨论】:

    标签: java struts2 zip inputstream


    【解决方案1】:

    参数&lt;param name="inputName"&gt;inputStream&lt;/param&gt; 告诉Struts2 从哪里获取将发送到客户端的原始字节。在您的情况下,您想要发送压缩字节。相反,您正在设置 inputStream=ZipInputStream ,这是一个需要压缩源的流 - 解压缩。你不想这样,你想发送原始压缩字节。

    然后替换

    inputStream = new ZipInputStream(new ByteArrayInputStream(baos.toByteArray()))
    

    inputStream = new ByteArrayInputStream(baos.toByteArray())
    

    它应该可以工作

    【讨论】:

    • 感谢您的回答,但至少对我不起作用。也许我应该改变 contentType = "application/octet-stream";对于其他内容类型?
    • 你好。在进行更多测试后,我得到了解决方案。事实上,我在阅读您的回复之前尝试了您的解决方案,但在阅读后我再次尝试了它,正如我所说它对我不起作用。但现在我知道为什么了,所以我发布了解决方案,因为它对其他人有用。问题是我无法在创建 byteArrayInputStream 之前关闭 ZipOutputStream。非常感谢您的回复,因为它让我再次尝试了几次。
    【解决方案2】:

    ZipInputStream 用于解压缩文件并访问未压缩的 zip 内容。您只想将压缩字节发送到浏览器。

    创建 zip 文件后,只需使用 response.getOutputStream(baos.toByteArray()) 发送文件的字节即可。

    如果您压缩大文件,则在内存中执行所有操作可能会导致内存不足。如果输入文件太大,最好限制输入文件的大小并给用户一个错误。

    另外,看起来 inputStream 是一个类变量。对于 servlet,这是一个非常糟糕的主意。基本上你的类不是线程安全的,当两个用户同时访问该页面时,一个用户可能会得到另一个用户的 zip 文件。相反,将响应对象作为参数传递给方法或将 baos 返回给调用者,以便它可以将字节写入正确的响应流。摆脱存储特定于单个用户的信息的类变量,并在调用之间传递它们。

    【讨论】:

    • 感谢您这么快的回答,但问题是我无法调用 response.getOutputStream,因为显然我在 .jsp 中使用了 getWriter。几天前我遇到了问题。你知道其他解决方法吗?
    • 那么不要调用getWriter,你不能同时调用getWriter和getOutputStream。如果要发送文本,请调用 getWriter。如果要发送 zip 文件,请调用 getOutputStream。
    • 问题是这个调用是在程序的其他部分执行的,我不能改变它,这就是我想发送一个InputStream的原因。我试图删除那个调用,它似乎是对内部类的调用或类似的调用,因为我没有调用它,所以我放弃了它,我开始使用 inputStream 来显示内容。问题是,如果我发送了一个 pdf 文件来显示它,它可以工作,但它没有 zip 文件,我不知道为什么。
    【解决方案3】:

    你很幸运,this is exactly what you need

    在这种特定情况下,我通过直接写入响应来绕过框架结果系统。这样,Zip 将立即在客户端系统中创建,并逐步提供,而不是等待精化结束并与 Stream 结果一起输出。

    然后没有定义结果:

    <action name="createZip" class="foo.bar.CreateZipAction" />
    

    在行动中:

    public String execute() {
        try {        
            /* Read the amount of data to be streamed from Database to File System,
               summing the size of all Oracle's BLOB, PostgreSQL's ABYTE etc: 
               SELECT sum(length(my_blob_field)) FROM my_table WHERE my_conditions
            */          
            Long overallSize = getMyService().precalculateZipSize();
    
            // Tell the browser is a ZIP
            response.setContentType("application/zip"); 
            // Tell the browser the filename, and that it needs to be downloaded instead of opened
            response.addHeader("Content-Disposition", "attachment; filename=\"myArchive.zip\"");        
            // Tell the browser the overall size, so it can show a realistic progressbar
            response.setHeader("Content-Length", String.valueOf(overallSize));      
    
            ServletOutputStream sos = response.getOutputStream();       
            ZipOutputStream zos = new ZipOutputStream(sos);
    
            // Set-up a list of filenames to prevent duplicate entries
            HashSet<String> entries = new HashSet<String>();
    
            /* Read all the ID from the interested records in the database, 
               to query them later for the streams: 
               SELECT my_id FROM my_table WHERE my_conditions */           
            List<Long> allId = getMyService().loadAllId();
    
            for (Long currentId : allId){
                /* Load the record relative to the current ID:         
                   SELECT my_filename, my_blob_field FROM my_table WHERE my_id = :currentId            
                   Use resultset.getBinaryStream("my_blob_field") while mapping the BLOB column */
                FileStreamDto fileStream = getMyService().loadFileStream(currentId);
    
                // Create a zipEntry with a non-duplicate filename, and add it to the ZipOutputStream
                ZipEntry zipEntry = new ZipEntry(getUniqueFileName(entries,fileStream.getFilename()));
                zos.putNextEntry(zipEntry);
    
                // Use Apache Commons to transfer the InputStream from the DB to the OutputStream
                // on the File System; at this moment, your file is ALREADY being downloaded and growing
                IOUtils.copy(fileStream.getInputStream(), zos);
    
                zos.flush();
                zos.closeEntry();
    
                fileStream.getInputStream().close();                    
            }
    
            zos.close();
            sos.close();    
    
        } catch (Exception e) {
            logError(e);
        finally {
            IOUtils.closeQuietly(sos);
        }
    
        return NONE;
    }
    

    【讨论】:

    • 感谢您的麻烦,但就像在 Sarael 答案中发生的那样,我无法使用 response.getOutputStream() 因为我的代码中存在对 response.getWriter() 的调用而引发异常.据我所知,我只能使用 InputStream 将其发送到使用 Struts2 的客户端。
    • 那么不要调用response.getWriter(),如果你只需要下载ZIP(而不是其他数据)。上面的例子是我使用的代码的一部分,它完全适用于 Struts2。
    • 另外,response.getWriter() 是一个 PrintWriter,它只处理字符,而不是二进制数据(如 ZIP 内容):docs.oracle.com/javaee/6/api/javax/servlet/…。这就是你应该使用 OutputStream 而不是 PrintWriter 的原因
    猜你喜欢
    • 2017-08-31
    • 1970-01-01
    • 2016-01-18
    • 2014-08-09
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    • 2019-04-06
    • 2014-05-28
    相关资源
    最近更新 更多