本小节你将建立一个可以接受HTTP multi-part 文件的服务。
你将建立一个后台服务来接收文件以及前台页面来上传文件。
要利用servlet容器上传文件,你要注册一个MultipartConfigElement类,以往需要在web.xml 中配置<multipart-config>,
而在这里,你要感谢SpringBoot,一切都为你自动配置好了。
1、新建一个文件上传的Controller:
应用已经包含一些 存储文件 和 从磁盘中加载文件 的类,他们在cn.tiny77.guide05这个包下。我们将会在FileUploadController中用到这些类。
1 package cn.tiny77.guide05;
2
3 import java.io.IOException;
4 import java.util.List;
5 import java.util.stream.Collectors;
6
7 import org.springframework.beans.factory.annotation.Autowired;
8 import org.springframework.core.io.Resource;
9 import org.springframework.http.HttpHeaders;
10 import org.springframework.http.ResponseEntity;
11 import org.springframework.stereotype.Controller;
12 import org.springframework.ui.Model;
13 import org.springframework.web.bind.annotation.ExceptionHandler;
14 import org.springframework.web.bind.annotation.GetMapping;
15 import org.springframework.web.bind.annotation.PathVariable;
16 import org.springframework.web.bind.annotation.PostMapping;
17 import org.springframework.web.bind.annotation.RequestParam;
18 import org.springframework.web.bind.annotation.ResponseBody;
19 import org.springframework.web.multipart.MultipartFile;
20 import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
21 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
22
23 @Controller
24 public class FileUploadController {
25
26 private final StorageService storageService;
27
28 @Autowired
29 public FileUploadController(StorageService storageService) {
30 this.storageService = storageService;
31 }
32
33 @GetMapping("/")
34 public String listUploadedFiles(Model model) throws IOException {
35
36 List<String> paths = storageService.loadAll().map(
37 path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
38 "serveFile", path.getFileName().toString()).build().toString())
39 .collect(Collectors.toList());
40
41 model.addAttribute("files", paths);
42
43 return "uploadForm";
44 }
45
46 @GetMapping("/files/{filename:.+}")
47 @ResponseBody
48 public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
49
50 Resource file = storageService.loadAsResource(filename);
51 return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
52 "attachment; filename=\"" + file.getFilename() + "\"").body(file);
53 }
54
55 @PostMapping("/")
56 public String handleFileUpload(@RequestParam("file") MultipartFile file,
57 RedirectAttributes redirectAttributes) {
58
59 storageService.store(file);
60 redirectAttributes.addFlashAttribute("message",
61 "You successfully uploaded " + file.getOriginalFilename() + "!");
62
63 return "redirect:/";
64 }
65
66 @ExceptionHandler(StorageFileNotFoundException.class)
67 public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
68 return ResponseEntity.notFound().build();
69 }
70
71 }
该类用@Controller注解,因此SpringMvc可以基于它设定相应的路由。每一个@GetMapping和@PostMapping注解将绑定对应的请求参数和请求类型到特定的方法。
GET / 通过StorageService 扫描文件列表并 将他们加载到 Thymeleaf 模板中。它通过MvcUriComponentsBuilder来生成资源文件的连接地址。
GET /files/{filename} 当文件存在时候,将加载文件,并发送文件到浏览器端。通过设置返回头"Content-Disposition"来实现文件的下载。
POST / 接受multi-part文件并将它交给StorageService保存起来。
你需要提供一个服务接口StorageService来帮助Controller操作存储层。接口大致如下
1 package cn.tiny77.guide05;
2
3 import org.springframework.core.io.Resource;
4 import org.springframework.web.multipart.MultipartFile;
5
6 import java.nio.file.Path;
7 import java.util.stream.Stream;
8
9 public interface StorageService {
10
11 void init();
12
13 void store(MultipartFile file);
14
15 Stream<Path> loadAll();
16
17 Path load(String filename);
18
19 Resource loadAsResource(String filename);
20
21 void deleteAll();
22
23 }
以下是接口实现类
1 package cn.tiny77.guide05;
2
3 import java.io.IOException;
4 import java.net.MalformedURLException;
5 import java.nio.file.Files;
6 import java.nio.file.Path;
7 import java.nio.file.Paths;
8 import java.nio.file.StandardCopyOption;
9 import java.util.stream.Stream;
10
11 import org.springframework.beans.factory.annotation.Autowired;
12 import org.springframework.core.io.Resource;
13 import org.springframework.core.io.UrlResource;
14 import org.springframework.stereotype.Service;
15 import org.springframework.util.FileSystemUtils;
16 import org.springframework.util.StringUtils;
17 import org.springframework.web.multipart.MultipartFile;
18
19 @Service
20 public class FileSystemStorageService implements StorageService {
21
22 private final Path rootLocation;
23
24 @Autowired
25 public FileSystemStorageService(StorageProperties properties) {
26 this.rootLocation = Paths.get(properties.getLocation());
27 }
28
29 @Override
30 public void store(MultipartFile file) {
31 String filename = StringUtils.cleanPath(file.getOriginalFilename());
32 try {
33 if (file.isEmpty()) {
34 throw new StorageException("无法保存空文件 " + filename);
35 }
36 if (filename.contains("..")) {
37 // This is a security check
38 throw new StorageException(
39 "无权访问该位置 "
40 + filename);
41 }
42 Files.copy(file.getInputStream(), this.rootLocation.resolve(filename),
43 StandardCopyOption.REPLACE_EXISTING);
44 }
45 catch (IOException e) {
46 throw new StorageException("无法保存文件 " + filename, e);
47 }
48 }
49
50 @Override
51 public Stream<Path> loadAll() {
52 try {
53 return Files.walk(this.rootLocation, 1)
54 .filter(path -> !path.equals(this.rootLocation))
55 .map(path -> this.rootLocation.relativize(path));
56 }
57 catch (IOException e) {
58 throw new StorageException("读取文件异常", e);
59 }
60
61 }
62
63 @Override
64 public Path load(String filename) {
65 return rootLocation.resolve(filename);
66 }
67
68 @Override
69 public Resource loadAsResource(String filename) {
70 try {
71 Path file = load(filename);
72 Resource resource = new UrlResource(file.toUri());
73 if (resource.exists() || resource.isReadable()) {
74 return resource;
75 }
76 else {
77 throw new StorageFileNotFoundException(
78 "无法读取文件: " + filename);
79
80 }
81 }
82 catch (MalformedURLException e) {
83 throw new StorageFileNotFoundException("无法读取文件: " + filename, e);
84 }
85 }
86
87 @Override
88 public void deleteAll() {
89 FileSystemUtils.deleteRecursively(rootLocation.toFile());
90 }
91
92 @Override
93 public void init() {
94 try {
95 Files.createDirectories(rootLocation);
96 }
97 catch (IOException e) {
98 throw new StorageException("初始化存储空间出错", e);
99 }
100 }
101 }
2、建立一个Html页面
这里使用Thymeleaf模板
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:if="${message}">
<h2 th:text="${message}"/>
</div>
<div>
<form method="POST" enctype="multipart/form-data" action="/">
<table>
<tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr>
<tr><td></td><td><input type="submit" value="Upload" /></td></tr>
</table>
</form>
</div>
<div>
<ul>
<li th:each="file : ${files}">
<a th:href="${file}" th:text="${file}" />
</li>
</ul>
</div>
</body>
</html>
页面主要分为三部分分
- 顶部展示SpringMvc传过来的信息
- 一个提供用户上传文件的表单
- 一个后台提供的文件列表
3、限制上传文件的大小
在文件上传的应用中通常要设置文件大小的,想象一下后台处理的文件如果是5GB,那得多糟糕!在SpringBoot中,我们可以通过属性文件来控制。
新建一个application.properties,代码如下:
spring.http.multipart.max-file-size=128KB #文件总大小不能超过128kb
spring.http.multipart.max-request-size=128KB #请求数据的大小不能超过128kb
4、应用启动函数
1 package cn.tiny77.guide05;
2
3 import org.springframework.boot.CommandLineRunner;
4 import org.springframework.boot.SpringApplication;
5 import org.springframework.boot.autoconfigure.SpringBootApplication;
6 import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 import org.springframework.context.annotation.Bean;
8
9
10 @SpringBootApplication
11 @EnableConfigurationProperties(StorageProperties.class)
12 public class Application {
13
14 public static void main(String[] args) {
15 SpringApplication.run(Application.class, args);
16 }
17
18 @Bean
19 CommandLineRunner init(StorageService storageService) {
20 return (args) -> {
21 storageService.deleteAll();
22 storageService.init();
23 };
24 }
25 }
5、运行结果
6.项目DEMO : https://github.com/qinrongjin/SpringGuide05