本小节你将建立一个可以接受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