本篇文章介绍了如何在spring boot应用中上传文件夹。
HTMLInputElement.webkitdirectory
在浏览器中,我们一般通过 <input type="file"/>
标签选择要上传的文件。默认情况下,它只能选择一个文件或者多个文件,不能直接选择整个文件夹。
<input/>
标签如果有一个名为 webkitdirectory
的属性,用户就可以选择整个文件夹,浏览器会把文件夹下的所有文件一次性地上传到服务器。
HTMLInputElement.webkitdirectory
是属于<input>
元素的一个 HTML 属性webkitdirectory
,它指示<input>
元素应该允许用户选择文件目录,而不是文件。当一个文件目录被选中,该目录及其整个内容层次结构将包含在所选项目集里面。可以使用webkitEntries
属性获取选定的文件系统条目
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件夹上传</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple webkitdirectory/>
<input type="submit"/>
</form>
</body>
</html>
浏览器会把文件在文件夹中的相对路径作为文件名称提交到服务器,服务器可以根据这个相对路径在本地创建文件,从而保证和客户端的目录结构保持一致。
Controller
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 谷歌浏览器,文件夹上传
* @author KevinBlandy
*
*/
@RestController
@RequestMapping("/upload")
public class UploadController {
private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);
/**
* 默认工作目录下的 public 目录是WEB静态资源目录,客户端可以直接访问。
*/
private static final Path PUBLIC_DIR = Paths.get(System.getProperty("user.dir"), "public");
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload (HttpServletRequest request) throws IOException, ServletException{
for (var part : request.getParts()) {
// 把客户端的路径分隔符,转换为服务器的文件路径分割符
String fileName = FilenameUtils.separatorsToSystem(part.getSubmittedFileName());
// 基于 public 目录,解析文件在本地磁盘的绝对路径
Path file = PUBLIC_DIR.resolve(fileName);
// 尝试创建文件所在的目录
if (Files.notExists(file.getParent())) {
Files.createDirectories(file.getParent());
}
// 把数据写入到文件
try (var inputStream = part.getInputStream()){
Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
}
LOGGER.info("write file: [{}] {}", part.getSize(), file);
}
return "ok";
}
}
测试
选择目录后,会弹出警告框,确认后会显示所选文件夹中的文件数量。
后台输出日志,输出的目录以及文件都没有问题。
2022-11-29 12:13:13.650 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [6] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\demo\public\empty.txt
2022-11-29 12:13:13.652 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [0] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\demo\logs\access.log
2022-11-29 12:13:13.656 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [12312] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\demo\logs\app.log
文件夹中文件的布局和客户端也是一致的。
C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\demo>tree /F
文件夹 PATH 列表
卷序列号为 D0CB-79F6
C:.
├─logs
│ access.log
│ app.log
│
└─public
empty.txt
最后
你也可以选择使用javascript异步上传文件。
document.querySelector('input[name="file"]').addEventListener('change', e => {
let formData = new FormData();
// 遍历文件夹中的每一个文件
for (let file of e.target.files){
// file.webkitRelativePath 属性就是文件在目录中的相对路径
// 浏览器会把 file.webkitRelativePath 作为文件的名称
formData.append("file", file);
}
// 上传
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
if(response.ok){
response.text().then(payload => {
console.log(payload);
});
}
}).catch(error => {
console.error(error);
});
});