• WEB文件上传之apache common upload使用(一)


    •  

      文件上传一个经常用到的功能,它有许多中实现的方案。

    页面表单 + RFC1897规范 + http协议上传

    页面控件(flash/html5/activeX/applet) + RFC1897规范 + http协议上传

    页面控件(flash/html5/activeX/applet) + 自定义数据规范 + http协议上传

    页面控件(flash/html5/activeX/applet) + FTP协议上传

    页面控件(flash/html5/activeX/applet) + 自定义协议

      用apache common upload组件实际就是采用的“页面表单 + RFC1897规范 + http协议上传”实现方式,需要实现的技术点:

    1. 多文件数据的提交

    2. 文件数据包接收存储功能

    3. 文件数据上传进度

    4. WEB页面无刷新异步提交

     时序图:

    • 文件上传时序图


    • 文件上传进度获取时序图
    实现思路:

    1. 多文件数据的提交

    在WEB页面采用多个<input type="file">利用form表单进行文件提交

    2. 文件数据包接收存储功能

    服务端采用servlet,利用apache common upload组件接收解析数据包,接收解析的过程中保存进度到session, 文件接收完毕后保存到指定目录

    3. 文件数据上传进度

    在WEB页面在界面写一个定时器,定时访问服务器提供上传进度获取功能的servlet,获取文件上传进度信息

    4. WEB页面无刷新异步提交

    利用iframe来实现WEB页面无刷新异步上传

    关键代码:

    UploadFileServlet.java

    Java代码  收藏代码
    1. package com.test.servlet;  
    2.   
    3. import java.io.File;  
    4. import java.io.IOException;  
    5. import java.io.Writer;  
    6. import java.util.Iterator;  
    7. import java.util.List;  
    8.   
    9. import javax.servlet.ServletException;  
    10. import javax.servlet.http.HttpServlet;  
    11. import javax.servlet.http.HttpServletRequest;  
    12. import javax.servlet.http.HttpServletResponse;  
    13.   
    14. import org.apache.commons.fileupload.FileItem;  
    15. import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;  
    16. import org.apache.commons.fileupload.disk.DiskFileItemFactory;  
    17. import org.apache.commons.fileupload.servlet.FileCleanerCleanup;  
    18. import org.apache.commons.fileupload.servlet.ServletFileUpload;  
    19. import org.apache.commons.io.FileCleaningTracker;  
    20. import org.apache.commons.io.FileUtils;  
    21. import org.apache.commons.io.FilenameUtils;  
    22. import org.apache.commons.io.IOUtils;  
    23. import org.apache.commons.lang3.ArrayUtils;  
    24. import org.apache.commons.logging.Log;  
    25. import org.apache.commons.logging.LogFactory;  
    26.   
    27. /** 
    28.  * 文件上传数据接收类 
    29.  *  
    30.  * @author chengqi 
    31.  * 
    32.  */  
    33. public class UploadFileServlet extends HttpServlet {  
    34.   
    35.     /** 日志对象*/  
    36.     private Log logger = LogFactory.getLog(this.getClass());  
    37.   
    38.     private static final long serialVersionUID = 1L;  
    39.   
    40.     /** 上传目录名*/  
    41.     private static final String uploadFolderName = "uploadFiles";  
    42.   
    43.     /** 上传临时文件存储目录*/  
    44.     private static final String tempFolderName = "tempFiles";  
    45.   
    46.     /** 上传文件最大为30M*/   
    47.     private static final Long fileMaxSize = 30000000L;   
    48.   
    49.     /** 允许上传的扩展名*/  
    50.     private static final String [] extensionPermit = {"txt", "xls", "zip"};  
    51.   
    52.     /** 统一的编码格式*/  
    53.     private static final String encode = "UTF-8";  
    54.   
    55.     @Override  
    56.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
    57.         logger.info("UploadFileServlet#doPost() start");  
    58.         try {  
    59.             String curProjectPath = this.getServletContext().getRealPath("/");  
    60.             String saveDirectoryPath = curProjectPath + "/" + uploadFolderName;  
    61.             String tempDirectoryPath = curProjectPath + "/" + tempFolderName;  
    62.             File saveDirectory = new File(saveDirectoryPath);  
    63.             File tempDirectory = new File(tempDirectoryPath);  
    64.             logger.debug("Project real path [" + saveDirectory.getAbsolutePath() + "]");  
    65.             //上传时产生的临时文件的默认保存目录  
    66.             logger.debug("Temp files default save path [" + System.getProperty("java.io.tmpdir") + "]");  
    67.             DiskFileItemFactory factory = new DiskFileItemFactory();  
    68.             //DiskFileItemFactory中DEFAULT_SIZE_THRESHOLD=10240表示如果上传文件大于10K则会产生上传临时文件  
    69.             //上传临时文件的默认目录为java.io.tmpdir中保存的路径,根据操作系统的不同会有区别  
    70.               
    71.             if(!tempDirectory.exists()) {  
    72.                 tempDirectory.mkdir();  
    73.             }  
    74.             //重新设置临时文件保存目录  
    75.             factory.setRepository(tempDirectory);  
    76.   
    77.             //设置文件清除追踪器,文件上传过程中产生的临时文件会在  
    78.             FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(this.getServletContext());  
    79.             factory.setFileCleaningTracker(fileCleaningTracker);  
    80.   
    81.             ServletFileUpload upload = new ServletFileUpload(factory);  
    82.   
    83.             //设置文件上传进度监听器  
    84.             FileProcessListener processListener = new FileProcessListener(request.getSession());  
    85.             upload.setProgressListener(processListener);  
    86.   
    87.             // 设置文件上传的大小限制  
    88.             upload.setFileSizeMax(fileMaxSize);  
    89.   
    90.             // 设置文件上传的头编码,如果需要正确接收中文文件路径或者文件名  
    91.             // 这里需要设置对应的字符编码,为了通用这里设置为UTF-8  
    92.             upload.setHeaderEncoding(encode);  
    93.   
    94.             //解析请求数据包  
    95.             List<FileItem> fileItems = upload.parseRequest(request);  
    96.             //遍历解析完成后的Form数据和上传文件数据  
    97.             for (Iterator<FileItem> iterator = fileItems.iterator(); iterator.hasNext();) {  
    98.                 FileItem fileItem = iterator.next();  
    99.                 String fieldName = fileItem.getFieldName();  
    100.                 String name = fileItem.getName();  
    101.                 //如果为上传文件数据  
    102.                 if(!fileItem.isFormField()) {  
    103.                     logger.debug("fieldName[" + fieldName + "] fileName[" + name + "] ");  
    104.                     if(fileItem.getSize() > 0) {  
    105.                         String fileExtension = FilenameUtils.getExtension(name);  
    106.                         if(!ArrayUtils.contains(extensionPermit, fileExtension)) {  
    107.                             throw new NoSupportExtensionException("No Support extension.");  
    108.                         }  
    109.                         String fileName = FilenameUtils.getName(name);  
    110.                         FileUtils.copyInputStreamToFile(fileItem.getInputStream(),   
    111.                                 new File(saveDirectory, fileName));  
    112.                     }  
    113.                 } else { //Form表单数据  
    114.                     String value = fileItem.getString(encode);  
    115.                     logger.debug("fieldName[" + value + "] fieldValue[" + fieldName + "]");  
    116.                 }  
    117.             }  
    118.             responseMessage(response, State.OK);  
    119.         } catch(FileSizeLimitExceededException e) {   
    120.             logger.error(e.getMessage(), e);  
    121.             responseMessage(response, State.OVER_FILE_LIMIT);  
    122.         } catch(NoSupportExtensionException e) {   
    123.             logger.error(e.getMessage(), e);  
    124.             responseMessage(response, State.NO_SUPPORT_EXTENSION);  
    125.         } catch(Exception e) {  
    126.             logger.error(e.getMessage(), e);  
    127.             responseMessage(response, State.ERROR);  
    128.         } finally {  
    129.             //清除上传进度信息  
    130.             request.getSession().removeAttribute("fileUploadProcess");  
    131.         }  
    132.         logger.info("UploadFileServlet#doPost() end");   
    133.     }  
    134.   
    135.     public enum State {  
    136.         OK(200, "上传成功"),  
    137.         ERROR(500, "上传失败"),  
    138.         OVER_FILE_LIMIT(501, "超过上传大小限制"),  
    139.         NO_SUPPORT_EXTENSION(502, "不支持的扩展名");  
    140.   
    141.         private int code;  
    142.         private String message;  
    143.         private State(int code, String message) {  
    144.             this.code = code;  
    145.             this.message = message;  
    146.         }  
    147.   
    148.         public int getCode() {  
    149.             return code;  
    150.         }  
    151.         public String getMessage() {  
    152.             return message;  
    153.         }  
    154.   
    155.     }  
    156.   
    157.     /** 
    158.      * 返回结果函数 
    159.      * @param response 
    160.      * @param state 
    161.      */  
    162.     private void responseMessage(HttpServletResponse response, State state) {  
    163.         response.setCharacterEncoding(encode);  
    164.         response.setContentType("text/html; charset=" + encode);  
    165.         Writer writer = null;  
    166.         try {  
    167.             writer = response.getWriter();  
    168.             writer.write("<script>");  
    169.             writer.write("window.parent.fileUploadCallBack({"code":" + state.getCode() +","message":"" + state.getMessage()+ ""});");  
    170.             writer.write("</script>");  
    171.             writer.flush();  
    172.             writer.close();  
    173.         } catch(Exception e) {  
    174.             logger.error(e.getMessage(), e);  
    175.         } finally {  
    176.             IOUtils.closeQuietly(writer);  
    177.         }  
    178.     }  
    179.   
    180.   
    181. }  

      

    GetFileProcessServlet.java

    Java代码  收藏代码
    1. package com.test.servlet;  
    2.   
    3. import java.io.IOException;  
    4. import java.io.Writer;  
    5.   
    6. import javax.servlet.ServletException;  
    7. import javax.servlet.http.HttpServlet;  
    8. import javax.servlet.http.HttpServletRequest;  
    9. import javax.servlet.http.HttpServletResponse;  
    10.   
    11. import org.apache.commons.io.IOUtils;  
    12. import org.apache.commons.logging.Log;  
    13. import org.apache.commons.logging.LogFactory;  
    14.   
    15. /** 
    16.  * 文件上传进度获取Servlet 
    17.  *  
    18.  * @author chengqi 
    19.  * 
    20.  */  
    21. public class GetFileProcessServlet extends HttpServlet {  
    22.   
    23.     /** 日志对象*/  
    24.     private Log logger = LogFactory.getLog(this.getClass());  
    25.   
    26.     private static final long serialVersionUID = 1L;  
    27.   
    28.     @Override  
    29.     protected void doGet(HttpServletRequest request, HttpServletResponse response)  
    30.             throws ServletException, IOException {  
    31.         logger.info("GetFileProcessServlet#doGet start");  
    32.         String fileUploadPercent = (String)request.getSession().getAttribute("fileUploadProcess");  
    33.         Writer writer = null;  
    34.         try {  
    35.             writer = response.getWriter();  
    36.             logger.info("percent:" + fileUploadPercent);  
    37.             IOUtils.write(fileUploadPercent == null ? "0%" : fileUploadPercent, writer);  
    38.             writer.flush();  
    39.             writer.close();  
    40.         } catch(Exception e) {  
    41.             logger.error(e.getMessage(), e);  
    42.         } finally {  
    43.             IOUtils.closeQuietly(writer);  
    44.         }  
    45.         logger.info("GetFileProcessServlet#doGet end");  
    46.     }  
    47.   
    48. }  

    FileProcessListener.java

    Java代码  收藏代码
    1. package com.test.servlet;  
    2.   
    3. import java.text.NumberFormat;  
    4.   
    5. import javax.servlet.http.HttpSession;  
    6.   
    7. import org.apache.commons.fileupload.ProgressListener;  
    8. import org.apache.commons.logging.Log;  
    9. import org.apache.commons.logging.LogFactory;  
    10.   
    11. /** 
    12.  * 文件进度监听器 
    13.  *  
    14.  * @author chengqi 
    15.  * 
    16.  */  
    17. public class FileProcessListener implements ProgressListener{  
    18.   
    19.     /** 日志对象*/  
    20.     private Log logger = LogFactory.getLog(this.getClass());  
    21.   
    22.     private HttpSession session;  
    23.   
    24.     public FileProcessListener(HttpSession session) {  
    25.         this.session = session;    
    26.     }  
    27.       
    28.   
    29.     public void update(long pBytesRead, long pContentLength, int pItems) {  
    30.         double readByte = pBytesRead;  
    31.         double totalSize = pContentLength;  
    32.         if(pContentLength == -1) {  
    33.             logger.debug("item index[" + pItems + "] " + pBytesRead + " bytes have been read.");  
    34.         } else {  
    35.             logger.debug("item index[" + pItems + "] " + pBytesRead + " of " + pContentLength + " bytes have been read.");  
    36.             String p = NumberFormat.getPercentInstance().format(readByte / totalSize);  
    37.             session.setAttribute("fileUploadProcess", p);  
    38.         }  
    39.     }  
    40.   
    41. }  
     

    apacheUploadDemo.html

    Html代码  收藏代码
    1. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
    2. <html>  
    3. <head>  
    4.     <title>Apache common实现基本文件上传</title>  
    5.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
    6.     <script type="text/javascript" src="js/jquery/jquery-1.9.1.js"></script>  
    7.     <script type="text/javascript" src="js/jquery/jquery.form.js"></script>  
    8.     <script type="text/javascript">  
    9.   
    10.     //定时器对象  
    11.     var uploadProcessTimer = null;  
    12.   
    13.     $(function (){  
    14.         //绑定定时器开始操作到提交按钮  
    15.         $('input[type=submit]').click(function () {  
    16.             //启动上传进度查询定时器  
    17.             uploadProcessTimer = window.setInterval(getFileUploadProcess, 20);  
    18.         })  
    19.     });  
    20.   
    21.     //获取文件上传进度  
    22.     function getFileUploadProcess() {  
    23.         $.get('/upload/getFileProcessServlet', function(data) {  
    24.             $('#fileUploadProcess').html(data);  
    25.         });  
    26.     }  
    27.   
    28.     //上传完成后,由iframe返回脚本自动调用  
    29.     function fileUploadCallBack(res) {  
    30.         //清除定时器  
    31.         if(uploadProcessTimer) {  
    32.             window.clearInterval(uploadProcessTimer);  
    33.         }  
    34.         var message = res['message'];  
    35.         var code = res['code'];  
    36.         if(code != 200) {  
    37.             $('#fileUploadProcess').html('0%');  
    38.         }  
    39.         alert(message);  
    40.     }  
    41.   
    42.     </script>  
    43. </head>  
    44. <body>  
    45. <h2>上传文件1</h2>  
    46.   
    47. 用户信息:  <br/>  
    48. <form id="testForm" action="/upload/uploadServlet" method="post" enctype="multipart/form-data" target="iframeUpload">  
    49.     姓名:<input name="name" type="text"<br/>  
    50.     附件1:<input name="file1" type="file" <br/>  
    51.     附件2:<input name="file2" type="file" <br/>  
    52.     <br><br>  
    53.     <input type="submit" value="提交" ><br/>  
    54. </form>  
    55. 上传进度:<label id="fileUploadProcess"></label>  
    56. <iframe name="iframeUpload" src="" width="350" height="35" frameborder=0  SCROLLING="no" style="display:NONE"></iframe>     
    57. </body>  
    58. </html>  
     
    总结:
    虽然使用apache common upload组件实现了文件上传,但是从上传的效果来看,并不是一个很完美的解决方案。
    有如下缺点:
    1. 当有多个文件上传时,无法知道单个文件的上传进度,因为文件上传消息中根本就没有关于单个文件大小的信息
    文件上传消息
    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: text/html;charset=UTF-8
    Transfer-Encoding: chunked
    Date: Tue, 22 Apr 2014 07:45:45 GMT

    POST /upload/uploadServlet HTTP/1.1
    Host: localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://localhost:8080/upload/apacheUploadDemo.html
    Cookie: JSESSIONID=33498CE814284D67F957CA53D45F0174
    Connection: keep-alive

    Content-Length 2363
    Content-Type multipart/form-data; boundary=---------------------------189163093917262

    -----------------------------189163093917262
    Content-Disposition: form-data; name="name" 

    -----------------------------189163093917262
    Content-Disposition: form-data; name="file1"; filename="New Text Document.txt" Content-Type: text/plain
    文件数据

    -----------------------------189163093917262
    Content-Disposition: form-data; name="file2"; filename="New Text Document (2).txt" Content-Type: text/plain
    文件数据

    -----------------------------189163093917262--

      

     2. 浏览器必须将所有文件读取完毕才开始上传,并且是一次性提交所有的数据文件,在互联网环境下,会http连接超时,大文件无法上传成功。

    3. 服务端判断是否超过大小限制,是通过计算接收数据的累积字节数和限制大小比较,这种情况下,如果限制大小是30M,那么在服务端已经读取了30M完成后才会抛出异常,多余的消耗的服务器的内存和硬盘空间

    所以基于这些原因,页面表单 + RFC1897规范 + http协议上传 + 后台apache common upload组件接收的这种解决方案,不适合解决WEB页面一次多文件上传,大文件上传情况,比较适合一次单个小文件附件的情况,如:博客附件,登记照片上传,预览等情况。

    Demo源码见附件

  • 相关阅读:
    让思考成为一种习惯:今年,我大四了
    asp.net core源码飘香:Logging组件
    asp.net core源码飘香:Options组件
    asp.net core源码飘香:Configuration组件
    asp.net core源码飘香:从Hosting开始
    webpack code split实现原理
    css specificity
    todo:read
    React Render Props 模式
    recompose mapProps 函数指南
  • 原文地址:https://www.cnblogs.com/firstdream/p/7375863.html
Copyright © 2020-2023  润新知