文件上传与下载在Web应用中是一个比较常见的功能。在本教程中,我将基于Spring 2.2.6版本实现一个基于Restful风格的文件上传与下载APIs。
Part1环境
- JDK: Java 1.8
- Framework: Spring Boot 2.2.6(Only Using Spring Web MVC)
- Maven: Maven 3.5.0+
- IDE: IntelliJ IDEA 2019.2
- Test: Postman 7.23.0
Part2功能
本教程中,使用Spring 2.2.6实现Restful风格的APIs并提供以下的功能:
- 客户端上传文件到服务端
- 对客户端上传文件大小进行限制(50MB)
- 点击链接地址下载文件
- 获得已上传文件列表(文件名和下载地址)
下面是教程所实现的APIs列表(服务端请求端口默认8080):
Part3工程结构
工程目录结构说明如下:
- config/FileUploadConfiguration.java: 常规组件,主要在重启应用时清理历史文件;
- controller/FileUploadController.java: 主要的控制器,负责处理文件的上传,下载,浏览等请求;
- exception/FileUploadExceptionAdvice.java: 全局的异常处理类,提供用户友好的异常提示信息;
- service/FileStorageService.java: 文件上传接口类,提供存储地址初始化,保存文件,加载文件,清理文件等操作;
- service/impl/FileStorageServiceImpl.java: 文件上传接口实现类;
- valueobject/UploadFile.java: 封装了文件名和存储地址的POJO类;
- valueobject/Message.java: 请求/响应的消息对象;
- resources/application.yml: 项目配置文件,主要配置了文件上传大小限制;
- pom.xml:Maven依赖配置文件。
Part4创建Spring Boot项目
本教程是基于IntelliJ IDEA创建Spring Boot项目的,你也可以选择自己喜欢的IDE创建项目。创建完项目后,请检查pom.xml文件中是否包含如下配置:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
本教程只使用到Spring Web MVC的功能,因此只需添加spring-boot-starter-web依赖。
4.1 文件上传接口
按照面向接口编程的约定(规范),创建一个用于操作上传文件的接口类FileStorageService.java,并提供相应的方法。
service/FileStorageService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.ramostear.springboot.uploadfile.service; import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Path; import java.util.stream.Stream;
public interface FileStorageService { void init(); void save(MultipartFile multipartFile); Resource load(String filename); Stream<Path> load(); void clear(); }
|
在启动应用时,先调用clear()方法清理历史文件,再调用init()方法初始化文件上传地址。
4.2 实现文件上传接口
文件上传接口实现类比较简单,这里直接给出代码:
service/impl/FileStorageServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
@Service("fileStorageService") public class FileStorageServiceImpl implements FileStorageService { private final Path path = Paths.get("fileStorage"); @Override public void init() { try { Files.createDirectory(path); } catch (IOException e) { throw new RuntimeException("Could not initialize folder for upload!"); } } @Override public void save(MultipartFile multipartFile) { try { Files.copy(multipartFile.getInputStream(),this.path.resolve(multipartFile.getOriginalFilename())); } catch (IOException e) { throw new RuntimeException("Could not store the file. Error:"+e.getMessage()); } } @Override public Resource load(String filename) { Path file = path.resolve(filename); try { Resource resource = new UrlResource(file.toUri()); if(resource.exists() || resource.isReadable()){ return resource; }else{ throw new RuntimeException("Could not read the file."); } } catch (MalformedURLException e) { throw new RuntimeException("Error:"+e.getMessage()); } } @Override public Stream<Path> load() { try { return Files.walk(this.path,1) .filter(path -> !path.equals(this.path)) .map(this.path::relativize); } catch (IOException e) { throw new RuntimeException("Could not load the files."); } } @Override public void clear() { FileSystemUtils.deleteRecursively(path.toFile()); } }
|
其中,Files、Path和Paths是java.nio.file提供的类,Resource是org.springframework.core.io包中提供的类。
4.3 定义值对象
本教程中,定义了两个简单的对象UploadFile.java和Message.java,分别封装了上传文件信息和响应消息,代码如下:
valueobject/UploadFile.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
public class UploadFile { private String fileName; private String url; public UploadFile(String fileName, String url) { this.fileName = fileName; this.url = url; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
|
valueobject/Message.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class Message { private String message; public Message(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
|
4.4 控制器
在controller包下创建文件上传控制器,用于处理客户端的请求。代码如下:
controller/FileUploadController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
@RestController public class FileUploadController { @Autowired FileStorageService fileStorageService; @PostMapping("/upload") public ResponseEntity<Message> upload(@RequestParam("file")MultipartFile file){ try { fileStorageService.save(file); return ResponseEntity.ok(new Message("Upload file successfully: "+file.getOriginalFilename())); }catch (Exception e){ return ResponseEntity.badRequest() .body(new Message("Could not upload the file:"+file.getOriginalFilename())); } } @GetMapping("/files") public ResponseEntity<List<UploadFile>> files(){ List<UploadFile> files = fileStorageService.load() .map(path -> { String fileName = path.getFileName().toString(); String url = MvcUriComponentsBuilder .fromMethodName(FileUploadController.class, "getFile", path.getFileName().toString() ).build().toString(); return new UploadFile(fileName,url); }).collect(Collectors.toList()); return ResponseEntity.ok(files); } @GetMapping("/files/{filename:.+}") public ResponseEntity<Resource> getFile(@PathVariable("filename")String filename){ Resource file = fileStorageService.load(filename); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\""+file.getFilename()+"\"") .body(file); } }
|
在控制器中,使用@RestController
组合注解替换了@Controller
+@ResponseBody
的注解方式,并采用@RequestMapping
的快捷方式注解方法。
4.5配置上传文件大小
通常,出于安全和性能考虑,我们需要限定客户端上传文件的大小,本教程限定的文件大小最大为50MB。在application.yml(application.properties)文件中添加如下配置:
application.yml
1 2 3 4 5
| spring: servlet: multipart: max-request-size: 50MB max-file-size: 50MB
|
application.properties
1 2
| spring.servlet.multipart.max-request-size=50MB spring.servlet.multipart.max-file-size=50MB
|
- spring.servlet.multipart.max-request-size=50MB: 单次请求所能上传文件的总文件大小
- spring.servlet.multipart.max-file-size=50MB:单个文件所能上传的文件大小
4.6 全局异常处理
在控制器中,文件上传过程中可能产生的异常我们使用try-catch语句进行了用户友好处理,但当客户端上传文件大小超过50MB时,应用会抛出MaxUploadSizeExceededException异常信息,我们需要对此异常信息做处理。最简单的方式是使用@ControllerAdvice
+@ExceptionHandler
组合方式处理异常。在exception包下创建异常处理类,代码如下:
exception/FileUploadExceptionAdvice.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@ControllerAdvice public class FileUploadExceptionAdvice extends ResponseEntityExceptionHandler { @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity<Message> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e){ return ResponseEntity.badRequest().body(new Message("Upload file too large.")); } }
|
4.7 初始化文件存储空间
为了在测试时获得干净的测试数据,同时也为了在应用启动后分配好上传文件存储地址,我们需要在config包下创建一个配置类,在应用启动时调用FileStorageService中的clear()方法和init()方法。实现该功能,最快的方式是配置类实现CommandLineRunner接口类的run()方法,代码如下:
config/FileUploadConfiguration.java
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class FileUploadConfiguration implements CommandLineRunner { @Autowired FileStorageService fileStorageService; @Override public void run(String... args) throws Exception { fileStorageService.clear(); fileStorageService.init(); } }
|
使用@Autowired
注解将FileStorageService注入到FileUploadConfiguration.java中。
Part5运行程序并测试
运行Spring Boot应用程序的方式有很多,例如:
- 命令方式:mvn spring-boot:run
- IntelliJ IDEA:点击IntelliJ IDEA的“Run”按钮
- main()方法:直接运行主类中的main()方法
- 运行jar包:java -jar springboot-fileupload.jar
选择一种你比较熟悉的方式运行Spring Boot应用程序。当应用程序启动成功后,在项目的根目录会创建一个名为fileStorage的文件夹,该文件夹将用于存放客户端上传的文件。
5.1 使用Postman对APIs进行测试
应用程序启动成功后,我们使用Postman对应用程序中的APIs进行测试。
调用/upload接口上传文件:
上传一个大小超过50MB的文件
执行结果:
检查文件存储文件夹
文件上传成功后,我们可以查看项目根目录下的fileStorage文件夹,检查是否有文件被存储到当中:
调用/files接口,获取所有已上传文件列表
/files接口将返回所有已上传的文件信息,我们可以点击其中任意一个链接地址下载文件。在Postman中,可以通过header选项卡查看响应头中文件的详细信息,例如:
你也可以复制列表中的链接地址,并在浏览器中访问该地址,浏览器会弹出一个下载询问对话框,点击确定按钮进行下载。
Part6 总结
本章节介绍了Spring Boot 2.0实现基于Restful风格的文件上传和下载APIs,并使用Postman工具对APIs进行测试,达到了设计的预期结果。