美文网首页
Java Web文件上传展示进度

Java Web文件上传展示进度

作者: SpaceCat | 来源:发表于2022-03-07 22:50 被阅读0次

    1、文件上传演示准备

    接前面“Java Web中的文件上传和下载”

    image.png
    为了演示文件上传,先新增一个用于接收文件上传的servlet。
    com.trial.servlet.FileUploadParseFileWithProgressServlet
    package com.trial.servlet;
    
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.List;
    import java.util.UUID;
    
    /**
     * Created by chengxia on 2021/11/27.
     */
    public class FileUploadParseFileWithProgressServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            DiskFileItemFactory fac = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(fac);
            upload.setFileSizeMax(10 * 1024 * 1024);
            upload.setSizeMax(20 * 1024 * 1024);
    
            if (upload.isMultipartContent(request)) {
                try {
                    List<FileItem> list = upload.parseRequest(request);
                    for (FileItem item : list) {
                        if (item.isFormField()) {
                            String fileName = item.getFieldName();
                            String value = item.getString("UTF-8");
                            System.out.println("普通表单, " + fileName + ":" + value);
                        } else {
                            //为了避免上传文件重名,在前面拼接一个随机串
                            String name = item.getName();
                            String id = UUID.randomUUID().toString();
                            name = id + name;
    
                            //上传文件放在一个统一的目录
                            String realPath = getServletContext().getRealPath("/upload");
                            File uploadDir = new File(realPath);
                            if (!uploadDir.exists() && !uploadDir.isDirectory()) {
                                uploadDir.mkdirs();
                                System.out.println("创建上传文件目录: " + realPath);
                            } else {
                                System.out.println("上传文件目录: " + realPath + " 已经存在!");
                            }
                            File file = new File(realPath, name);
                            item.write(file);
                            System.out.println("文件" + item.getName() + "上传到" +file.getAbsolutePath());
                            item.delete();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("不处理!");
            }
    
            PrintWriter out = response.getWriter();
            out.println("upload ok!");
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            this.doPost(request, response);
        }
    }
    

    然后在WEB-INF/web.xml中添加servlet映射:

    <servlet>
        <servlet-name>FileUploadParseFileWithProgressServlet</servlet-name>
        <servlet-class>com.trial.servlet.FileUploadParseFileWithProgressServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FileUploadParseFileWithProgressServlet</servlet-name>
        <url-pattern>/FileUploadParseWithProgressDemo</url-pattern>
    </servlet-mapping>
    

    在项目中添加jquery依赖js/jquery-3.6.0.js

    2、文件上传展示进度的实现

    2.1 原理

    原理上来说,还是比较简单的,就是通过XMLHttpRequest上传用户选中的文件,然后在传输过程中,可以指定传输过程中、传输完成、传输报错等分别执行不同的回调函数。上传文件的进度就是通过这里的上传过程中,计算已经上传完文件大小和总大小的关系,来更新一个进度条dom元素的样式,实现上传进度展现。

    2.2 代码实现

    代码如下。
    indexParseFileShowUploadProgress1.jsp

    <%--
      Created by IntelliJ IDEA.
      User: chengxia
      Date: 2021/10/31
      Time: 4:40 PM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>$Title$</title>
        <script src="/js/jquery-3.6.0.js"></script>
        <style>
          #progressBar {
            width: 0%;
            height: 20px;
            background-color: greenyellow;
          }
        </style>
      </head>
      <body>
      <form id="form1" enctype="multipart/form-data" method="post" action="/FileUploadParseWithProgressDemo"> <div class="row">
        <label for="fileToUpload">Select a File to Upload</label>
        <input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected();"/>
      </div>
        <div id="fileName"></div>
        <div id="fileSize"></div>
        <div id="fileType"></div>
        <div class="row">
          <input type="button" onclick="uploadFile()" value="Upload" />
        </div>
        <div id="progressNumber"></div>
        <div id="progressBar"></div>
      </form>
      <script>
          function fileSelected() {
              var file = document.getElementById('fileToUpload').files[0];
              if (file) {
                  var fileSize = 0;
                  if (file.size > 1024 * 1024)
                      fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
                  else fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
                  document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
                  document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
                  document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
              }
          }
    
          function uploadFile() {
              var fd = new FormData();
              fd.append("fileToUpload", document.getElementById('fileToUpload').files[0]);
              var xhr = new XMLHttpRequest();
              xhr.upload.addEventListener("progress", uploadProgress, false);
              xhr.addEventListener("load", uploadComplete, false);
              xhr.addEventListener("error", uploadFailed, false);
              xhr.addEventListener("abort", uploadCanceled, false);
              xhr.open("POST", "/FileUploadParseWithProgressDemo");
              xhr.send(fd);
          }
    
          function uploadProgress(evt) {
              if (evt.lengthComputable) {
                  var percentComplete = Math.round(evt.loaded * 100 / evt.total);
                  document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
                  $("#progressBar").css("width", percentComplete.toString() + '%');
                  console.log('上传已完成' + percentComplete.toString() + '%');
              } else {
                  document.getElementById('progressNumber').innerHTML = 'unable to compute';
              }
          }
    
          function uploadComplete(evt) {
            /* This event is raised when the server send back a response */
              alert(evt.target.responseText);
          }
    
          function uploadFailed(evt) {
              alert("There was an error attempting to upload the file.");
          }
    
          function uploadCanceled(evt) {
              alert("The upload has been canceled by the user or the browser dropped the connection.");
          }
      </script>
      </body>
    </html>
    

    2.3 运行效果

    服务器启动之后,访问http://localhost:8080/indexParseFileShowUploadProgress1.jsp

    image.png
    选中文件之后:
    image.png
    点击Upload,可以看到进度条和百分比的变化,上传完成之后:
    image.png
    查看javascript控制台,可以看到如下输出:
    image.png
    从这里可以看出,这个上传进度的回调函数被多次调用。

    2.4 上传进度回调说明

    到这里,可能会疑问,能否控制上传进度回调函数调用的频率或者是时间间隔。答案是不可能,原因如下:

    The W3 sets forth the following guidelines in their XMLHttpRequest Level 2 document. Obviously varying levels of conformance across browsers are to be expected.
    Uploads:
    While the request entity body is being uploaded and the upload complete flag is false, queue a task to fire a progress event named progress at the XMLHttpRequestUpload object about every 50ms or for every byte transmitted, whichever is least frequent. - W3 XMLHttpRequest Level 2 (Bolded for emphasis)

    从上面可以看出,这个progress事件每50ms或者每上传一个字节触发一次。具体可能和浏览器的不同实现有关系。

    3、其他示例代码

    这里附上两个其他形式的上传进度展示代码。

    3.1 发送ajax请求上传文件

    代码如下。
    indexParseFileShowUploadProgress2.jsp

    <%--
      Created by IntelliJ IDEA.
      User: chengxia
      Date: 2020/7/5
      Time: 8:05 PM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>File Upload Demo</title>
        <script src="/js/jquery-3.6.0.js"></script>
        <style>
            div {
                width: 0%;
                height: 20px;
                background-color: #f00;
            }
        </style>
    
    </head>
    <body>
    <div></div>
    <input id="progressFileInput" type="file" />
    <script>
        $(function() {
            // 用户选择好文件之后单击弹出层的“打开”按钮的触发事件是:change
            $('input').on('change', function() {
                // 1.收集文件数据
                var myfile = $('#progressFileInput').prop("files")[0];
                var formdata = new FormData()
                formdata.append('file_data', myfile)
    
                // 2.发起ajax请求
                $.ajax({
                    url: '/FileUploadParseWithProgressDemo',
                    type: 'post',
                    data: formdata,
                    processData: false,
                    contentType: false,
                    xhr: function() {
                        var newxhr = new XMLHttpRequest()
                        // 添加文件上传的监听
                        // onprogress:进度监听事件,只要上传文件的进度发生了变化,就会自动的触发这个事件
                        newxhr.upload.onprogress = function(e) {
                            console.log(e)
                            var percent = (e.loaded / e.total) * 100 + '%'
                            $('div').css('width', percent)
                        }
                        return newxhr
                    },
                    success: function(res) {
                        console.log(res)
                    },
                    dataType: 'json'
                })
            })
        })
    </script>
    </body>
    </html>
    </body>
    </html>
    

    3.2 原生的XMLHttpRequest上传文件

    代码如下。
    indexParseFileShowUploadProgress3.jsp

    <%--
      Created by IntelliJ IDEA.
      User: chengxia
      Date: 2020/7/5
      Time: 8:05 PM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>File Upload Demo</title>
        <script src="/js/jquery-3.6.0.js"></script>
        <style>
            div {
                width: 0%;
                height: 20px;
                background-color: #f00;
            }
        </style>
    
    </head>
    <body>
    <div></div>
    <input id="progressFileInput" type="file" />
    <script>
        //将num左补0为len长度的字符串
        function lpadNum(num, len) {
            var l = num.toString().length;
            while(l < len) {
                num = "0" + num;
                l++;
            }
            return num;
        }
        //将传入的Date格式化为"yyyyMMdd HH:mm:ss.SSS"
        function formatDate(d){
            var year = d.getFullYear();
            var month = d.getMonth() + 1;
            var day = d.getDate();
            var hours = d.getHours();
            var minutes = d.getMinutes();
            var seconds = d.getSeconds();
            var milliSeconds = d.getMilliseconds();
            var resStr = year + lpadNum(month, 2) + lpadNum(day, 2) + " " + lpadNum(hours,2) + ":" + lpadNum(minutes,2) + ":" + lpadNum(seconds,2) + "." + lpadNum(milliSeconds, 3);
            return resStr;
        }
        $(function() {
            // 用户选择好文件之后单击弹出层的“打开”按钮的触发事件是:change
            $('input').on('change', function() {
                // 1.收集文件数据
                var myfile =  $('#progressFileInput').prop("files")[0];
                var formdata = new FormData()
                formdata.append('file_data', myfile)
    
                var xhr = new XMLHttpRequest()
    
                xhr.open('post', '/FileUploadParseWithProgressDemo')
    
                // 细节1:文件上传,如果使用fromdata,则不要设置请求头
                xhr.upload.onprogress = function(e) {
                    console.log(e);
                    console.log("onprogress function is called on " + formatDate(new Date()));
                    var percent = (e.loaded / e.total) * 100 + '%'
                    $('div').css('width', percent)
                }
                // 细节2:send中可以直接传递formdata
                xhr.send(formdata)
            })
        })
    </script>
    </body>
    </html>
    </body>
    </html>
    

    3.3 运行效果

    上面的两个例子运行效果一样,无论是访问http://localhost:8080/indexParseFileShowUploadProgress2.jsp还是http://localhost:8080/indexParseFileShowUploadProgress3.jsp效果都如下图:

    image.png
    选中文件之后,将自动上传,并展现上传进度。效果如下:
    image.png

    4、最后的文件结构

    到这里,最后的文件结构如下图:


    image.png

    参考资料

    相关文章

      网友评论

          本文标题:Java Web文件上传展示进度

          本文链接:https://www.haomeiwen.com/subject/bzdorrtx.html