最近因为一个项目,需要做统一的下载,并且要支持批量下载..其中涉及到的知识点有:get请求中文处理,下载动态设置下载名,批量下载,动态打包,流处 理,删除临时文件,使用迅雷下载后台发出两次次下载请求,以及struts2工作流程与原理等..
下面是我自己做的一个实例,主要实现遍历一个文件夹生成下载列表,用户可以单一下载,也可选择相关文件批量下载.....做的其中发现有很多疑惑的地方, 请高手们指出....谢谢
一.实例区
1.index.html
<%
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 >
< a href ="downloadList.action" > 下载列表 </ a >< br />
</ body >
</ html >
2.配置struts.xml
<! 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
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
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 >
< a 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 >
< a 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
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工作底层到底有何 联系..
请高手多多指教!!!!