在工作流的一张表单里可能会有多个步骤上传附件,在用户的待办中往往会存在多条带有附件的任务,如果一一打开并且点击下载链接下载,不仅费时,而且繁琐,用户体验较差。

  OA系统采用的是FastDFS做为文件服务器,FastDFS的Java客户端提供了上传、下载等功能供调用。

在我之前的文章里对此有描述,目前已有的代码有对文件的批量上传功能,但下载的参数往往是针对单个文件。比如单个文件的下载方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
     * 文件下载
     * @author chao.gao
     * @date 2014-2-17 下午5:28:23
     * @see com.gaochao.platform.components.upload.IUploadService#download(java.lang.String,
     *      java.lang.String)
     * @param id
     * @param fileName
     */
    @Override
    public void download(String id, String fileName) {
        InputStream in= null;
        FileOutputStream out  = null;
        try {
            Resource[] resources = RESOLVER
                    .getResources("classpath*:com/fx/**/META-INF/upload/*.conf");
            if (resources == null || resources.length < 1) {
                LOGGER.error("下载文件失败,失败原因 : ""client.conf不存在!");
            else {
                 in = resources[0].getInputStream();
                File file = new File("client.conf");
                 out = new FileOutputStream(file);
                byte[] buf = new byte[1024];
                while (true) {
                    int r = in.read(buf);
                    if (r == -1) {
                        break;
                    }
                    out.write(buf, 0, r);
                }
                ClientGlobal.init(file.getAbsolutePath());
                LOGGER.info("下载中:" "client.conf初始化成功!");
                TrackerClient trackerClient = new TrackerClient();
                TrackerServer trackerServer = trackerClient.getConnection();
                if (trackerServer != null) {
                    LOGGER.info("下载中:" "成功获得均衡器");
                else {
                    LOGGER.error("下载失败:" "均衡器获取失败");
                }
                StorageServer storageServer = null;
                StorageClient storageClient = new StorageClient(trackerServer,
                        storageServer);
                // groupName and remoteFileName should exist
                String group_name = "group1";
                AttachmentEntity aEntity = attachmentDao.queryById(id);
                String remote_filename = aEntity.getPosition();
                String[] paths = remote_filename.split("/");
                FileInfo fi = storageClient.get_file_info(paths[0],
                        remote_filename.replace(paths[0] + "/"""));
                LOGGER.error("下载中..." "得到文件信息");
                HttpServletResponse response = ResponseContext
                        .geRequestContext().getResponse();
                response.reset();
                response.setContentType("application/download;charset=UTF-8");
                // response.setHeader("Content-Disposition","attachment;" +
                // "filename=" + new String(fileName.getBytes("ISO_8859_1"),
                // "UTF-8"));
                // 如果使用上面编码格式,否则附件名称若为中文,则乱码,下载下来的附件名称,显示不全.或者乱乱码 .应该使用下面的编码格式.
                response.setHeader("Content-Disposition""attachment;"
                        "filename="
                        new String(fileName.getBytes("gbk"), "ISO8859-1"));
                // String basepath =
                // System.getProperties().getProperty("user.home");
                // String path = basepath + File.separator + new
                // String(fileName.getBytes("GBK"), "ISO_8859_1");
                ServletOutputStream oOutput = response.getOutputStream();
                byte[] inputStream = storageClient.download_file(paths[0],
                        remote_filename.replace(paths[0] + "/"""));
                try {
                    oOutput.write(inputStream);
                    oOutput.flush();
                catch (IOException ioe) {
                    LOGGER.error("下载失败:" "写入本地文件失败!");
                finally {
                    IOUtils.close(oOutput);
                }
                String sourceIpAddr = fi.getSourceIpAddr();
                long size = fi.getFileSize();
                LOGGER.info("ip:" + sourceIpAddr + ",size:" + size);
            }
        catch (FileNotFoundException fnfex) {
            LOGGER.error("下载失败,文件未找到:" + fnfex.getMessage());
        catch (IOException ioex) {
            LOGGER.error("下载失败,IO 错误 :" + ioex.getMessage());
        catch (MyException e) {
            LOGGER.error("下载失败,其他错误 :" + e.getMessage());
        }
        finally {
             try {
                 if(in!=null)
                in.close();
                 if(out!=null)
                 out.close(); 
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }  
             
        }
    }

  我尝试向后端传入多个文件的id,然后循环调用下载方法。我的目的是在循环调用中多个文件依次下载。但测试结果表明这种方法是行不通的。在第一个文件下载结束后,该方法在

1
2
3
4
HttpServletResponse response = ResponseContext
                        .geRequestContext().getResponse();
                response.reset();
                response.setContentType("application/download;charset=UTF-8");

前后会报:java.lang.IllegalStateException

  最初我是怀疑跟我的页面提交方式有关,我是采用ajax提交,参数直接用json格式。action中返回的是String类型的SUCCESS,struts.xml中配置的<result name="success" type="json" >。又借鉴单个成功下载的经验,我将struts.xml中的action的result配置修改为如下:

1
2
3
 <result name="success" type="json">
                <param name="contentType">text/html</param>
            </result>

  同时根据网上有关该异常的处理办法,本文记为方法1,以下类推。这些方法在某些场合或某些情况下是有效的,大家遇到类似问题可以逐一尝试。

方法一、将返回值修改为null,而不是使用SUCCESS;

该方法在我的应用下未起作用。然后我查找了资料,又借鉴了我之前所写的批量导出的实现,在批量导出时也遇到了很多问题,最后采用动态组装form提交的方式解决了。我将我的逻辑也参考修改,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function download(){
        var selected = $("#proposalDataGrid").selectRow();
        if(selected.length == 0){
            alert("未选择提案!");
        }else{
            var data = {proposalVo : {ids : selected}}; 
            var url = '../patent/dowloadAttByIds.action'
            var name = "proposalVo.ids";
             
            download_(url, name,selected);
        }
    }
     
    function download_(url, name, uuidArray) {
            var tempForm = document.createElement("form");
            tempForm.action = url;
            tempForm.method = "post";
            tempForm.contentType = "application/x-www-form-urlencoded;charset=utf-8";
            document.body.appendChild(tempForm);
             
            for var i = 0; i < uuidArray.length; i++) {
                var tempInput = document.createElement("input");
                tempInput.type = "hidden";
                tempInput.name = name;
                var uuids = new Array();           
                uuids[i] = uuidArray[i];
                tempInput.value = uuids[i];
                tempForm.appendChild(tempInput);
            }
             
            tempForm.submit();
        }

  这是一个动态的js组装,非常巧妙,我使用这种方法传递参数以及向后端发起下载请求。事实表明这种方法与第一种方案完全相同,只下载了第一个,在下载第二个时即抛出同样的错误。

  现在矛盾聚集在了该exception的解决上。我上网搜索了很多资料,有一篇文章介绍的情况与本文类似,如下:

  该异常表示,当前对客户端的响应已经结束,不能在响应已经结束(或说消亡)后再向客户端(实际上是缓冲区)输出任何内容。具体分析:

  首先解释下flush(),我们知道在使用读写流的时候数据先被读入内存这个缓冲区中,然后再写入文件,但是当数据读完时不代表数据已经写入文件完毕,因为可能还有一部分仍未写入文件而留在内存中,这时调用flush()方法就会把缓冲区的数据强行清空输出,因此flush()的作用就是保证缓存清空输出。 response是服务端对客户端请求的一个响应,其中封装了响应头、状态码、内容等, 服务端在把response提交到客户端之前,会向缓冲区内写入响应头和状态码,然后将所有内容flush。这就标志着该次响应已经committed(提交)。对于当前页面中已经committed(提交)的response,就不能再使用这个response向缓冲区写任何东西(注:同一个页面中的response.XXX()是同一个response的不同方法,只要其中一个已经导致了committed,那么其它类似方式的调用都会导致 IllegalStateException异常)。  

  我的代码里存在flush,flush导致了第一次的提交,我将flush语句去掉,问题依然。看来flush还不是最终的原因。但最终的原因肯定是第一个文件下载结束后,response已经返回给前端,状态已经失效,第二个文件下载的时候当然会出问题。

  我在百度云网盘有账号,我记得其支持多个文件下载。登陆网盘后多选下载,百度是将多个文件打包后发给了用户。参考该方法,我最终的实现方案是循环FastDFS下载方法,将字节流转化为文件,再将文件添加进zip文件中,然后将zip转化为字节流写入response的输出流中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
for(int i = 0;i<ids.length; i++){
                AttachmentEntity aEntity = attachmentDao.queryById(ids[i]);
                String remote_filename = aEntity.getPosition();
                String[] paths = remote_filename.split("/");
                FileInfo fi = storageClient.get_file_info(paths[0],
                        remote_filename.replace(paths[0] + "/"""));
                LOGGER.error("下载中..." "得到文件信息");
                 
                byte[] inputStream = storageClient.download_file(paths[0],
                        remote_filename.replace(paths[0] + "/"""));
                File fileTemp = byte2File(inputStream, fileNames[i]);
                fileList.add(fileTemp);
                }
                File[] files = (File[])fileList.toArray(new File[fileList.size()]);
                File zipFile = genZip(files);
                 
                HttpServletResponse response = ResponseContext
                        .geRequestContext().getResponse();
                response.reset();
                response.setContentType("application/download;charset=UTF-8");
                // response.setHeader("Content-Disposition","attachment;" +
                // "filename=" + new String(fileName.getBytes("ISO_8859_1"),
                // "UTF-8"));
                // 如果使用上面编码格式,否则附件名称若为中文,则乱码,下载下来的附件名称,显示不全.或者乱乱码 .应该使用下面的编码格式.
                response.setHeader("Content-Disposition""attachment;"
                        "filename="
                        new String("download.zip".getBytes("gbk"), "ISO8859-1"));
                ServletOutputStream oOutput = response.getOutputStream();
                try {
                    oOutput.write(File2byte(zipFile));
                    oOutput.flush();
                    zipFile.delete();
                catch (IOException ioe) {
                    LOGGER.error("下载失败:" "写入本地文件失败!");
                finally {
                    IOUtils.close(oOutput);
                }

  该方案涉及到了文件转字节流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
     
     * file to byte
     * @author chao.gao
     * @date 2015-6-25 下午5:08:14
     * @param filePath
     * @return
     */
    public static byte[] File2byte(File file)  
    {  
        byte[] buffer = null;  
        try  
        {  
            FileInputStream fis = new FileInputStream(file);  
            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            byte[] b = new byte[1024];  
            int n;  
            while ((n = fis.read(b)) != -1)  
            {  
                bos.write(b, 0, n);  
            }  
            fis.close();  
            bos.close();  
            buffer = bos.toByteArray();  
        }  
        catch (FileNotFoundException e)  
        {  
            e.printStackTrace();  
        }  
        catch (IOException e)  
        {  
            e.printStackTrace();  
        }  
        return buffer;  
    }

  字节流写入文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
     
     * byte to file
     * @author chao.gao
     * @date 2015-6-25 下午4:56:50
     * @param buf
     * @param filePath
     * @param fileName
     */
    public static File byte2File(byte[] buf, String fileName)  
    {  
        BufferedOutputStream bos = null;  
        FileOutputStream fos = null;  
        File file = null;  
        try  
        {  
            file = new File(fileName);  
            fos = new FileOutputStream(file);  
            bos = new BufferedOutputStream(fos);  
            bos.write(buf); 
            return file;
        }  
        catch (Exception e)  
        {  
            e.printStackTrace();  
        }  
        finally  
        {  
            if (bos != null)  
            {  
                try  
                {  
                    bos.close();  
                }  
                catch (IOException e)  
                {  
                    e.printStackTrace();  
                }  
            }  
            if (fos != null)  
            {  
                try  
                {  
                    fos.close();  
                }  
                catch (IOException e)  
                {  
                    e.printStackTrace();  
                }  
            }  
        }  
        return file;
    }

  以及文件的zip打包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static File genZip(File[] file1){
         try {  
                File filezip = new File( "download.zip");
                ZipOutputStream out = new ZipOutputStream(new FileOutputStream(  
                        filezip));  
                byte[] buffer = new byte[1024];  
                for (int i = 0; i < file1.length; i++) {  
                    FileInputStream fis = new FileInputStream(file1[i]);  
                    out.putNextEntry(new ZipEntry(file1[i].getName()));  
                    int len;  
                    // 读入需要下载的文件的内容,打包到zip文件  
                    while ((len = fis.read(buffer)) > 0) {  
                        out.write(buffer, 0, len);  
                    }  
                    out.closeEntry();  
                    fis.close();  
                    file1[i].delete();
                }  
                out.close();  
                return filezip;
            catch (Exception e) {  
                
            }  
         return null;
    }

本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1665686,如需转载请自行联系原作者

批量下载的实现及java.lang.IllegalStateException异常相关推荐

  1. 【问题解决】java.lang.IllegalStateException异常是什么问题?解决办法

    java.lang.IllegalStateException异常是什么问题?解决办法 Illegal State Exception 非法状态异常 Life cycle Exception 生命周期 ...

  2. java.lang.IllegalStateException异常:简单的分析和简单解决方案

    java.lang.IllegalStateException异常:简单的分析和简单解决方案 参考文章: (1)java.lang.IllegalStateException异常:简单的分析和简单解决 ...

  3. java.lang.IllegalStateException异常原因和解决

    1.出现该异常场景 Iterator.remove() 迭代删除 引发 2.问题定位分析 1. Iterator.remove() 迭代删除 引发 重复 remove 导致出现该异常 场景示例 @Te ...

  4. spring boot:java.lang.IllegalStateException异常

    1.单元测试报错: java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]. Specify @B ...

  5. java ajax点击出现_jquery ajax获取json数据 然后action 的response 返回数据出现 java.lang.IllegalStateException异常...

    程序运行出现这样的异常: 严重: Servlet.service() for servlet default threw exception java.lang.IllegalStateExcepti ...

  6. 转:java.lang.IllegalStateException异常产生的原因及解决办法

    地址:http://jorton468.blog.163.com/blog/static/72588135201102441617287/ 问题描述: 错误类型大致为以下几种: java.lang.I ...

  7. Eureka进行服务调用服务报 java.lang.IllegalStateException: No instances available for XXXX 异常的解决方案。附上完整的代码案例

    使用RestTemplate进行微服务调用,报了一个java.lang.IllegalStateException异常 第一个可能原因 服务提供者 我这里导致的错误原因是没有进行服务发现,导致使用Re ...

  8. java.lang.IllegalStateException: Not on FX application thread

    问题描述 运行javaFX程序,系统会自动创建一个FX application thread线程,用于更新界面的组件信息,例如ListView的items.Label的text.当我们想运用多线程实现 ...

  9. 记录一次gilde引起的异常:(java.lang.IllegalStateException: Cannot pool recycled bitmap)

    遇到的log日志打印如下: 12-15 16:55:50.435 20360-20360/com.ysb E/AndroidRuntime: FATAL EXCEPTION: mainProcess: ...

最新文章

  1. [投稿]通过Web界面在多台服务器上批量创建文件
  2. powerdesigner生成php代码,让powerdesigner生成的sql语句在mysql上成功执行
  3. PCA和白化练习之处理图像
  4. PHP7扩展开发(二):配置项与全局数值
  5. Java中恒等条件判断:“equals”和“==”
  6. Python---寻找给定序列中相差最小的两个数字
  7. CNN中的卷积操作与权值共享
  8. 有向图生成树是如何画的_漫画:什么是最小生成树?
  9. php 三元预算? :_项目预算:一种反模式
  10. 基于 YOLOV3 和 OpenCV的目标检测
  11. 微型计算机的输出有,微型计算机必不可少的输入/输出设备是()。 - 百科题库网...
  12. atitit 数据库mysq启动不起来解决方案.docx
  13. 《英雄联盟》支撑最高750万同时在线用户的聊天服务打造
  14. vue项目使用i18n插件实现多语言切换功能
  15. 反汇编linux内核,如何反汇编linux固件内核
  16. C case和UVM TB的交互,tube_print, event_sync
  17. 尚硅谷韩顺平Linux教程学习笔记
  18. R的可视化以及ggplot2
  19. 【边做项目边学Android】手机安全卫士10-设置向导之绑定SIM卡
  20. 几款科学计算显卡对比(GTX Titan X、GTX 980、Tesla K40 K80 及quadro K4200) 科学计算显卡的两个主要性能指标: 1、CUDA compute capabili

热门文章

  1. Java程序员面试如何超常发挥?
  2. linux 中输入一个c程序,从c源程序到Linux可执行代码的过程
  3. python的迭代器for_python特性(二):迭代器与for语句
  4. python核心装饰_Python核心编程 | 装饰器
  5. 取消默认html打开文档,怎么取消mac默认打开文档方式
  6. Apriltag可用图片:TAG16H5
  7. 全国大学生智能汽车竞赛 --智慧物流创意组
  8. 从冲撞避让到碾压,竞争模式发生了改变
  9. 配置腾讯云服务器-2021-3-27
  10. 第十五届全国大学生华东赛赛区开赛啦