最近因为一个项目,需要做统一的下载,并且要支持批量下载..其中涉及到的知识点有: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 pagetitle>
<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">
head>
<body>
<hr>
<h3>欢迎光临下载区h3>
<a 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.jspresult>
<result name="error">/downloadlisterror.jspresult>
action>
<action name="download" class="cn.edu.cuit.disastersystem.web.struts2.action.downloadaction">
<result name="success" type="stream">
<param name="contenttype">application/octet-stream;charset=iso8859-1param>
<param name="contentdisposition">
attachment;filename=${filename}
param>
<param name="inputname">downloadfileparam>
<param name="buffersize">4096param>
result>
<result name="input">/downloadlist.jspresult>
<result name="error">/downloadlisterror.jspresult>
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 heretitle>
<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="key" />,<s:property value="value" />">
td>
<td>
<s:property value="key" />
td>
<td>
<a href="#"
onclick="downloadfile1('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="key" />,<s:property value="value" />">
td>
<td>
<s:property value="key" />
td>
<td>
<a href="#"
onclick="downloadfile1('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配置文件attachment;filename=${filename}
* 要想正确的显示中文文件名,我们需要对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的downloadfile
* 但是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=测试下载&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的
downloadfile
, 但是struts2后台不可能一次性将我们的输入流输出到输出流里面.. 而我们也就是不好控制,例在何时删除产生的临时文件,而且我上面删除临时文件的时候出错.(所有下面有一个struts2的工作流程,欢迎大家来讨论,指教,学习)
2.就下载的时候,如果用普通的window对话框形式来下载,一切正常.而我们用迅雷下载的时候,产生两个临时文件,当时把我雷惨了...后来打断点测试,确实迅雷下载的时候是重新发出了一次请求,虽然对下载无影响,但打包下载本身就比较慢,这样就对下载的性能有很大的影响,这也是我下面要问的问题
3.打包下载性能真的很差,有没有更好的批量下载方法,请大家指出..谢谢
四.讨论struts2流程
1.我加载struts2的filterdispatcher类的init()方法处打下断点,可以明显看出从tomcat到struts2工作的整个流程,大家都看看,把学到的跟小弟共享下.
2. 一个傻傻的问题,但是要真正把它弄清楚也不容易,servlet,filter,intercept,struts2工作底层到底有何联系..
请高手多多指教!!!!
名称: ♪4c.esl | .↗evon
口号: 遇到新问题♪先要寻找一个方案乄而不是创造一个方案こ
mail: