strtus2 批量下载 中文问题、压缩文件等 ------ 讨论struts2工作流程
2010-05-17 19:53
756 查看
最近因为一个项目,需要做统一的下载,并且要支持批量下载..其中涉及到的知识点有: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
>
<
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.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
>
<
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
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工作底层到底有何
联系..
请高手多多指教!!!!
理,删除临时文件,使用迅雷下载后台发出两次次下载请求,以及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
>
<
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.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
>
<
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
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工作底层到底有何
联系..
请高手多多指教!!!!
相关文章推荐
- strtus2 批量下载 中文问题、压缩文件等 ------ 讨论struts2工作流程 (摘自:http://www.blogjava.net/xcp/archive/2009/10/30/downloadlist.html)
- strtus2 批量下载 中文问题、压缩文件等 ------ 讨论struts2工作流程
- struts2文件下载中文乱码问题彻底解决
- Struts2 文件下载及中文乱码问题的解决方案
- struts2 文件下载文件名中文乱码及变成空格问题
- 工作问题:http下载文件,中文文件名在firefox下乱码问题
- struts2解决文件上传下载中文乱码问题
- struts2文件上传下载(含中文编码问题)
- struts2 文件下载方式 此法解决下载中文文件问题。不错
- Struts2学习9--中文文件下载的问题
- struts2 文件下载(修正中文问题)
- Struts2 .apk 文件下载及解决中文文件名乱码问题(转)
- Struts2 文件下载及中文乱码问题的解决方案
- Struts2文件下载时,中文文件名和chorm不兼容问题
- Struts2 文件下载中文文件名乱码问题
- struts2 Annotation 实现文件下载功能 文件名中文乱码问题
- struts2 文件下载方式 此法解决下载中文文件问题
- struts2文件下载 注意中文问题
- 使用struts2实现下载功能遇到的文件中文问题
- 文件下载--Struts2的中文文件下载显示为空格问题