文件上传与下载在Web应用中是一个比较常见的功能。在本教程中,我将基于Spring 2.2.6版本实现一个基于Restful风格的文件上传与下载APIs。
基于Spring Boot 2.0实战系列源码已经Push到Github仓库:https://github.com/ramostear/springboot2.0-action 。感兴趣朋友欢迎Star/Fork。
本教程中,使用Spring 2.2.6实现Restful风格的APIs并提供以下的功能:
下面是教程所实现的APIs列表(服务端请求端口默认8080):
请求方式 | URL地址 | 说明 |
---|---|---|
POST | /upload | 上传一份文件 |
GET | /files | 获取已上传文件列表 |
GET | /files/{filename} | 根据链接地址下载文件 |
工程目录结构说明如下:
本教程是基于IntelliJ IDEA创建Spring Boot项目的,你也可以选择自己喜欢的IDE创建项目。创建完项目后,请检查pom.xml文件中是否包含如下配置:
org.springframework.boot
spring-boot-starter-web
本教程只使用到Spring Web MVC的功能,因此只需添加spring-boot-starter-web依赖。
按照面向接口编程的约定(规范),创建一个用于操作上传文件的接口类FileStorageService.java,并提供相应的方法。
service/FileStorageService.java
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;
/**
* @ClassName FileStorageService
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 18:35
* @Version since 1.0
**/
public interface FileStorageService {
void init();
void save(MultipartFile multipartFile);
Resource load(String filename);
Stream load();
void clear();
}
在启动应用时,先调用clear()方法清理历史文件,再调用init()方法初始化文件上传地址。
文件上传接口实现类比较简单,这里直接给出代码:
service/impl/FileStorageServiceImpl.java
/**
* @ClassName FileStorageServiceImpl
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 18:38
* @Version since 1.0
**/
@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 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包中提供的类。
本教程中,定义了两个简单的对象UploadFile.java和Message.java,分别封装了上传文件信息和响应消息,代码如下:
valueobject/UploadFile.java
/**
* @ClassName UploadFile
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 18:48
* @Version since 1.0
**/
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
/**
* @ClassName Message
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 19:21
* @Version since 1.0
**/
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;
}
}
在controller包下创建文件上传控制器,用于处理客户端的请求。代码如下:
controller/FileUploadController.java
/**
* @ClassName FileUploadController
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 18:52
* @Version since 1.0
**/
@RestController
public class FileUploadController {
@Autowired
FileStorageService fileStorageService;
@PostMapping("/upload")
public ResponseEntity 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> files(){
List 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 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的快捷方式注解方法。
通常,出于安全和性能考虑,我们需要限定客户端上传文件的大小,本教程限定的文件大小最大为50MB。在application.yml(application.properties)文件中添加如下配置:
application.yml
spring:
servlet:
multipart:
max-request-size: 50MB
max-file-size: 50MB
application.properties
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:单个文件所能上传的文件大小
在控制器中,文件上传过程中可能产生的异常我们使用try-catch语句进行了用户友好处理,但当客户端上传文件大小超过50MB时,应用会抛出MaxUploadSizeExceededException异常信息,我们需要对此异常信息做处理。最简单的方式是使用@ControllerAdvice+@ExceptionHandler组合方式处理异常。在exception包下创建异常处理类,代码如下:
exception/FileUploadExceptionAdvice.java
/**
* @ClassName FileUploadExceptionAdvice
* @Description TODO
* @Author 树下魅狐
* @Date 2020/4/28 0028 19:10
* @Version since 1.0
**/
@ControllerAdvice
public class FileUploadExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e){
return ResponseEntity.badRequest().body(new Message("Upload file too large."));
}
}
为了在测试时获得干净的测试数据,同时也为了在应用启动后分配好上传文件存储地址,我们需要在config包下创建一个配置类,在应用启动时调用FileStorageService中的clear()方法和init()方法。实现该功能,最快的方式是配置类实现CommandLineRunner接口类的run()方法,代码如下:
config/FileUploadConfiguration.java
@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中。
运行Spring Boot应用程序的方式有很多,例如:
选择一种你比较熟悉的方式运行Spring Boot应用程序。当应用程序启动成功后,在项目的根目录会创建一个名为fileStorage的文件夹,该文件夹将用于存放客户端上传的文件。
应用程序启动成功后,我们使用Postman对应用程序中的APIs进行测试。
执行结果:
文件上传成功后,我们可以查看项目根目录下的fileStorage文件夹,检查是否有文件被存储到当中:
/files接口将返回所有已上传的文件信息,我们可以点击其中任意一个链接地址下载文件。在Postman中,可以通过header选项卡查看响应头中文件的详细信息,例如:
你也可以复制列表中的链接地址,并在浏览器中访问该地址,浏览器会弹出一个下载询问对话框,点击确定按钮进行下载。
本章节介绍了Spring Boot 2.0实现基于Restful风格的文件上传和下载APIs,并使用Postman工具对APIs进行测试,达到了设计的预期结果。你可以通过下面的链接地址获取本次教程的相关源代码。
Github仓库地址
https://github.com/ramostear/springboot2.0-action
如果你在运行本次教程提供的源代码过程中遇到什么问题,请在评论区与我联系。
未经允许,请勿转载!