一般网站都会提供文件的上传与下载的功能,尤其是资料管理型网站。刚好在工作中需要用到,就提前学习了一下,并建了一个maven工程做练习。
1. 本工程使用maven创建工程,是为了省去包导入细节,其中maven工程的pom.xml文件主要如下:
<!-- struts2 core -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.3.16.3</version>
</dependency>
<!-- struts2注解包依赖,实现零配置 -->
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>2.3.16.3</version>
</dependency>
引入struts2-convention-plugin,是为了使用注解,从而实现零配置,实例中将看到这个好处。仔细查看struts2-core的pom.xml,可以发现如下依赖
<!-- File upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
可见,struts2.x已经默认使用Jakarta的Components-FileUpload组件作为其文件上传的框架。
2. 了解表单enctype属性
在表单元素<form>中有一个属性enctype是用来设置上传数据的编码方式,enctype属性规定在发送到服务器之前浏览器应该对表单数据进行何种编码。默认的,表单数据会编码为application/x-www-form-urlencoded。具体enctype有是3个值,含义具体如下
值 | 描述 |
application/x-www-form-urlencoded | 在发送前编码所有字符(默认) 该种方式主要用于能输出网页的应用。当传送的内容包含大量的非ASCII字符的文本或二进制数据时,效率比较低 |
multipart/form-data | 不对字符编码。 在使用包含文件上传控件的表单时,必须使用该值。 该方式主要用于文件上传等应用,当利用该种方式上传文件时,首先会把数据转化成二进制数据,然后才会进行上传 |
text/plain | 空格转换为 "+" 加号,但不对特殊字符编码。该方式主要用于电子邮件方面的应用 |
从上述表中可以看出,只有把属性enctype的值设置为multipart/form-data,就能实现文件上传。
3. struts.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<!-- 常量配置-->
<constant name="struts.convention.package.locators" value="web,action" />
<constant name="struts.convention.package.locators.basePackage" value="com.ljx" />
<constant name="struts.convention.action.name.lowercase" value="true" />
<constant name="struts.convention.action.name.separator" value="-" />
<constant name="struts.date.format" value="yyyy-MM-dd HH:mm:ss" />
<!--<constant name="struts.convention.default.parent.package" value="ljx-default" />-->
<constant name="struts.convention.result.path" value="/WEB-INF/views" />
<constant name="struts.convention.action.includeJars" value=".*/struts*.*jar(!/)?" />
<!-- 存放上传文件的临时位置 -->
<constant name="struts.multipart.maxSize" value="10701096"/>
<constant name="struts.multipart.saveDir" value="D:/Download/tmp"/>
<!-- 调试模式 -->
<constant name="struts.devMode" value="false" />
<constant name="struts.convention.classes.reload" value="true" />
<!-- 其余的零配置 -->
</struts>
使用struts2-convention-plugin,只需要再struts.xml中配置一些常量值,其余的元素<package>,<interceptor>等都不需要在这里配置。
由简到难,接下来先介绍单文件上传
4. 单文件上传
前端jsp文件如下
<body background="${images}/bonjour-bg.jpg">
<form name="file-upload-form" action="${ctx}/file/upload.action" method="post" enctype="multipart/form-data">
<br/>
选择上传的文件:
<input type="file" name="upload"/><br/><br/>
<input type="submit" value="提交"/>
</form>
</body>
备注,上传文件,form提交方式不能为Get,而应该为post。使用原生的html<input type=”file”>。题外话,因为struts-tag标签有过安全漏洞,所以组里开发严禁使用,楼主小生,不是特别理解xss漏洞原理,特别摘引了一篇大作(http://huaidan.org/archives/3433.html),留作后续研读。当然,这里只是作为输入,所以应该使用<s:file>没有太大问题的。
后台Action如下:
/**
* 文件上传
*
* @author linjx
* @date 2014年7月17日 下午5:13:12
* @version: 1.0.0
*/
@ParentPackage(value = "struts-default")
public class FileUploadAction {
/**
* 上传的文件
*/
private File upload;
/**
* 文件名称
*/
private String uploadFileName;
/**
* 文件类型
*/
private String uploadContentType;
@Action(value = "/file/upload")
public String saveUploadFile() {
//String realpath = ServletActionContext.getServletContext().getRealPath("/images"); //需要依赖javax.servlet.jar
String realpath = "D:/download/download"; //指定存储位置
System.out.println("realpath: " + realpath);
if (CommonUtils.isNotNull(upload)) {
File saveFile = new File(new File(realpath), uploadFileName);
if (!saveFile.getParentFile().exists()) {
saveFile.getParentFile().mkdirs();
}
try {
org.apache.commons.io.FileUtils.copyFile(upload, saveFile);
} catch (IOException e) {
e.printStackTrace();
}
ActionContext.getContext().put("message", "文件上传成功");
}
return ActionSupport.SUCCESS;
}
public File getUpload() {
return upload;
}
public void setUpload(File upload) {
this.upload = upload;
}
public String getUploadFileName() {
return uploadFileName;
}
public void setUploadFileName(String uploadFileName) {
this.uploadFileName = uploadFileName;
}
public String getUploadContentType() {
return uploadContentType;
}
public void setUploadContentType(String uploadContentType) {
this.uploadContentType = uploadContentType;
}
}
通过@ParenPackage和@Action注解,实现类似
<package name="file" extends="struts-default" namespace="/file ">
<action name=" saveUploadFile " class="……. FileUploadAction ">
<result>../upload.jsp</result>
<result name="input">../file-upload.jsp</result>
</action>
</package>
显然,注解的优势就是简练。
结果返回upload.jsp代码如下:
<body background="${images}/bonjour-bg.jpg">
<br/><br/>
<h2>${message}</h2>
<p>${uploadFileName}</p>
<hr />
</body>
具体运行结果如下:
选定上传文件,点击提交,后台报错:
WARNING: Request exceeded size limit!
org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (29488656) exceeds the configured maximum (10701096)
非常直观,是因为上传文件超过在struts.xml文件中设定的上传文件最大值。我们选择一个小文件重新上传,并观察文件上传的流程,打调试断点于FileUtils.copyFile(upload, saveFile)行上,如下
可以发现,程序运行到这一步
D:/Download/tmp路径下会有个临时文件,
继续运行,则该临时文件会主动清除;文件成功上传到D:/Download/download文件夹下,文件名保持不变。
跳转到upload.action,显示如下
5. 多文件上传
了解了单文件上传流程,就可以开始开发多文件上传。其实,没有太大的差别,只是多文件上传在表单提交时,由文件列表接收多个文件,表单上传后,会将多个文件缓存在临时路径,从操作上看,我们只需要将文件列表拷贝到相应的路径就行了。
具体代码如下:
上传jsp
<body background="${images}/bonjour-bg.jpg">
<form name="file-upload-form" action="${ctx}/file/multi-upload.action" method="post"
enctype="multipart/form-data">
<br/>
选择上传的文件:
<br/>
<input type="file" name="upload"/><br/>
<input type="file" name="upload"/><br/>
<input type="file" name="upload"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
返回upload.jsp不变
Action代码如下:
/**
* @author linjx
* @date 2014-7-17
* @version 1.0.0
*/
@ParentPackage(value = "struts-default")
public class MultiFileUploadAction {
/**
* 上传的文件
*/
private List<File> upload;
/**
* 文件名称
*/
private List<String> uploadFileName;
/**
* 文件类型
*/
private List<String> uploadContentType;
@Action(value = "/file/multi-upload", //view还是指向upload.jsp
results = {@Result(name=ActionSupport.SUCCESS, location="/WEB-INF/views/file/upload.jsp")})
public String saveUploadFile() {
//需要javax.servlet.jar
//String realpath = ServletActionContext.getServletContext().getRealPath("/images");
String realpath = "D:/Download/download";
System.out.println("realpath: " + realpath);
if (CommonUtils.isNotNull(upload)) {
if (CollectionUtils.isNotEmpty(upload)) {
for (int i = 0; i < upload.size(); i ++) {
File saveFile = new File(realpath, uploadFileName.get(i));
if (!saveFile.getParentFile().exists()) {
saveFile.getParentFile().mkdirs();
}
try {
FileUtils.copyFile(upload.get(i), saveFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
ActionContext.getContext().put("message", "文件上传成功");
}
return ActionSupport.SUCCESS;
}
public List<File> getUpload() {
return upload;
}
public void setUpload(List<File> upload) {
this.upload = upload;
}
public List<String> getUploadFileName() {
return uploadFileName;
}
public void setUploadFileName(List<String> uploadFileName) {
this.uploadFileName = uploadFileName;
}
public List<String> getUploadContentType() {
return uploadContentType;
}
public void setUploadContentType(List<String> uploadContentType) {
this.uploadContentType = uploadContentType;
}
}
上传结果如下,选定上传文件:
成功界面:
查看download路径下文件有:
就此,完成多文件上传。
备注:还可以通过fileUpload拦截器对上传的文件进行过滤。
@InterceptorRef(value="fileUpload",params= {"allowedTypes", "image/bmp, /image/gif"})这个注解只限于类级别,如果加上这个过滤器,png格式的图片上传将被过滤掉,并且后台并不会报错。