strtus2 批量下载 中文问题、压缩文件等 ------ 讨论struts2工作流程 (摘自:http://www.blogjava.net/xcp/archive/2009/10/30/downloadlist.html)

最近因为一个项目,需要做统一的下载,并且要支持批量下载..其中涉及到的知识点有:get请求中文处理,下载动态设置下载名,批量下载,动态打包,流处理,删除临时文件,使用迅雷下载后台发出两次次下载请求,以及struts2工作流程与原理等..
       
       下面是我自己做的一个实例,主要实现遍历一个文件夹生成下载列表,用户可以单一下载,也可选择相关文件批量下载.....做的其中发现有很多疑惑的地方,请高手们指出....谢谢
      
      一.实例区
      1.index.html
      
<%@ page language="java" pageEncoding="gbk"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  
<head>
    
<base href="<%=basePath%>">
    
    
<title>My JSP 'index.jsp' starting page</title>
    
<meta http-equiv="pragma" content="no-cache">
    
<meta http-equiv="cache-control" content="no-cache">
    
<meta http-equiv="expires" content="0">    
    
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    
<meta http-equiv="description" content="This is my page">
    
<!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    
-->
  
</head>
  
  
<body>
           
<hr>
           
<h3>欢迎光临下载区</h3>
           
<href="downloadList.action">下载列表</a><br/>
  
</body>
</html>
      2.配置struts.xml
      
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd"
>
<struts>


    
<constant name="struts.custom.i18n.resources" value="message"></constant>
    
<constant name="struts.i18n.encoding" value="gbk"></constant>
    
<constant name="struts.multipart.saveDir" value="/tmp"></constant>
    
<constant name="struts.multipart.maxSize" value="209715200" />
    
    
<package name="struts2" extends="struts-default">
    
    
        
<action name="downloadList" class="cn.edu.cuit.disasterSystem.web.struts2.action.DownloadListAction">
            
<result name="success">/downloadList.jsp</result>
            
<result name="error">/downloadListError.jsp</result>        
        
</action>
    
    
        
            
<action name="download" class="cn.edu.cuit.disasterSystem.web.struts2.action.DownloadAction">
            
<result name="success" type="stream">
                
<!-- contentType为二进制方式 -->
                
<param name="contentType">application/octet-stream;charset=ISO8859-1</param>
                
<!-- attachment属性强调是下载,就不会主动打开,比如图片 -->
                
<!-- 使用经过转码的文件名作为下载文件名,downloadFileName属性对应action类中的方法 getDownloadFileName() -->
                
<param name="contentDisposition">
                    attachment;filename=${filename}
                
</param>
                
<param name="inputName">downloadFile</param>
                
<param name="bufferSize">4096</param>
            
</result>
            
<result name="input">/downloadList.jsp</result>
            
<result name="error">/downloadListError.jsp</result>
        
</action>
    
</package>
</struts>
      3.产生下载列表的Action----DownloadListAction
package cn.edu.cuit.disasterSystem.web.struts2.action;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

/**
 * 显示所有down目录的文件,供下载所用
 * 
@author  xcp
 * 
@version  1.0
 * Copyright (C), 2009 智能开发实验室 所有
 * Program Name:灾情信息管理系统
 * Date: 2009-10-24 上午11:16:41
 
*/
@SuppressWarnings(
"serial")
public class DownloadListAction  extends ActionSupport{
    
    
private static ArrayList<String> filelist = new ArrayList<String>();
    
    
    
/**
     *    可以是前台一个页面传入,也可以是手动指定,其作用是指定下载文件的根目录
     * 
@author  向才鹏
     * 2009-10-24 下午12:02:47
     
*/
    
private String downloadRootPath = "/upload";
    
    
public String getDownloadRootPath() {
        
return downloadRootPath;
    }
    
public void setDownloadRootPath(String downloadRootPath) {
        
this.downloadRootPath = downloadRootPath;
    }


    
/**
     * 将指定文件路径下的文件全部遍历出来 
     * 
@author 向才鹏
     * 
@param strPath 指来要遍历的文件
     * 2009-10-24   下午12:04:48
     
*/
    
public static void refreshFileList(String strPath)
    {
        File dir 
= new File(strPath);
        File[] files 
= dir.listFiles();

        
if (files == null)
            
return;
        
for (int i = 0; i < files.length; i++)
        {
            
if (files[i].isDirectory())
            {
                refreshFileList(files[i].getAbsolutePath());
            } 
else
            {
                String filePath 
=  files[i].getPath();
                filelist.add(filePath);
            }
        }
    }

    
    
    
/**
     * 格式化输出数据存入Map,形式文件名+文件服务端路径
     * 
@author 向才鹏
     * 
@param filelist 遍历出来的文件路径
     * 
@param downloadRootPath 指明服务器下载的文件,便于从遍历出来的文件中取得服务端路径
     * 
@return
     * 2009-10-24   下午12:06:18
     
*/
    
private static Map<String,String> formatFileMap(ArrayList<String> filelist,String downloadRootPath){
        Map
<String,String> formatFileMap = new HashMap<String,String>();
        
//得到服务下载的根路径,并将/换成\\,这样便于替换
        String formatDownloadRootPath =  downloadRootPath.replaceAll("/""\\\\");
        
for(String filePath : filelist){
            
//得到下载的相对路径
            String  downloadPath = filePath.substring(filePath.indexOf(formatDownloadRootPath));
            
//将得到的相对路径的\\转换成/
            String formatDownloadPath = downloadPath.replaceAll("\\\\""/");
            
//得到文件名
            String filename = formatDownloadPath.substring(formatDownloadPath.lastIndexOf("/")+1);
        
            
/*try {
                formatFileMap.put(filename, URLEncoder.encode(formatDownloadPath, "gbk"));
            } catch (UnsupportedEncodingException e) {
                formatFileMap.put(filename, formatDownloadPath);
                e.printStackTrace();
            }
*/
            
            
//这就不用考虑设置编码了,再后面统一使用javascript的encodeURI函数
            formatFileMap.put(filename, formatDownloadPath);
            
        }
        
return formatFileMap;
    }
    
    
    @SuppressWarnings(
"unchecked")
    @Override
    
public String execute() throws Exception {
        
        
//指定下载目录
        String upload = ServletActionContext.getServletContext().getRealPath(downloadRootPath);
        
//清理filelist
        filelist.clear();
        
//遍历文件
        refreshFileList(upload);
        
        ActionContext context 
= ActionContext.getContext();
        Map request 
= (Map) context.get("request");
        
        
        
if(filelist != null){
            
//格式化文件信息,包括文件名和地址
            Map<String,String> formatFileMap = formatFileMap(filelist,downloadRootPath);
            request.put(
"fileMap", formatFileMap);
            
return SUCCESS;
        }
        
else{
            request.put(
"errorMessage""没有相关的下载文件");
            
return ERROR;
        }
            
    }
    
    
}
      4.显示下载列表downloadList.jsp
<%@ page language="java" contentType="text/html; charset=gbk"
    pageEncoding
="gbk"%>

<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<script type="text/javascript">
    
function downloadFile1(filenames,filepaths){
        location.href
=encodeURI("download.action?filenames="+filenames+"&filepaths="+filepaths);
    }
    
function SelectAll(oForm)
    {
        
for(var i=0;i<oForm.url.length;i++)
        {
            oForm.url[i].checked
=true;
        }
    }
    
function TurnOver(oForm)
    {
        
for(var i=0;i<oForm.url.length;i++)
        {
            oForm.url[i].checked
=!oForm.url[i].checked;
        }
    }
    
function DownlodSelected(oForm){
         
if(confirm("因需要在服务端动态打包,需要时间比较长,是否继续批量下载?"))
            {
             
var arrDownloadList = [];
            
for(var i=0;i<oForm.url.length;i++){
                
if(oForm.url[i].checked==true){
                    
if(arrDownloadList.length==0){
                        arrDownloadList[
0= oForm.url.value;
                    }
                    arrDownloadList[arrDownloadList.length] 
= oForm.url[i].value;
                }
            }
            
if (arrDownloadList.length>0){
                
var temp = [];
                
var filenames="";
                
var filepaths="";
                
for(var i=1;i<arrDownloadList.length;i++){
                    temp 
= arrDownloadList[i].split(",")
                    
if(filenames=="" && filepaths==""){
                        filenames
=temp[0]
                        filepaths
=temp[1]
                    }
else{    
                        filenames
=filenames+"|"+temp[0];
                        filepaths
=filepaths+"|"+temp[1];
                    }
                }
                downloadFile1(filenames,filepaths);
            }
else{
                alert(
"还没有选中下载项");
            }
           }
    }
</script>
<html>
    
<head>
        
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
        
<title>Insert title here</title>
        
<script type="text/javascript" src="dwr/engine.js"></script>
        
<script type="text/javascript" src="dwr/util.js"></script>
        
<script type="text/javascript" src="dwr/interface/downloaddwr.js"></script>
    
</head>
    
<body>
        
<form name="myform" style="display: inline" onSubmit="return false">
            
<table width="50%" align="center">
                
<tr>
                    
<td colspan="2">
                        
<h3>
                            以后是下载列表,点击进行下载
                        
</h3>
                    
</td>
                
</tr>
                
<tr>
                    
<td colspan="2">
                        
<font color="red"><s:fielderror></s:fielderror> </font>
                    
</td>
                
</tr>
                
<s:iterator value="#request.fileMap" status="stuts">
                    
<s:if test="#stuts.odd == true">
                        
<tr style="background-color: #77D9F6">
                            
<td>
                                
<input name="url" type="checkbox" id="url"
                                    value
="<s:property value="key" />,<s:property value="value" />">
                            
</td>
                            
<td>
                                
<s:property value="key" />
                            
</td>
                            
<td>
                                
<href="#"
                                    onclick
="downloadFile1('<s:property value="key" />','<s:property value="value" />')">点击下载</a>
                            
</td>
                        
</tr>
                    
</s:if>
                    
<s:else>
                        
<tr style="background-color: #D7F2F4">
                            
<td>
                                
<input name="url" type="checkbox" id="url"
                                    value
="<s:property value="key" />,<s:property value="value" />">
                            
</td>
                            
<td>
                                
<s:property value="key" />
                            
</td>

                            
<td>
                                
<href="#"
                                    onclick
="downloadFile1('<s:property value="key" />','<s:property value="value" />')">点击下载</a>
                            
</td>
                        
</tr>
                    
</s:else>
                
</s:iterator>
            
</table>
            
<div align="center">
                
<input class="green_at_bn" title="选择下载的文件"
                    onClick
="SelectAll(this.form)" type="button" value="全选">
                
<input class="green_at_bn" title="反向选择下载文件"
                    onClick
="TurnOver(this.form)" type="button" value="反选">
                
<input class="green_at_bn" title="下载选中文件"
                    onClick
="DownlodSelected(this.form)" type="button" value="批量下载文件">
            
</div>
        
</form>
        
<frame src="" id="dis">

        
</frame>
    
</body>
</html>
      5.统一处理下载的Action----DownloadAction
package cn.edu.cuit.disasterSystem.web.struts2.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;


import org.apache.struts2.ServletActionContext;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

import com.opensymphony.xwork2.ActionSupport;

/**
 * 统一下载类
 * 
 * 
@author xcp
 * 
@version 1.0 Copyright (C), 2009 智能开发实验室 所有 Program Name:灾情信息管理系统
 *          Date: 2009-10-30 上午09:06:01
 
*/
@SuppressWarnings(
"serial")
public class DownloadAction extends ActionSupport {

    
private String   filenames;
    
private String   filepaths;
    
private String[] filenameArray = null;
    
private String[] filepathArray = null;
    
private String   filename;
    
private String   filepath;
    
private SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        
    
    
/**
     * 得到客户端请求的文件名字符串
     * 
@author 向才鹏
     * 
@return 客户端请求的文件名字符串
     * 2009-10-30   下午11:21:31
     
*/
    
public String getFilenames() {
        
return filenames;
    }
    
/**
     * 将客户端请求的文件名字符串set到filenames变量
     * 
@author 向才鹏
     * 
@param filenames
     * 2009-10-30   下午11:21:34
     
*/
    
public void setFilenames(String filenames) {
        
this.filenames = filenames;
        
if (this.filenames.contains("|")) {
            parseFilenamesToArray();
        }
    }
    
/**
     * 得到客户端请求的文件路径字符串
     * 
@author 向才鹏
     * 
@return 客户端请求的文件路径字符串
     * 2009-10-30   下午11:21:37
     
*/
    
public String getFilepaths() {
        
return filepaths;
    }
    
/**
     * 将客户端请求的文件路径字符串set到filepaths变量
     * 
@author 向才鹏
     * 
@param filepaths
     * 2009-10-30   下午11:21:40
     
*/
    
public void setFilepaths(String filepaths) {
        
this.filepaths = filepaths;
        
if (this.filepaths.contains("|")) {
            parseFilepathsToArray();
        }
    }
    
    
    
    
/**
     * 解析客户端请求下载的文件名
     * 
@author 向才鹏
     * 2009-10-30   下午11:23:43
     
*/
    
public void parseFilenamesToArray() {
        filenameArray 
= filenames.split("\\|");
    }
    
/**
     * 解析客户端请求下载的文件路径
     * 
@author 向才鹏
     * 2009-10-30   下午11:23:46
     
*/
    
public void parseFilepathsToArray() {
        filepathArray 
= filepaths.split("\\|");
    }
    
    
      
    
    
/**
     *  得到下载显示名,对就struts.xml配置文件<param name="contentDisposition">attachment;filename=${filename}</param>
     *  要想正确的显示中文文件名,我们需要对fileName再次编码 否则中文名文件将出现乱码,或无法下载的情况
     * 
@author 向才鹏
     * 
@return 返回下载显示名
     * 2009-10-30   下午11:26:49
     
*/
    
public String getFilename() {
        
try {
            
return new String(filename.getBytes(), "ISO-8859-1");
        } 
catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            
return filename;
        }
    }
    
    
    
/** 
     *  得到下载文件路径
     * 
@author 向才鹏
     * 
@return 返回下载路径
     * 2009-10-30   下午11:27:52
     
*/
    
public String getFilepath(){
        
return filepath;
    }
    
    
/**
     * 初始化下载文件名
     * 
@author 向才鹏
     * 2009-10-30   下午11:29:00
     
*/
    
public void initFilename() {
        
if(isBaleZip()){
            
this.filename = "批量打包下载.zip";
        }
else{
            
this.filename = getFilenames();
        }
        System.out.println(
"下载文件名:    "+filename);
    }
    
    
    
/**
     *  初始化下载路径
     * 
@author 向才鹏
     * 2009-10-30   下午11:30:04
     
*/
    
public void initFilepath() {
        
if(isBaleZip()){
            String rootpath 
= ServletActionContext.getServletContext().getRealPath("/upload/temp");
            String requestip 
= ServletActionContext.getRequest().getLocalAddr();
            
//this.filepath = "c:\\批量打包下载.zip";
            this.filepath = rootpath+"\\"+requestip+"-"+format.format(new Date())+".zip";
        }
else{
            
this.filepath = getFilepaths();
        }
        System.out.println(
"下载文件路径:    "+filepath);
    }

    
    
/**
     * 判断是否符合打包要求
     * 
@author 向才鹏
     * 
@return 否符合打包要求
     * 2009-10-30   上午11:36:09
     
*/
    
public  boolean isBaleZip(){
        
boolean isZip = false;
        
if(this.filenameArray!= null && this.filepathArray!= null && this.filenameArray.length>0 && this.filenameArray.length==this.filepathArray.length){
             isZip 
=  true;
        }
        
return isZip;
    }
    
    
    
/**
     * 压缩文件
     * 
@author 向才鹏
     * 
@param zipFilePath  产生的压缩文件路径和名字
     * 
@param names        传入要进行打包的所有文件名
     * 
@param paths        传入要进行打包的所有文件路径
     * 
@throws IOException
     * 2009-10-30   下午11:39:14
     
*/
    
public void baleZip(String zipFilePath,String[] names,String[] paths) throws IOException{
        File f 
= new File(zipFilePath);
        f.createNewFile();
        ZipOutputStream out 
= new ZipOutputStream(new FileOutputStream(f));
        out.putNextEntry(
new ZipEntry("/"));
        
for(int i=0;i<paths.length;i++){
            out.putNextEntry(
new ZipEntry(names[i])); 
            InputStream in 
=ServletActionContext.getServletContext().getResourceAsStream(paths[i]);
            
int b;
            
while ((b = in.read()) != -1) {
                out.write(b);
            }
            in.close();
        }
         out.flush();
         out.close();
    }
    
    
    
    
    
/**
     *  返回目标下载文件输入流跟struts2,然后struts2再生成输出流,对应struts.xml的<param name="inputName">downloadFile </param>
     *  但是struts2后台不可能一次性将我们的输入流输出到输出流里面.. 而我们也就是不好控制,例在何时删除产生的临时文件
     * 
@author 向才鹏
     * 
@return 目标下载文件输入流
     * 2009-10-30   上午11:45:29
     
*/
    
public InputStream getDownloadFile(){
        initFilename();
        initFilepath();
        InputStream in 
= null;
        File tempfile 
= null;
        
if(isBaleZip()){
            
try {
                baleZip(
this.filepath,this.filenameArray,this.filepathArray);
                tempfile 
= new File(this.filepath);
                in 
=  new FileInputStream(tempfile);
                
            } 
catch (IOException e) {
                System.out.println(e.getMessage()
+"   "+"压缩文件出错!!");
                
return null;
            } 
finally{
                
if(tempfile.exists()){
                    tempfile.delete();
                    
if(tempfile.exists()){
                        System.out.println(
"------删除临时文件失败-------");
                    }
else{
                        System.out.println(
"------删除打包产生的临时文件------");
                    }
                }
            }
        }
else{
            in  
= ServletActionContext.getServletContext().getResourceAsStream(getFilepath());
        }
        
return in;
    }
    
    
    
    
/**
     * 而这种文件下载方式却是存在安全隐患的, 因为访问者如果精通Struts2的话,它可能使用这样的带有表单参数的地址来访问:
     * 
http://localhost:8080/disasterSystem/download.action?filename=%E6%B5%8B%E8%AF%95%E4%B8%8B%E8%BD%BD&filepath=/WEB-INF/web.xml
     * 这样的结果就是下载后的文件内容是您系统里面的web.xml的文件的源代码,甚至还可以用这种方式来下载任何其它JSP文件的源码, 这对系统安全是个很大的威胁。
     * 作为一种变通的方法,读者最好是从数据库中进行路径配置,然后把Action类中的设置inputPath的方法统统去掉,简言之就是所有set方法定义
     * 第二种方法,读者可以在execute()方法中进行路径检查,如果发现有访问不属于download下面文件的代码,就一律拒绝,不给他们返回文件内容。
     * 
     * 
@author 向才鹏
     * 
@param filepath
     *            2009-10-30 上午09:34:43
     
*/
    @Override
    
public String execute() throws Exception {
        
// 文件下载目录路径
        String downloadDir = "/upload";
        
// 发现企图下载不在 /download 下的文件, 就显示空内容
        if (!filepaths.startsWith(downloadDir)) {
            
// 可以抛出一些异常信息
            System.out.println("只能下载upload里面的东西,谢谢!");
            
return ERROR;
        }
        
return SUCCESS;
    }
}

      二.  说明区
       1.get请求中文处理参见:http://www.blogjava.net/xcp/archive/2009/10/29/download2.html
       2.文件打包参见:http://www.blogjava.net/xcp/archive/2009/10/30/CompressToZip.html 
      


      三.本人疑惑区
      1.getDownloadFile()返回目标下载文件输入流跟struts2,然后struts2再生成输出流,对应struts.xml的<param name="inputName">downloadFile </param>
 , 但是struts2后台不可能一次性将我们的输入流输出到输出流里面.. 而我们也就是不好控制,例在何时删除产生的临时文件,而且我上面删除临时文件的时候出错.(所有下面有一个struts2的工作流程,欢迎大家来讨论,指教,学习)
     2.就下载的时候,如果用普通的window对话框形式来下载,一切正常.而我们用迅雷下载的时候,产生两个临时文件,当时把我雷惨了...后来打断点测试,确实迅雷下载的时候是重新发出了一次请求,虽然对下载无影响,但打包下载本身就比较慢,这样就对下载的性能有很大的影响,这也是我下面要问的问题
     3.打包下载性能真的很差,有没有更好的批量下载方法,请大家指出..谢谢




      四.讨论struts2流程
      1.我加载struts2的FilterDispatcher类的init()方法处打下断点,可以明显看出从tomcat到struts2工作的整个流程,大家都看看,把学到的跟小弟共享下.
      2. 一个傻傻的问题,但是要真正把它弄清楚也不容易,Servlet,Filter,Intercept,Struts2工作底层到底有何联系..

      请高手多多指教!!!!
posted on 2009-12-14 10:05  草原和大树  阅读(3176)  评论(0编辑  收藏  举报