批量下载的实现及java.lang.IllegalStateException异常

简介:

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

  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,如需转载请自行联系原作者


相关文章
|
1月前
|
Java
Java中的异常链:从根源到解决方案
Java中的异常链:从根源到解决方案
35 0
|
1月前
|
存储 监控 Java
Java认识异常(超级详细)
Java认识异常(超级详细)
|
3月前
|
Java 程序员 数据库连接
JAVA中的异常
Throwable Error Exception 编译时异常 运行时异常 异常的处理 try-catch捕获并处理 finally throw throws 自定义异常类
26 0
|
28天前
|
SQL Java
java中的异常
java中的异常
9 1
|
28天前
|
Java 程序员 编译器
Java中异常
Java中异常
12 0
|
28天前
|
Java 程序员 编译器
Java中的异常
Java中的异常
9 0
|
28天前
|
Java
Java异常的抛出
Java异常的抛出
8 0
|
1月前
|
Java 索引
JAVA异常类及其主要方法
JAVA异常类及其主要方法
28 3
|
1月前
|
Java
JAVA异常概述
JAVA异常概述
8 1
|
1月前
|
Java 程序员 数据安全/隐私保护
Java中的异常语法知识居然这么好玩!后悔没有早点学习
Java中的异常语法知识居然这么好玩!后悔没有早点学习
32 1