java基于servlet实现文件上传功能解析

      2020-01-12 15:46      JAVA编程
这篇文章主要为大家详细介绍了java基于servlet实现上传功能,后台使用java实现,前端主要是js的ajax实现,感兴趣的小伙伴们可以参考一下

最近项目需要做一个文件上传功能,做完了分享下,顺带当做笔记。
上传功能用后台用java实现,前端主要是js的ajax实现。后台还加入定时删除临时文件。
效果如图

首先是上传功能的主要类,下面是代码

package util.upload;import java.io.File;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Iterator;import java.util.List;import java.util.UUID;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;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.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;public class UploadServlet extends HttpServlet { private static final long serialVersionUID = -3100028422371321159L; private boolean isAllowed; private String upFileName;  //定义合法后缀名的数组 private String[] allowedExtName=new String[]      {"zip","rar",//压缩文件     "txt","doc","wps","docx","java",//文本     "xls","xlsx",//表格     "ppt","pptx",//幻灯片     "pdf",//pdf     "jpg","jpeg","bmp","gif","png"//图片     }; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置编码格式为utf-8 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");  //获取session,保存进度和上传结果,上传开始为nok,当为Ok表示上传完成 HttpSession session=request.getSession(); session.setAttribute("result", "nok"); session.setAttribute("error", ""); String error=""; upFileName=""; isAllowed=false; //给上传的文件设一个最大值,这里是不得超过100MB int maxSize=100*1024*1024; //创建工厂对象和文件上传对象 DiskFileItemFactory factory=new DiskFileItemFactory(); ServletFileUpload upload=new ServletFileUpload(factory); //创建上传监听器和设置监听器 UploadListener listener=new UploadListener(); session.setAttribute("LISTENER", listener); upload.setProgressListener(listener);  //上传路径 String path = request.getSession().getServletContext().getRealPath("/upload"); String requestPath = request.getSession().getServletContext().getContextPath()+"/upload"; File dirFile =new File(path);   //System.out.println(request.getSession().getServletContext().getContextPath()); //如果文件夹不存在则创建   if (!dirFile .exists() && !dirFile .isDirectory())    {       dirFile .mkdir();   }   //根据日期创建文件夹,保存到对应日期的文件夹下 Date date=new Date(); SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd"); String subDirName=sdf.format(date); File subDirFile=new File(path+"/"+subDirName); if (!subDirFile .exists() && !subDirFile .isDirectory())    {       subDirFile .mkdir();   }   try {   //解析上传请求  List<FileItem> items=upload.parseRequest(request);    Iterator<FileItem> itr=items.iterator();    while(itr.hasNext()){     FileItem item=(FileItem)itr.next();  //判断是否为文件域    if(!item.isFormField()){   if(item.getName()!=null&&!item.getName().equals("")){   //获取上传文件大小和文件名称   long upFileSize=item.getSize();     String fileName=item.getName();   //获取文件后缀名   String[] splitName=fileName.split("\\.");   String extName=splitName[splitName.length-1];   //检查文件后缀名   for(String allowed:allowedExtName)   {    if(allowed.equalsIgnoreCase(extName))    {      isAllowed=true;    }       }   if(!isAllowed){     error="上传文件格式不合法!";     break;   }   if(upFileSize>maxSize){    error="您上传的文件太大了,请选择不超过100MB的文件!";    break;   }    //此时文件暂存在服务器的内存中,构造临时对象   File tempFile=new File(makeFileName(fileName));   //指定文件上传服务器的目录及文件名称   File file=new File(path+"/"+subDirName+"/",tempFile.getName());   item.write(file);//第一种写文件方法   upFileName=requestPath+"/"+subDirName+"/"+tempFile.getName(); if(upFileName.equals("")){   error="没选择上传文件!"; } System.out.println(upFileName);   /*//构造输入流读文件 第二种写文件方法   InputStream is=item.getInputStream();   int length=0;   byte[] by=new byte[1024];   FileOutputStream fos=new FileOutputStream(file);   while((length=is.read(by))!=-1){    fos.write(by, 0, length);    //Thread.sleep(10);   }   fos.close();   //Thread.sleep(1000);*/   }else{   error="没选择上传文件!";   }  }  }   } catch (Exception e) {  e.printStackTrace();  error="上传文件出现错误:"+e.getMessage(); } if(!error.equals("")){    System.out.println(error);  session.setAttribute("error", error); }else{   session.setAttribute("result", "OK");   session.setAttribute("filename",upFileName); } } /** * 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名 * @param filename 原文件名 * @return 生成的唯一文件名 */ private String makeFileName(String filename){   return UUID.randomUUID().toString() + "_" + filename;  } }

其中需要引入commons-fileupload-1.3.1.jar,commons-io-2.4.jar
上传过程中,我们需要实时获取上传进度等信息,引入的库里为我们添加了一个ProgressListener接口,我们再写一个类实现这个接口,上面类中添加该接口

//创建工厂对象和文件上传对象 DiskFileItemFactory factory=new DiskFileItemFactory(); ServletFileUpload upload=new ServletFileUpload(factory); //创建上传监听器和设置监听器 UploadListener listener=new UploadListener(); session.setAttribute("LISTENER", listener); upload.setProgressListener(listener);

下面是这个监听类的具体实现代码

package util.upload;import org.apache.commons.fileupload.ProgressListener;public class UploadListener implements ProgressListener{  private volatile long   bytesRead = 0L,//上传的字节数  contentLength = 0L,//总字节数  item = 0L;    public UploadListener()     {      super();    }   @Override   public void update(long aBytesRead, long aContentLength, int anItem) {    bytesRead = aBytesRead;     contentLength = aContentLength;     item = anItem;   }   public long getBytesRead()    {     return bytesRead;   }   public long getContentLength()    {     return contentLength;   }   public long getItem()    {     return item;   }}

现在能获取上传进度等信息了,但还需要一个servlet返回给前端,下面实现

package util.upload;import java.io.IOException;import java.io.PrintWriter;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.commons.fileupload.ProgressListener;import com.google.gson.Gson;/** 获取上传进度,上传路径,错误,上传结果等信息 */public class GetProgressServlet extends HttpServlet{ private static final long serialVersionUID = -3596466520775012991L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {   request.setCharacterEncoding("utf-8");   response.setCharacterEncoding("utf-8");   UploadListener listener= null;   HttpSession session = request.getSession();   String error=(String) session.getAttribute("error");   String result= (String) session.getAttribute("result");   String fileName=(String) session.getAttribute("filename");   PrintWriter out = response.getWriter();   long bytesRead = 0,contentLength = 0;    if (session != null)    {      listener = (UploadListener)session.getAttribute("LISTENER");      if (listener == null)      {        return;      }      else      {                bytesRead = listener.getBytesRead();//上传的字节数        contentLength = listener.getContentLength();//总字节数      }      //自己定义的返回格式      String rp=bytesRead+","          +contentLength+","          +error+","          +result+","          +fileName;      //System.out.println(rp);      out.print(rp);      /*   //返回json格式数据      Map<String,Object> map=new HashMap<String,Object>();      map.put("bytesRead", bytesRead);      map.put("contentLength", contentLength);      map.put("error", error);      map.put("result", result);      map.put("fileName", fileName);      Gson gson=new Gson();      String json=gson.toJson(map);      out.print(json);*/      out.flush();      out.close();      } }}

后台上传的功能代码写完了,下面实现上传的前端,首先是html

<!DOCTYPE html>  <html>  <head>    <meta charset="utf-8" />    <script type="text/javascript" src="js/upfile.js" charset="utf-8"></script>    <link rel="stylesheet" type="text/css" href="css/upfile.css">  </head>  <body >    <a href="javascript:addOne()">添加</a>    <div id="target">      <input type="file" id="file" name="file" onchange="addfile(event)" multiple/>    </div>    <span id="test">0</span>  </body> </html>

界面比较简单,就一个添加的a标签,负责上传的input隐藏起来
css文件主要渲染的上传进度的显示

      #file {        display: none;      }      .pro{        width:500px;      }      .pborder {        position: relative;        width: 500px; /* 宽度 */        border: 1px solid #B1D632;        padding: 1px;      }      .drawpro {        width: 0px;        display: block;        position: relative;        background: #B1D632;        color: #333333;        height: 20px; /* 高度 */        line-height: 20px; /* 必须和高度一致,文本才能垂直居中 */      }      .pspan {        position: absolute;        width: 500px;        text-align: center;        font-weight: bold;      }

接着是前端的重点,js文件

//显示上传信息的htmlvar upfile_html = '<div class="pborder"><div class="drawpro">'    + '<span class="pspan">0%</span></div></div><span name="path"></span><img src="common/upload/images/del.png" style="float:right" width="20" height="20" name="del" onclick=abortUpload(this)>';var targetDIV_id = "target";//显示上传文件的目标div的IDvar httpXML = null;//发送上传请求的XMLHttpRequest对象var httpProgress = null;//发送请求进度信息的XMLHttpRequest对象var oldFileList = new Array();//修改时保存已有附件信息的列表var uplist = new Array();//保存上传文件的列表var f_input;//上传文件的input对象var flag = true;//是否可以上传下一个文件标志var uurl = "Upload";//上传文件的请求urlvar gurl = "getProgress";//获取上传进度信息的urlvar cancelFlag = 0;//取消标志var timer, waittimer;//定时器var nowID = 0;//正在上传文件的idvar ID = 0;//队列中最后一个文件的id/** * 文件对象 */function UploadFile(id, file) {  this.id = id;  this.file = file;  this.state = 0;  this.path = "";}/** * 初始化的方法 */window.onload = function init() {  f_input = document.getElementById("file");  var tdiv = document.getElementById(targetDIV_id);  var oldspan = tdiv.getElementsByTagName("SPAN");  for ( var i = 0; i < oldspan.length; i++) {    oldFileList.push(oldspan[i].getAttribute("name"));  }}/** * 选择一个文件上传 */function addOne() {  f_input.value = null;  f_input.click();}/** * 选中文件后将文件对象添加到队列,开始上传 *  */function addfile(evt) {  var f = f_input.files[0];  if (f != undefined) {    var uf = new UploadFile(ID, f);    uplist.push(uf);    var div = document.createElement("DIV");    div.setAttribute("id", "pro" + (ID));    div.setAttribute("class", "pro");    div.innerHTML = upfile_html;    var targetDiv = document.getElementById(targetDIV_id);    targetDiv.appendChild(div);    div.getElementsByTagName("SPAN")[1].innerHTML = "文件名:"        + uplist[ID].file.name;    waittimer = setInterval("upload()", 1000);    ID++;  }}/** * 将队列中的文件上传 */function upload() {  if (flag == true) {    if (uplist.length > 0) {      var uf;      for ( var i = 0; i < uplist.length; i++) {        if (uplist[i].state == 0) {          uf = uplist[i];          uplist[i].state = 1;          break;        }      }      if (uf != undefined & uf != null) {        flag = false;        if (window.XMLHttpRequest) {          httpUP = new XMLHttpRequest();        } else if (window.ActiveXObject) {          httpUP = new ActiveXObject("Microsoft.XMLHTTP");        }        var formData = new FormData();        formData.append("file", uf.file);        httpUP.open("POST", uurl, true);       httpUP.upload.addEventListener('progress', uploadProgress, false);        httpUP.send(formData);        nowID = uf.id;        timer = setInterval("getP()", 50);      }    }  }}/** * 获取上传进度等信息 */function getP() {  if (window.XMLHttpRequest) {    httpProgress = new XMLHttpRequest();  } else if (window.ActiveXObject) {    httpProgress = new ActiveXObject("Microsoft.XMLHTTP");  }  httpProgress.onreadystatechange = onProgress;  httpProgress.open("post", gurl, true);  httpProgress.setRequestHeader("Content-type",      "application/x-www-form-urlencoded");  httpProgress.send("&timeStamp=" + (new Date()).getTime());}/** * 处理返回的上传信息,显示到界面 */function onProgress() {  if (httpProgress.readyState == 4 && httpProgress.status == 200) {    result = httpProgress.responseText;    var result = result.replace(/(^\s*)|(\s*$)/g, "");    var res = result.split(",");    var now = parseInt(res[0]);    var all = parseInt(res[1]);    var err = res[2];    var state = res[3];    var path = res[4];    var per = (now / all * 100).toFixed(2);    var prodiv = document.getElementById("pro" + nowID);    if (prodiv != null & prodiv != undefined) {      if (err != "" & err != null & err.length > 0) {        window.clearInterval(timer);        if (cancelFlag == 1) {          err = "上传终止";          cancelFlag = 0;        }        prodiv.getElementsByTagName("DIV")[0].style.display = "none";        prodiv.getElementsByTagName("SPAN")[1].innerHTML = err;        httpUP.abort();        flag = true;        uplist[nowID].state = 3;        return;      }      if (state == "OK") {        prodiv.getElementsByTagName("DIV")[0].style.display = "none";        var tmpf = uplist[nowID].file;        prodiv.getElementsByTagName("SPAN")[1].innerHTML = "文件名:"            + tmpf.name;        window.clearInterval(timer);        flag = true;        uplist[nowID].state = 2;        uplist[nowID].path = path;        return;      }      prodiv.getElementsByTagName("DIV")[1].style.width = per * 5 + "px";      prodiv.getElementsByTagName("SPAN")[0].innerHTML = per + "%";    }  }}/** * 取消上传的方法 */function abortUpload(obj) {  var idStr = obj.parentNode.id;  var id = idStr.slice(3);  if (uplist[id].state == 1) {    httpUP.abort();    flag = true;    cancelFlag = 1;  } else {    uplist[id].state = 3;  }  document.getElementById(idStr).remove();}/** * 获取上传文件的路径 * @returns 格式化后字符串 */function getFileListStr() {  var str = "";  if (oldFileList.length > 0) {    for ( var i = 0; i < oldFileList.length; i++) {      if (oldFileList[i] != null & oldFileList[i] != ""          & oldFileList[i] != undefined) {        str = str + oldFileList[i] + ",";      }    }  }  for ( var i = 0; i < uplist.length; i++) {    var f = uplist[i];    if (f.state == 2) {      str = str + f.path + ",";    }  }  return str;}/** * 移除修改时已有的旧附件 *  */function removeOld(btn) {  var num = btn.getAttribute("name");  oldFileList[num - 1] = null;  btn.parentNode.remove();}  function uploadProgress(e) {        if (e.lengthComputable) {          var iBytesUploaded = e.loaded;          var iBytesTotal = e.total;          document.getElementById("test").innerHTML=iBytesUploaded+"/"+iBytesTotal;        }      }

使用ajax发送上传文件,获取上传进度,结果等信息。
使用的html5的file API,所以必须ie9以上的才可以兼容,火狐还有个问题,ajax请求不立即返回,直到所有ajax请求都发送完了,才都返回同一个结果,这就导致上传进度不显示。不过上传进度信息也可以使用html5的file api获取,其中加了一点代码,页面下面test的div里的数值就是在前端获取到的进度。

上传的都实现完了,接着是处理上传后的临时文件,因为使用的uuid命名文件,所以文件会生成很多,没用的需要定时处理。使用ServletContextListener:
在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。

当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。
利用其特性,实现定时删除临时文件的功能,代码如下:

package util.upload;import java.io.IOException;import java.io.InputStream;import java.util.Date;import java.util.Properties;import java.util.Timer;import java.util.TimerTask;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;/**  * 时间监听器  *   *  */ public class TempFileListener implements ServletContextListener {   private Timer timer;   private SystemTaskTest systemTask;   private static String every_time_run;   static {     Properties prop = new Properties();     InputStream inStrem = TempFileManager.class.getClassLoader()         .getResourceAsStream("tempfile.properties");     try {       prop.load(inStrem);       System.out.println(inStrem);      every_time_run = prop.getProperty("every_time_run");     } catch (IOException e) {       e.printStackTrace();     } finally {       try {         inStrem.close();       } catch (IOException e) {         e.printStackTrace();       }     }   }   // 监听器初始方法   public void contextInitialized(ServletContextEvent sce) {     timer = new Timer();     systemTask = new SystemTaskTest(sce.getServletContext()         .getRealPath("/"), sce.getServletContext());     try {       System.out.println("定时器已启动");      // 监听器获取网站的根目录       String path = sce.getServletContext().getRealPath("/");       Long time = Long.parseLong(every_time_run) * 1000;// 循环执行的时间       System.out.println("time" + time);        // 第一个参数是要运行的代码,第二个参数是从什么时候开始运行,第三个参数是每隔多久在运行一次。重复执行       timer.schedule(systemTask, 10000, time);       System.out.println("已经添加任务调度表");    } catch (Exception e) {       e.printStackTrace();    }   }   public void contextDestroyed(ServletContextEvent sce) {     try {       timer.cancel();     } catch (Exception e) {     }   } } /**  * 时间任务器  *  */ class SystemTaskTest extends TimerTask {   private ServletContext context;   private String path;   public SystemTaskTest(String path, ServletContext context) {     this.path = path;     this.context = context;   }   /**    * 把要定时执行的任务就在run中    */   public void run() {      TempFileManager etf;       try {       System.out.println("开始执行任务!");      // 需要执行的代码       System.out.println(new Date().toLocaleString());       etf = new TempFileManager(path);       etf.run();          System.out.println("指定任务执行完成!");    } catch (Exception e) {       e.printStackTrace();     }   } } 

上面只是监听器,负责定时调用删除临时文件的方法,具体实现是下面的类

package util.upload;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.util.Date;import java.util.Properties;/**  * 删除服务器上的文件  *  */ public class TempFileManager implements Runnable {   private String path;//路径   private static String RETENTION_TIME = "1440";// 文件保存的时间 一天单位分  static {     Properties prop = new Properties();     InputStream inStrem = TempFileManager.class.getClassLoader()         .getResourceAsStream("execl.properties");     try {       prop.load(inStrem);       RETENTION_TIME = prop.getProperty("file_retention_time");     } catch (IOException e) {       e.printStackTrace();     } finally {       try {         inStrem.close();       } catch (IOException e) {         e.printStackTrace();       }     }   }   /**    * 构造函数。初始化参数    * @param path    */   public TempFileManager(String path) {     this.path = path;   }   /**    * 把线程要执行的代码放在run()中    */   public void run() {     System.out.println("文件管理开始=========");     path = path + "upload";     System.out.println("文件管理路径===" + path);     File file = new File(path);     deletefiles(file);   }   /**    * 批量删除文件    *     * @param folder    */   public void deletefiles(File folder) {   if(folder.isDirectory()){    File[] files = folder.listFiles();    if(files.length<=0){      if(!folder.getAbsolutePath().equalsIgnoreCase(path)){      if(canDeleteFile(folder)){        if (folder.delete()) {           System.out.println("文件夹" + folder.getName() + "删除成功!");         } else {           System.out.println("文件夹" + folder.getName()               + "删除失败!此文件夹内的文件可能正在被使用");         }       }      }    }    for (int i = 0; i < files.length; i++) {       if(files[i].isDirectory())      {      deletefiles(files[i]);      }else{        deleteFile(files[i]);       }    }   }  }   /**    * 删除文件    *     * @param file    */   private void deleteFile(File file) {     try {       if (file.isFile()) {         // 删除符合条件的文件         if (canDeleteFile(file)) {           if (file.delete()) {             System.out.println("文件" + file.getName() + "删除成功!");           } else {             System.out.println("文件" + file.getName()                 + "删除失败!此文件可能正在被使用");           }         } else {         }       } else {         System.out.println("没有可以删除的文件了");       }     } catch (Exception e) {       System.out.println("删除文件失败========");       e.printStackTrace();     }   }   /**    * 判断文件是否能够被删除    */   private boolean canDeleteFile(File file) {     Date fileDate = getfileDate(file);     Date date = new Date();     long time = (date.getTime() - fileDate.getTime()) / 1000 / 60         - Integer.parseInt(RETENTION_TIME);// 当前时间与文件间隔的分钟 //    System.out.println("time=="+time);    if (time > 0) {       return true;     } else {       return false;     }   }   /**    * 获取文件最后的修改时间    *     * @param file    * @return    */   private Date getfileDate(File file) {     long modifiedTime = file.lastModified();     Date d = new Date(modifiedTime);     return d;   } } 

判断文件是否超时,超时就自动删除,并且能自动删除文件夹。

以上就是本文的全部内容,希望对大家学习java程序设计有所帮助。