基于Ajax的文件上传要实现的功能要求,要在用户提交了上传按钮请求后,客户端其页面要显示文件上传进度条。
其整个功能时序图如图所示。
简单的说,要实现在客户端显示进度条,需要做的是:当客户端提交上传文件请求后,服务器在上传文件的过程中,将上传进度情况保存到Session中,客户端周期性的发送请求来获取保存在Session中值,以获取上传文件的进度信息。
1. 新建web工程AjaxUpload。
2. 将commons-fileupload-1.2.1-bin.zip包中的commons-fileupload-1.2.1.jar文件和commons-io-1.4-bin.zip包中的commons-io-1.4.jar文件拷贝到web工程下的WEB-INF\lib目录下。
3. 由于本实例涉及到多个类,处理此类问题最好是给相应的类打包进行管理。在web工程src目录下新建一个包com.ncu.upload。
4. 服务器端实现。
首先要创建一个用来保存文件上传状态的类 FileUploadStatus。其源码如下:
[java] view plain copy print?- package com.ncu.upload;
-
- import java.util.*;
-
- public class FileUploadStatus {
-
- private long uploadTotalSize=0;
-
- private long readTotalSize=0;
-
- private int currentUploadFileNum=0;
-
- private int successUploadFileCount=0;
-
- private String status="";
-
- private long processStartTime=0l;
-
- private long processEndTime=0l;
-
- private long processRunningTime=0l;
-
- private List uploadFileUrlList=new ArrayList();
-
- private boolean cancel=false;
-
- private String baseDir="";
-
- public String getBaseDir() {
- return baseDir;
- }
- public void setBaseDir(String baseDir) {
- this.baseDir = baseDir;
- }
- public boolean getCancel() {
- return cancel;
- }
- public void setCancel(boolean cancel) {
- this.cancel = cancel;
- }
- public List getUploadFileUrlList() {
- return uploadFileUrlList;
- }
- public void setUploadFileUrlList(List uploadFileUrlList) {
- this.uploadFileUrlList = uploadFileUrlList;
- }
- public long getProcessRunningTime() {
- return processRunningTime;
- }
- public void setProcessRunningTime(long processRunningTime) {
- this.processRunningTime = processRunningTime;
- }
- public long getProcessEndTime() {
- return processEndTime;
- }
- public void setProcessEndTime(long processEndTime) {
- this.processEndTime = processEndTime;
- }
- public long getProcessStartTime() {
- return processStartTime;
- }
- public void setProcessStartTime(long processStartTime) {
- this.processStartTime = processStartTime;
- }
- public long getReadTotalSize() {
- return readTotalSize;
- }
- public void setReadTotalSize(long readTotalSize) {
- this.readTotalSize = readTotalSize;
- }
- public int getSuccessUploadFileCount() {
- return successUploadFileCount;
- }
- public void setSuccessUploadFileCount(int successUploadFileCount) {
- this.successUploadFileCount = successUploadFileCount;
- }
- public int getCurrentUploadFileNum() {
- return currentUploadFileNum;
- }
- public void setCurrentUploadFileNum(int currentUploadFileNum) {
- this.currentUploadFileNum = currentUploadFileNum;
- }
- public String getStatus() {
- return status;
- }
- public void setStatus(String status) {
- this.status = status;
- }
- public long getUploadTotalSize() {
- return uploadTotalSize;
- }
- public void setUploadTotalSize(long uploadTotalSize) {
- this.uploadTotalSize = uploadTotalSize;
- }
-
- }
由于要在客户端要显示进度条,所以在上传过程中服务器端需要监视和维护上传状态的信息,此过程需要处理的数据信息是:不断更新Session中保存的FileUploadStatus实例的信息,如:已经上传的字节数,上传文件的总大小等。FileUpload现在的1.2版本为监视上传进度提供了内建的支持,可以直接继承类ProgressListener,然后重载update()方法,在该方法中添加自己要处理的代码,最后在文件上传处理代码(后面会讲到)中通过为ServletFileUpload对象注册创建的监听类。监听类UploadListener的源代码如下:
[java] view plain copy print?- package com.ncu.upload;
-
- import javax.servlet.http.HttpSession;
-
- import org.apache.commons.fileupload.ProgressListener;
-
- public class UploadListener implements ProgressListener {
-
- private HttpSession session=null;
-
- public UploadListener (HttpSession session){
- this.session=session;
- }
-
-
-
-
-
-
- public void update(long pBytesRead, long pContentLength, int pItems) {
- FileUploadStatus fuploadStatus = UploadServlet.takeOutFileUploadStatusBean(this.session);
- fuploadStatus.setUploadTotalSize(pContentLength);
-
- if (pContentLength == -1) {
- fuploadStatus.setStatus("完成对" + pItems + "个文件的读取:读取了 " + pBytesRead + "/" + pContentLength+ " bytes.");
- fuploadStatus.setReadTotalSize(pBytesRead);
- fuploadStatus.setCurrentUploadFileNum(pItems);
- fuploadStatus.setProcessEndTime(System.currentTimeMillis());
- fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());
- }else{
- fuploadStatus.setStatus("当前正在处理第" + pItems+"个文件:已经读取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
- fuploadStatus.setReadTotalSize(pBytesRead);
- fuploadStatus.setCurrentUploadFileNum(pItems);
- fuploadStatus.setProcessRunningTime(System.currentTimeMillis());
- }
-
- UploadServlet.storeFileUploadStatusBean(this.session, fuploadStatus);
- }
-
- }
有了前面两个类的基础,下来我们可以动手去实现真正处理整个操作Servlet类。源代码如下。
[java] view plain copy print?- package com.ncu.upload;
-
- import java.io.*;
- import java.util.List;
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import javax.servlet.http.HttpSession;
-
- import org.apache.commons.fileupload.FileItem;
- import org.apache.commons.fileupload.FileUploadException;
- import org.apache.commons.fileupload.disk.DiskFileItemFactory;
- import org.apache.commons.fileupload.servlet.*;
-
-
-
-
-
- public class UploadServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
- static final long serialVersionUID = 1L;
-
- public static final String UPLOAD_STATUS="UPLOAD_STATUS";
- public static final String UPLOAD_DIR="/upload";
-
- public UploadServlet() {
- super();
- }
-
-
-
-
-
-
- private String takeOutFileName(String filePath){
- int pos=filePath.lastIndexOf(File.separator);
- if (pos>0){
- return filePath.substring(pos+1);
- }
- else{
- return filePath;
- }
- }
-
-
-
-
-
-
- public static FileUploadStatus takeOutFileUploadStatusBean(HttpSession session){
- Object obj=session.getAttribute(UPLOAD_STATUS);
- if (obj!=null){
- return (FileUploadStatus)obj;
- }
- else{
- return null;
- }
- }
-
-
-
-
-
-
- public static void storeFileUploadStatusBean(
- HttpSession session,
- FileUploadStatus uploadStatusBean){
- session.setAttribute(UPLOAD_STATUS,uploadStatusBean);
- }
-
-
-
-
-
- private void deleteUploadedFile(HttpServletRequest request){
- FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- for(int i=0;i<fUploadStatus.getUploadFileUrlList().size();i++){
- File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+
- File.separator+fUploadStatus.getUploadFileUrlList().get(i));
- uploadedFile.delete();
- }
- fUploadStatus.getUploadFileUrlList().clear();
- fUploadStatus.setStatus("删除已上传的文件");
- storeFileUploadStatusBean(request.getSession(),fUploadStatus);
- }
-
-
-
-
-
-
-
-
- private void uploadExceptionHandle(
- HttpServletRequest request,
- String errMsg) throws ServletException, IOException{
-
- deleteUploadedFile(request);
- FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- fUploadStatus.setStatus(errMsg);
- storeFileUploadStatusBean(request.getSession(),fUploadStatus);
- }
-
-
-
-
-
-
- private FileUploadStatus initFileUploadStatusBean(HttpServletRequest request){
- FileUploadStatus fUploadStatus=new FileUploadStatus();
- fUploadStatus.setStatus("正在准备处理");
- fUploadStatus.setUploadTotalSize(request.getContentLength());
- fUploadStatus.setProcessStartTime(System.currentTimeMillis());
- fUploadStatus.setBaseDir(request.getContextPath()+UPLOAD_DIR);
- return fUploadStatus;
- }
-
-
-
-
-
-
-
-
- private void processFileUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
- DiskFileItemFactory factory = new DiskFileItemFactory();
-
-
-
-
- ServletFileUpload upload = new ServletFileUpload(factory);
-
-
-
-
-
- upload.setProgressListener(new UploadListener(request.getSession()));
-
- storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
-
- try {
- List items = upload.parseRequest(request);
-
- for(int i=0;i<items.size();i++){
- FileItem item=(FileItem)items.get(i);
-
-
- if (takeOutFileUploadStatusBean(request.getSession()).getCancel()){
- deleteUploadedFile(request);
- break;
- }
-
- else if (!item.isFormField() && item.getName().length()>0){
- String fileName=takeOutFileName(item.getName());
- File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
- item.write(uploadedFile);
-
- FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- fUploadStatus.getUploadFileUrlList().add(fileName);
- storeFileUploadStatusBean(request.getSession(),fUploadStatus);
- Thread.sleep(500);
- }
- }
-
- } catch (FileUploadException e) {
- e.printStackTrace();
-
- } catch (Exception e) {
-
- e.printStackTrace();
-
- }
- }
-
-
-
-
-
-
-
- private void responseFileUploadStatusPoll(HttpServletRequest request,HttpServletResponse response) throws IOException{
- FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
-
- long percentComplete = (long)Math.floor(((double) fUploadStatus.getReadTotalSize()/(double) fUploadStatus.getUploadTotalSize())*100.0);
- System.out.println("com:"+percentComplete);
- response.setContentType("text/xml");
- response.setCharacterEncoding("UTF-8");
- response.setHeader("Cache-Control", "no-cache");
- if ( ((long)fUploadStatus.getReadTotalSize() == (long)fUploadStatus.getUploadTotalSize()) || (fUploadStatus.getCancel() == true)){
- response.getWriter().write(fUploadStatus.getStatus().toString()+"success");
- }else{
- response.getWriter().write(fUploadStatus.getStatus().toString()+"<div class=\"prog-border\"><div class=\"prog-bar\" style=\"width: "
- + percentComplete + "%;\"></div></div>");
- }
- }
-
-
-
-
-
-
- private void processCancelFileUpload(HttpServletRequest request,HttpServletResponse response) throws IOException{
- FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
- fUploadStatus.setCancel(true);
- request.getSession().setAttribute(UPLOAD_STATUS, fUploadStatus);
- responseFileUploadStatusPoll(request,response);
-
- }
-
-
-
-
-
-
-
- private int findFileIdInFileUploadedList(HttpServletRequest request,String fileName){
- FileUploadStatus fileUploadStatus=takeOutFileUploadStatusBean(request.getSession());
- for(int i=0;i<fileUploadStatus.getUploadFileUrlList().size();i++){
- if (fileName.equals((String)fileUploadStatus.getUploadFileUrlList().get(i))){
- return i;
- }
- }
- return -1;
- }
-
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- doPost(request,response);
- }
-
-
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- boolean isMultipart = ServletFileUpload.isMultipartContent(request);
-
- if (isMultipart) {
- processFileUpload(request,response);
- }else{
- request.setCharacterEncoding("UTF-8");
-
- if (request.getParameter("uploadStatus")!=null){
- responseFileUploadStatusPoll(request,response);
- }
- if (request.getParameter("cancelUpload")!=null){
- processCancelFileUpload(request,response);
- }
- }
-
- }
- }
至此,服务器端的代码已经基本完成。
5. 客户端实现
由于在上传文件时需要在同一页面显示对应的进度条控件,因此,在提交表单时当前页面不能被刷新。我们可以通过将表单提交至一个隐藏的 iframe 中来实现。关于Ajax的技术前面讲过,这里就不再细说,直接给出源代码如下:
[html] view plain copy print?- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>基于Ajax的上传文件显示进度条</title>
- <style>
- .prog-border {
- height: 15px;
- width: 205px;
- background: #fff;
- border: 1px solid #000;
- margin: 0;
- padding: 0;
- }
- .prog-bar {
- height: 11px;
- margin: 2px;
- padding: 0px;
- background: #178399;
- font-size: 10pt;
- }
- body{
- font-family: Arial, Helvetica, sans-serif;
- font-size: 10pt;
- }
- </style>
- <script language="javascript" type="text/javascript">
- <!--
- //var userName=document.getElementById("userName").value;
- //创建跨浏览器的XMLHttpRequest对象
- var timer;
- function startListener(){
- var xmlhttp;
- try{
- //IE 5.0
- xmlhttp = new ActiveXObject('Msxm12.XMLHTTP');
- }catch(e){
- try{
- //IE 5.5 及更高版本
- xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
- }catch(e){
- try{
- //其他浏览器
- xmlhttp = new XMLHttpRequest();
- }catch(e){}
- }
- }
- var progressStatusText = document.getElementById("progressBar");
- xmlhttp.open("get","UploadServlet?uploadStatus=true",true);
- /**此处Header设置非常重要,必须设置Content-type类型,负责会报错误
- */
- xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- xmlhttp.onreadystatechange = function(){
- if(xmlhttp.readyState == 4){
- if(xmlhttp.status == 200){
- progressStatusText.innerHTML = "";
- progressStatusText.innerHTML = xmlhttp.responseText;
- var temp = xmlhttp.responseText.indexOf("success");
- if ( temp > 0 ){
- window.clearTimeout(timer);
- }else{
- timer = window.setTimeout(startListener,1000);
- }
- }
- }
- }
- xmlhttp.send(null);
- }
- function startUpload(){
- timer = window.setTimeout(startListener,1000);
- return true;
- }
- function cancelUpload(){
- var xmlhttp;
- try{
- //IE 5.0
- xmlhttp = new ActiveXObject('Msxm12.XMLHTTP');
- }catch(e){
- try{
- //IE 5.5 及更高版本
- xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
- }catch(e){
- try{
- //其他浏览器
- xmlhttp = new XMLHttpRequest();
- }catch(e){}
- }
- }
- var progressStatusText = document.getElementById("progressBar");
- xmlhttp.open("get","UploadServlet?cancelUpload=true",true);
- xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- //xmlhttp.setRequestHeader("Content-type", "multipart/form-data");
- xmlhttp.onreadystatechange = function(){
- if(xmlhttp.readyState == 4){
- if(xmlhttp.status == 200){
- progressStatusText.innerHTML = "";
- progressStatusText.innerHTML = xmlhttp.responseText;
- }
- }
- }
- xmlhttp.send(null);
- return false;
- }
- //-->
- </script>
- </head>
- <body>
- <div id="controlPanel">
- <!-- 这个是隐藏的<iframe>作为表单提交后处理的后台目标
- 通过表单form的target属性指定该<iframe>将返回信息显示在<iframe>框架中
- -->
- <iframe id='target_upload' name='target_upload' src='' style='display: none'></iframe>
- <form id="fileUploadForm" name="fileUploadForm" action="UploadServlet"
- enctype="multipart/form-data" method="post" onsubmit="return startUpload();" target="target_upload">
- <input type="file" name="file" id="file" size="40"/><br>
- <input type="submit" name="uploadButton" id="uploadButton" value="开始上传"/>
- <input type="button" name="cancelUploadButton" id="cancelUploadButton" value="取消上传" onclick="return cancelUpload();"/><br>
- </form>
- <div id="progressBar">
- </div>
- </div>
- </body>
- </html>
至此,整个文件上传的实现到此完成,读者可以在此基础上,发挥自己的创新能力,去完善此实例。
Good Luck!