最近在做一个Struts2上传功能的模块,需求很明确
- 能控制上传文件类型
- 能根据上传文件类型控制上文件大小
- 所有要界面友好(容错,给予足够的提示)
一、开工前准备
1、了解struts2 上传原理
打开struts-default.xml我们发现,struts2在做文件上传时首先定义了struts.multipart.parser这一个专门用于解析上传文件的解析器,默认由JakartaMultiPartRequest实现,并且使用了fileUpload这个拦截器在请求尚未进入Action之前将文件从客户端浏览器读入服务器缓冲区,等待处理,也就是说在Action还未对文件作出处理之前Struts已经将文件上传完成,后续工作(主要是文件转移和存档)则交给开发者去完成(强大),FileUpload这个拦截器对应的类的名字就叫作FileUploadInterceptor.java,
<interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> ...<!-- 此处省略其他拦截器配置 --> </interceptor-stack>
在这个类的intercept()方法中,它首先是拿到了此次请求的对象主体HttpServletRequest
ActionContext ac = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
然后再把它强制转换成MultiPartRequestWrapper,也就是在request的外面再包裹一层,通过HttpServletRequestWrapper包装文件上传请求,模块化文件上传处理,提高代码复用率。MultiPartRequestWrapper持有被装饰者(HttpServletRequest对象)引用,初始化用于存放MultipartRequest的参数的Map,同时也持有MultiPartRequest的一个引用(其中的parse()方法)方便于解析上传和文件,而MultiPartRequest是一个接口,由JakartaMultiPartRequest来具体实现。
多媒体请求到达->进行包裹,转换成commons-fileupload能够接受的MultipartRequest对象->交由ServletFileUpload处理(建立连接管道等)
二、整体思路
监听上传状态并保存到Session中,在上传过程中客户端定时向服务器询问这个状态
好,问题来了,既然Struts在Action前已经将文件上传这一步的工作完成了,我们怎么去监听文件上传的进度?
--改默认配置!
改之前先创建一个替换对像,上文说过,在Struts中上传解析工作是由JakartaMultiPartRequest中的parse()具体来实现的(解析来自ServletFileUpload的流),可它并没有给提供定时更新上传状态的功能,所以我们要给它加一个监听器,监听其状态,监听器要实现apache的ProgressListener接口,这样可以让每一次状态的改变都能被这个监听器监听到。
开始着手做:
监听上传状态
复制一份JakartaMultiPartRequest.java取名:DerrickMultiPartRequest.java放到自己的一个包中,重写其parseRequest()方法如下:
private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException {
UploadStatus ups = new UploadStatus(); //UploadStatus自己新建的类
UploadingListener upadlistener = new UploadingListener(ups);//UploadingListener实现ProgressListener接口
DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
ServletFileUpload upload = new ServletFileUpload(fac);
upload.setSizeMax(maxSize);
// 设置进度监听器
upload.setProgressListener(upadlistener);
System.out.println("====================parseRequest setting listener completed =================");
return upload.parseRequest(createRequestContext(servletRequest));
}
//UploadStatus.java
public class UploadStatus {
private long readbytes;
private long contentLengh;
private int item;
//getters and setters are begin here ...
}
将状态保存到Session里
//UploadingListener.java
public class UploadingListener implements ProgressListener{
UploadStatus ups ;
public UploadingListener(UploadStatus ups){
this.ups = ups;
}
/* (non-Javadoc)
* @see org.apache.commons.fileupload.ProgressListener#update(long, long, int)
*/
@Override
public void update(long readbytes, long contentLengh, int itemIndex) {
ups.setContentLengh(contentLengh);
ups.setReadbytes(readbytes);
ups.setItem(itemIndex);
Map<String,Object> session = ServletActionContext.getContext().getSession();
session.put("processStatus", ups);
}
替换准备工作完成,下面着手编写Action
FileUploadAction 完成两个工作,文件转移和响应状态询问
//FileUploadAction.java
public class FileUploadAction extends BaseAction{
private static final long serialVersionUID = 1L;
private List<File> upload;
private List<String> uploadFileName;
private List<String> uploadContentType;
private IFileService fileService;
/*
* 文件上传分发
*/
public String upload() throws AuthorizationException, IOException{
String flag = "decline";
for (int i=0;i<upload.size();i++){
if(this.getUploadContentType().get(i).startsWith("image")){
flag = imgupload();
}else if(this.getUploadContentType().get(i).startsWith("application")){
flag = fileupload();
}else{
flag = "fileupload";
}
}
return flag;
}
/* (non-Javadoc)
* 除图片外其他类型文件上传
*/
public String fileupload() throws AuthorizationException ,IOException{
String root = ServletActionContext.getServletContext().getRealPath("/uploads");
InputStream is = null;
OutputStream os = null;
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < upload.size(); i++){
String str = uploadFileName.get(i).substring(uploadFileName.get(i).lastIndexOf("\\")+1);
File f = Common.CreateFile(root, str);
is = new FileInputStream(upload.get(i));
os = new FileOutputStream(f);
int len = 0;
byte[] buffer = new byte[1024];
while(len != -1){
len = is.read(buffer, 0, buffer.length);
os.write(buffer);
}
is.close();
os.close();
list.add(root.substring(root.lastIndexOf("\\")+1)+"\\"+str);
}
getSession().put("uploadList", list);
return SUCCESS;
}
/* (non-Javadoc)
* 上传图片方法
*/
public String imgupload() throws AuthorizationException ,IOException{
String root = ServletActionContext.getServletContext().getRealPath("/uploads/img");
InputStream is = null;
OutputStream os = null;
List<String> list = new ArrayList<String>();
for(int i = 0 ; i < upload.size(); i++){
String str = uploadFileName.get(i).substring(uploadFileName.get(i).lastIndexOf("\\")+1);
File f = Common.CreateFile(root, str);
is = new FileInputStream(upload.get(i));
os = new FileOutputStream(f);
int len = 0;
byte[] buffer = new byte[1024];
while(len != -1){
len = is.read(buffer, 0, buffer.length);
os.write(buffer);
}
is.close();
os.close();
list.add(root.substring(root.lastIndexOf("\\")+1)+"\\"+str);
}
getSession().put("uploadList", list);
return SUCCESS;
}
public void uploadStatus() throws Exception{
BigDecimal read ;
BigDecimal total;
BigDecimal bdpercent;
double percent = 0.00;
HttpServletResponse response = ServletActionContext.getResponse();
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
UploadStatus ups = (UploadStatus) getSession().get("processStatus");
if(null!=ups){
if(0 != ups.getReadbytes()){
read = new BigDecimal(ups.getReadbytes());
total = new BigDecimal(ups.getContentLengh());
bdpercent = read.divide(total, 2, BigDecimal.ROUND_UP);
percent = bdpercent.doubleValue();
}
}else{
System.out.println("ups is null");
}
out.print((int)(percent*100));
out.flush();
out.close();
//return null;
}
至此后台就改造完成了,配置一下:
//struts.xml定义要替换的MultiPartRequest的实现并让struts应用更改的实现而不使用其自带的Jakatra <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="derrick" class="com.llb.base.impl.DerrickMultiPartRequest" scope="default" /> <constant name="struts.multipart.parser" value="derrick" />
//定义自己的拦截器栈,注意一定要追加defaultStack这个栈,不然struts的其他拦截器是会在自己的这个栈之后执行,后果不堪设想 <interceptors> <interceptor-stack name="imageUploadStack"> <interceptor-ref name="fileUpload"> <param name="allowedTypes"> image/bmp,image/png,image/gif,image/jpeg,image/pjpeg,image/x-png </param> <param name="maximumSize">5242880</param><!-- 50M --> </interceptor-ref> <interceptor-ref name="defaultStack" /> </interceptor-stack> <interceptor-stack name="attachedUploadStack"> <interceptor-ref name="fileUpload"> <param name="allowedTypes"> application/octet-stream,application/zip,application/x-zip-compressed,application/x-rar-compressed </param> <param name="maximumSize">209715200</param><!-- 200M --> </interceptor-ref> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors>
//配置Action <action name="upload" class="fileUploadAction" method="upload" > <result>${appContext}/upload/form.jsp</result> <result name="input">${appContext}/upload/form.jsp</result> <result name="decline" type="redirect">${appContext}/login.jsp</result> </action> <action name="fileupload" class="fileUploadAction" method="fileupload" > <interceptor-ref name="attachedUploadStack" /> <result>${appContext}/upload/form.jsp</result> <result name="input">${appContext}/upload/form.jsp</result> <result name="error">${appContext}/upload/error.jsp</result> <result name="decline">${appContext}/login.jsp</result> <!-- <exception-mapping result="error" exception="java.io.FileNotFoundException" /> --> </action> <!-- imgupload action 略 --> <!--页面定时刷新的后台接收Action--> <action name="fileuploadstatus" class="fileUploadAction" method="uploadStatus" />
客户端定时向服务器询问
程序页面的主体部分:
JS脚本
<script type="text/javascript"> $(document).ready(function(){ $("#btn_submit").click(function(){ //updateStatus(); setInterval(updateStatus, 1000); }); function updateStatus(){ $('.div_process').show('slow'); $.ajax({ type:'get', url: "fileuploadstatus.action", success: function(percent){ $(".bar").css('width',percent+'%'); $(".txt_percent").text(percent+'%'); } }); } }); $(document).ajaxComplete(function (){ $('.bar').add("<span>Ajax Complete trigger on</span>"); }) </script>
HTML
<div class="container">
<div class="content">
<div class="main">
<div class="progress progress-striped active hide div_process">
<div class="bar" style="width:2px;">
<span class="txt_percent">0%</span>
</div>
</div>
<s:fielderror name="tooLarge"></s:fielderror>
<!--<s:actionerror />-->
<div class="file-box">
<s:form enctype="multipart/form-data" class="form-inline" action="upload.action" method="post" >
<input id="textfield" type="text" name="fileName" style="width:180px; margin-bottom:0px;" />
<input class="btn" type="button" value="浏览..." />
<input id="fileField" class="file" type="file" style="width:260px; cursor:pointer;" οnchange="document.getElementById('textfield').value=this.value" name="upload" />
<input id ="btn_submit" class="btn" type="submit" value="上传" name="submit" />
</s:form>
</div>
</div>
</div>
<!-- end .container -->
</div>
好了,一切就绪,跑起来吧:
首先是上传表单页面
上传中