前言

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

* 前言
* 文件上传
* 前端设计
* Java后端设计
* CommonsMultipartResolver文件上传解析器
* 监听器
* Progress Bean
* controller
* 误区
* 在Spring的配置文件中的CommonMutipartResolver配置的id名称
* 轮询的方式
* Demo文件下载地址

文件上传

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

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

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

 <!-- 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>

前端设计

效果如图:

<%@ 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;
        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) {
                    clearInterval(interval);
                }
                $("#fill").css("width", progressdata+"%");
                $("#fill").text(progressdata+"%");
                console.log(progressdata);
            }
        });
    }
</script>
</html>

这里解释下:

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

Java后端设计

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

CommonsMultipartResolver文件上传解析器

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

/*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中配置:

 <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方法。

监听器代码如下:


/*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,方便数据传递。

代码如下:

/*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类。创建好文件夹后,便需要将它上传到刚才创建好的文件夹中。

/*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中取出值
	 * @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 result;
	}
}

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

误区

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

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

/**
	 * 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