在Spring Boot应用中实现文件夹上传

本篇文章介绍了如何在spring boot应用中上传文件夹。

HTMLInputElement.webkitdirectory

在浏览器中,我们一般通过 <input type="file"/> 标签选择要上传的文件。默认情况下,它只能选择一个文件或者多个文件,不能直接选择整个文件夹。

<input/> 标签如果有一个名为 webkitdirectory 的属性,用户就可以选择整个文件夹,浏览器会把文件夹下的所有文件一次性地上传到服务器。

HTMLInputElement.webkitdirectory 是属于<input>元素的一个 HTML 属性webkitdirectory,它指示<input> 元素应该允许用户选择文件目录,而不是文件。当一个文件目录被选中,该目录及其整个内容层次结构将包含在所选项目集里面。可以使用 webkitEntries 属性获取选定的文件系统条目

HTMLInputElement.webkitdirectory - Web API 接口参考 | MDN

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";
	}
}

测试

选择目录后,会弹出警告框,确认后会显示所选文件夹中的文件数量。

image

后台输出日志,输出的目录以及文件都没有问题。

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);
	});
});

文中的东西,都是原生实现。不需要任何的第三方依赖,除了:

FilenameUtilscommons-io包的一个工具类。客户端和服务器的操作系统可能不一样,文件分隔符也就不一样,这个工具类可以把客户端的文件分隔符修改为服务器的文件分隔符。

<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.11.0</version>
</dependency>

群主好厉害(

1 Like

学习了