springboot多文件异步上传带进度条的详细实现

文件上传,很常见的功能。

SpringBoot

配置

# 是否开启文件上传
spring.servlet.multipart.enabled=true
# 文件大小阈值,当大于这个阈值时将写入到磁盘(临时目录),否则存在内存中
spring.servlet.multipart.file-size-threshold=0B
# IO文件的临时目录
spring.servlet.multipart.location=
# 单个文件最大大小
spring.servlet.multipart.max-file-size=1MB
# 整个请求体最大大小
spring.servlet.multipart.max-request-size=10MB
# 是否延迟解析multipart请求
spring.servlet.multipart.resolve-lazily=false

Controller

使用MultipartFile作为文件的接收参数,如果有多个同名的文件参数,那么可以使用数组或者List

import java.io.IOException;
import java.nio.file.Paths;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


@RestController
@RequestMapping("/upload")
public class UploadController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);
	
	@PostMapping
	public Object test (@RequestParam("files") MultipartFile[] files) {
		
		for (MultipartFile file : files) {
			
			// 文件的原始名称
			String originalFilename = file.getOriginalFilename();
			
			// 文件的大小 
			long size = file.getSize();
			
			// 文件的ContentType
			String contentType = file.getContentType();
			
			LOGGER.info("originalFilename={},  size={}, contentType={}", originalFilename, size, contentType);
			
			try {
				// 序列化到磁盘
				file.transferTo(Paths.get("D:\\upload", originalFilename));
			} catch (IllegalStateException | IOException e) {
				e.printStackTrace();
			}
		}
		return "ok";
	}
}

浏览器上传

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>文件上传</title>
	</head>
	<body>
		<input type="file" 
			accept="image/*" 	
			multiple="multiple" 
			onchange="fileChange(event);"/>
	</body>
	<script type="text/javascript">
		function fileChange(event) {
			
			// 获取到FileList对象,它不是数组,可迭代
			const files = event.target.files;
			
			if (!files){
				// 未选择文件
				return ;
			}
			
			const formData = new FormData();
			
			for (const file of files){
				// 文件的名字
				const name = file.name;
				// 最后修改的时间戳
				const lastModified = file.lastModified;
				// 最后修改的本地时间
				const lastModifiedDate = file.lastModifiedDate;
				// 文件的绝对路径
				const webkitRelativePath = file.webkitRelativePath;
				// 文件的大小
				const size = file.size;
				// 文件的类型
				const contentType = file.type;
				
				// 添加文件到formData
				formData.append('files',  file);
			}
			
			const xhr = new XMLHttpRequest();
			xhr.open('POST','/upload',true);
			// 监听上传进度
			if(xhr.upload){
				xhr.upload.addEventListener('progress', event => {
					let percent  = (event.loaded / event.total) * 100;
					percent = percent.toFixed(2);
					console.log(`上传进度:${percent}`)
				}, false);
			}
			xhr.send(formData);
			xhr.onreadystatechange = event => {
				if(event.target.readyState == 4 && event.target.status == 200){
					// 获取服务器的响应
					const responseBody = xhr.responseText;
					console.log(`服务器响应:${responseBody}`);
				}
			}
		}
	</script>
</html>

<input/>的属性

  • type 设置当前输入框是文件选择框
  • accept 允许选择的文件类型
  • multiple 允许一次选择多个文件
  • onchange 监听选择文件的变化

FormData不能直接append(FileList)

在上传多个文件的时候,也许你以为这样更省事儿

const files = event.target.files;
const formData = new FormData();
formData.append('files',  files);

实际行不通,append方法不支持FileList参数,只能自己一个个的append,否则FormData不能正确的进行编码。

除了append以外FormData还有一个set方法。

const formData = new FormData();
formData.set('files',  files);

区别:set是覆盖,append是添加。