SpringMVC文件AJAX上传+JQuery文件进度条

前言

文件上传是一个很常用的功能,比如头像上传,视频上传等等,本文主要使用SpringMVC+Ajax实现文件上传,下载,删除等操作,同时重写CommonsMultipartResolver添加监听器ProgressListener,通过客户端轮询的方式来获取上传文件的进度。本文会首先介绍文件AJAX上传并获取进度的操作。

文件上传

一般就文件上传的方式来说,有两种:

  • 第一种:使用FORM表单提交的方式,在这种提交的方式中,需要将FORM表单的==method==设置为POST方式,同时需要将==enctype==设置为multipart/form-data
  • 第二种:使用AJAX的方式,使用这种方式来获取form表单中的二进制流也有两种实现策略:
    • 1、使用FormData对象:FormData对象是HTML5 的一个对象,目前的很多浏览器已经兼容。
    • 2、使用Jquery.form.js插件:它提供了大量的操作表单的方法。详情可以点击这里查看官方文档

在开始之前需要添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>

前端设计

效果如图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>文件上传下载</title>
<script src="jquery/jquery-3.3.1.js"></script>
</head>
<body>
<style>
#progressbar {
width: 200px;
border: 1px solid darkgray;
height: 15px;
border-radius: 1rem;
margin-top: -10px;
display: none;
}
#fill {
height: 15px;
text-align: center;
line-height: 15px;
border-radius: 1rem;
background-color: mediumturquoise;
}
</style>


<div id="progressbar">
<div id="fill"></div>
</div>
<form action="#" id="form">
<input type="file" name="file"/>
<input type="button" value="上传" onclick="upload()"/>
</form>
</body>
</html>
<script type="text/javascript">
var interval;
function upload() {
var formData = new FormData($("form")[0]);
interval = setInterval(getProgress, 100);//开启定时器(间歇调用)
$.ajax({
url: "/upload",
type: "POST",
data: formData,
cache: false,
processData: false,//此处需要设置为false
contentType: false,//此处需要设置为false
success: function (data) {
console.log(data);
}
});
}
//轮询获取文件上传进度的方法
function getProgress() {
$("#progressbar").show()
$.ajax({
url: "getInfo",
type: "get",
success: function (progressdata) {
if (progressdata == "stop") {
clearInterval(interval);
}
$("#fill").css("width", progressdata+"%");
$("#fill").text(progressdata+"%");
console.log(progressdata);
}
});
}
</script>
</html>

这里解释下:

使用的是刚才说到的第一种方式,即使用FormData对象的方式来进行ajax上传。

Java后端设计

刚才说到了我们的核心思想分为下面几步:

CommonsMultipartResolver文件上传解析器

1、重写CommonsMultipartResolver文件上传解析器,如果只是文件上传是不需要重写的,这里需要用到重写是因为需要设置文件上传的监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*CustomMultipartResolver.java*/
public class CustomMultipartResolver extends CommonsMultipartResolver {
@Autowired
FileUploadProgressListener progressListener;
@Override
public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);//获取字符编码,这个很重要
FileUpload fileUpload = prepareFileUpload(encoding);//获取FileUpload对象
progressListener.setSession(request.getSession());//保存数据到session
fileUpload.setProgressListener(progressListener);//设置监听器
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);//解析数据
return parseFileItems(fileItems, encoding);
} catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
} catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
}

当然了以上的代码除了需要自己添加设置监听器的代码,其他的可以参考源码实现:

enter description here

为了让Spring知道这个multipartResolver的存在,还需要在springMVC.xml中配置:

1
2
3
4
<bean id="multipartResolver" class="com.dimple.resolver.CustomMultipartResolver">
<!--<property name="defaultEncoding" value="UTF-8"/>-->
<!--<property name="maxUploadSize" value="102400000 "/> &lt;!&ndash; 最大文件大小限制 &ndash;&gt;-->
</bean>

监听器

2、既然说到了要监听,那么就需要设置一个监听器,此监听器是实现了ProgressListener接口,重写了update方法。

监听器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

/*FileUploadProgressListener.java*/
@Component
public class FileUploadProgressListener implements ProgressListener {

private HttpSession session;

/**
* 设置Session,这样能够将状态保存在session域中
* @param session
*/
public void setSession(HttpSession session) {
this.session = session;
Progress status = new Progress();
session.setAttribute("status", status);
}

/**
* 文件上传会回调的update方法
* @param bytesRead 已经读取到的字节数
* @param contentLength 该上传文件的总字节数
* @param items 当前是上传第几个文件,默认为1
*/
@Override
public void update(long bytesRead, long contentLength, int items) {
Progress status = (Progress) session.getAttribute("status");
status.setBytesRead(bytesRead);
status.setContentLength(contentLength);
status.setItems(items);
}
}

Progress Bean

3、以上可以看到有一个Progress类,此类为JavaBean,方便数据传递。

代码如下:

1
2
3
4
5
6
7
8
/*Progress.java*/
public class Progress {
private long bytesRead;//已经上传的字节数
private long contentLength;//所有文件的总长度
private long startTime = System.currentTimeMillis();//开始上传的时间
private int items;//正在上传第几个文件
/*Geter and Seter*/
/*toString*/

controller

4、以上所有都是为了Controller服务的,在Controller中,需要实现两个目标:

- 实现文件上传
- 实现进度查询

文件上传的方式很简单:通过File类来设置文件上传的路径的问题,比如这个要放到哪个文件夹中,判断文件夹是否存在等等,还有文件的删除也需要用到File类。创建好文件夹后,便需要将它上传到刚才创建好的文件夹中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*FileUploadController.java*/
@Controller
public class FileUploadController {
/**
* 文件上传的处理
*
* @param request request用于获取Session,方便向session存值
* @param file 文件上传类
* @return 文件上传状态字符串:ok,error
*/
@ResponseBody
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(HttpServletRequest request, @RequestParam("file") CommonsMultipartFile[] file) {
String path = request.getSession().getServletContext().getRealPath("upload");
for (int i = 0; i < file.length; i++) {
String fileName = file[i].getOriginalFilename();//获取原始的文件名称
System.out.println(path);
File targetFile = new File(path, fileName);
if (!targetFile.exists()) {
targetFile.mkdirs();//创建文件夹
}
try {
file[i].transferTo(targetFile);//将文件转移到指定的文件夹中
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
return "ok";
}

/**
* 用于处理客户端轮询获取文件上传进度
* @param request 用于从session中取出值
* @return 如果文件上传完毕返回stop,告诉客户端停止轮询;如果还没有上传完,返回当前进度。
* @throws IOException
*/
@RequestMapping("getInfo")
@ResponseBody
public String getProgress(HttpServletRequest request) throws IOException {
Progress progress = (Progress) request.getSession().getAttribute("status");
System.out.println(progress);
DecimalFormat decimalFormat = new DecimalFormat("0");
String result = decimalFormat.format((float) progress.getBytesRead() / (float) progress.getContentLength() * 100);
if ("100".equals(result)) {
return "stop";
}
return result;
}
}

在getProgress方法中,就很简单了,由于监听器是将数据放在session域里面的,只需要将数据取出来进行处理传递到客户端即可。

误区

在Spring的配置文件中的CommonMutipartResolver配置的id名称

此处的id必须要为multipartResolver,因为在SpringMV的DispatcherServlet中有一个==initMultipartResolver==方法,方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Initialize the MultipartResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* no multipart handling is provided.
*/
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}

在注释上就说明了如果不按照这个名字进行配置的话,就不会有multipart处理。

轮询的方式

在这个例子中,使用的是普通的轮询方法,就是隔一段时间就向客户端发送请求,服务器收到请求后马上返回响应消息并马上关闭连接。这种写法很简单,但是带来的问题也是显而易见的,因为这样的请求中,就拿我们的文件上传的进度来说,如果文件足够大,500ms去请求服务器,可能很多次都是获取的同样的数据,这样的方式占用带宽比较严重,比较浪费服务器的资源。

Demo文件下载地址

Click Me

-------------The End-------------

本文标题:SpringMVC文件AJAX上传+JQuery文件进度条

文章作者:Dimple

发布时间:2018年08月08日 - 05:08

最后更新:2018年09月28日 - 17:09

原始链接:http://www.bianxiaofeng.com/2018/08/08/2018-08-08-05-56-11/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

na,给我一个棒棒糖!
0%