zoukankan      html  css  js  c++  java
  • 批量下载的实现及java.lang.IllegalStateException异常

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

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

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

    /**
    	 * 文件下载
    	 * @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 = p_w_uploadDao.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","p_w_upload;" +
    				// "filename=" + new String(fileName.getBytes("ISO_8859_1"),
    				// "UTF-8"));
    				// 如果使用上面编码格式,否则附件名称若为中文,则乱码,下载下来的附件名称,显示不全.或者乱乱码 .应该使用下面的编码格式.
    				response.setHeader("Content-Disposition", "p_w_upload;"
    						+ "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,然后循环调用下载方法。我的目的是在循环调用中多个文件依次下载。但测试结果表明这种方法是行不通的。在第一个文件下载结束后,该方法在

    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配置修改为如下:

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

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

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

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

    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的输出流中。

    for(int i = 0;i<ids.length; i++){
    				AttachmentEntity aEntity = p_w_uploadDao.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","p_w_upload;" +
    				// "filename=" + new String(fileName.getBytes("ISO_8859_1"),
    				// "UTF-8"));
    				// 如果使用上面编码格式,否则附件名称若为中文,则乱码,下载下来的附件名称,显示不全.或者乱乱码 .应该使用下面的编码格式.
    				response.setHeader("Content-Disposition", "p_w_upload;"
    						+ "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);
    				}
     

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

    /**
    	 * 
    	 * 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;  
        }
     

      字节流写入文件:

    /**
    	 * 
    	 * 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打包:

    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;
    	}
     
  • 相关阅读:
    oracle查询哪些sp修改了某些表
    asp.net mvc
    更新计算机驱动
    instr函数的用法
    UNION ALL UNION
    Python机器学习ch02 代码学习2
    Python机器学习 ch02代码学习1
    转载Python切片(小知识点)
    FMCW部分资料连接
    Python基础25 异常堆栈跟踪,释放资源,自定义异常和主动抛出
  • 原文地址:https://www.cnblogs.com/awzh2020/p/12615655.html
Copyright © 2011-2022 走看看