Java Web文件上传展示进度

2022-03-07  本文已影响0人  SpaceCat

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

参考资料

上一篇下一篇

猜你喜欢

热点阅读