1、文件上传演示准备
接前面“Java Web中的文件上传和下载”
为了演示文件上传,先新增一个用于接收文件上传的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
点击
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
4、最后的文件结构
到这里,最后的文件结构如下图:
image.png
网友评论