SpringBoot 2.0實(shí)現(xiàn)基于Restful風(fēng)格的文件上傳與下載
文件上傳與下載在Web應(yīng)用中是一個(gè)比較常見的功能。在本教程中,我將基于Spring 2.2.6版本實(shí)現(xiàn)一個(gè)基于Restful風(fēng)格的文件上傳與下載APIs。
基于Spring Boot 2.0實(shí)戰(zhàn)系列源碼已經(jīng)Push到Github倉(cāng)庫(kù):https://github.com/ramostear/springboot2.0-action 。感興趣朋友歡迎Star/Fork。
Part1環(huán)境
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實(shí)現(xiàn)Restful風(fēng)格的APIs并提供以下的功能:
客戶端上傳文件到服務(wù)端 對(duì)客戶端上傳文件大小進(jìn)行限制(50MB) 點(diǎn)擊鏈接地址下載文件 獲得已上傳文件列表(文件名和下載地址)
下面是教程所實(shí)現(xiàn)的APIs列表(服務(wù)端請(qǐng)求端口默認(rèn)8080):

Part3工程結(jié)構(gòu)

工程目錄結(jié)構(gòu)說明如下:
config/FileUploadConfiguration.java: 常規(guī)組件,主要在重啟應(yīng)用時(shí)清理歷史文件; controller/FileUploadController.java: 主要的控制器,負(fù)責(zé)處理文件的上傳,下載,瀏覽等請(qǐng)求; exception/FileUploadExceptionAdvice.java: 全局的異常處理類,提供用戶友好的異常提示信息; service/FileStorageService.java: 文件上傳接口類,提供存儲(chǔ)地址初始化,保存文件,加載文件,清理文件等操作; service/impl/FileStorageServiceImpl.java: 文件上傳接口實(shí)現(xiàn)類; valueobject/UploadFile.java: 封裝了文件名和存儲(chǔ)地址的POJO類; valueobject/Message.java: 請(qǐng)求/響應(yīng)的消息對(duì)象; resources/application.yml: 項(xiàng)目配置文件,主要配置了文件上傳大小限制; pom.xml:Maven依賴配置文件。
Part4創(chuàng)建Spring Boot項(xiàng)目
本教程是基于IntelliJ IDEA創(chuàng)建Spring Boot項(xiàng)目的,你也可以選擇自己喜歡的IDE創(chuàng)建項(xiàng)目。創(chuàng)建完項(xiàng)目后,請(qǐng)檢查pom.xml文件中是否包含如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
本教程只使用到Spring Web MVC的功能,因此只需添加spring-boot-starter-web依賴。
4.1 文件上傳接口
按照面向接口編程的約定(規(guī)范),創(chuàng)建一個(gè)用于操作上傳文件的接口類FileStorageService.java,并提供相應(yīng)的方法。
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<Path> load();
void clear();
}
在啟動(dòng)應(yīng)用時(shí),先調(diào)用clear()方法清理歷史文件,再調(diào)用init()方法初始化文件上傳地址。
4.2 實(shí)現(xiàn)文件上傳接口
文件上傳接口實(shí)現(xiàn)類比較簡(jiǎn)單,這里直接給出代碼:
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<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());
}
}
其中,F(xiàn)iles、Path和Paths是java.nio.file提供的類,Resource是org.springframework.core.io包中提供的類。
4.3 定義值對(duì)象
本教程中,定義了兩個(gè)簡(jiǎn)單的對(duì)象UploadFile.java和Message.java,分別封裝了上傳文件信息和響應(yīng)消息,代碼如下:
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;
}
}
4.4 控制器
在controller包下創(chuàng)建文件上傳控制器,用于處理客戶端的請(qǐng)求。代碼如下:
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<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
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: 單次請(qǐng)求所能上傳文件的總文件大小
spring.servlet.multipart.max-file-size=50MB:?jiǎn)蝹€(gè)文件所能上傳的文件大小
4.6 全局異常處理
在控制器中,文件上傳過程中可能產(chǎn)生的異常我們使用try-catch語句進(jìn)行了用戶友好處理,但當(dāng)客戶端上傳文件大小超過50MB時(shí),應(yīng)用會(huì)拋出MaxUploadSizeExceededException異常信息,我們需要對(duì)此異常信息做處理。最簡(jiǎn)單的方式是使用@ControllerAdvice+@ExceptionHandler組合方式處理異常。在exception包下創(chuàng)建異常處理類,代碼如下:
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<Message> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e){
return ResponseEntity.badRequest().body(new Message("Upload file too large."));
}
}
4.7 初始化文件存儲(chǔ)空間
為了在測(cè)試時(shí)獲得干凈的測(cè)試數(shù)據(jù),同時(shí)也為了在應(yīng)用啟動(dòng)后分配好上傳文件存儲(chǔ)地址,我們需要在config包下創(chuàng)建一個(gè)配置類,在應(yīng)用啟動(dòng)時(shí)調(diào)用FileStorageService中的clear()方法和init()方法。實(shí)現(xiàn)該功能,最快的方式是配置類實(shí)現(xiàn)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中。
Part5運(yùn)行程序并測(cè)試
運(yùn)行Spring Boot應(yīng)用程序的方式有很多,例如:
命令方式:mvn spring-boot:run IntelliJ IDEA:點(diǎn)擊IntelliJ IDEA的“Run”按鈕 main()方法:直接運(yùn)行主類中的main()方法 運(yùn)行jar包:java -jar springboot-fileupload.jar
選擇一種你比較熟悉的方式運(yùn)行Spring Boot應(yīng)用程序。當(dāng)應(yīng)用程序啟動(dòng)成功后,在項(xiàng)目的根目錄會(huì)創(chuàng)建一個(gè)名為fileStorage的文件夾,該文件夾將用于存放客戶端上傳的文件。

5.1 使用Postman對(duì)APIs進(jìn)行測(cè)試
應(yīng)用程序啟動(dòng)成功后,我們使用Postman對(duì)應(yīng)用程序中的APIs進(jìn)行測(cè)試。
調(diào)用/upload接口上傳文件:

上傳一個(gè)大小超過50MB的文件

執(zhí)行結(jié)果:

檢查文件存儲(chǔ)文件夾
文件上傳成功后,我們可以查看項(xiàng)目根目錄下的fileStorage文件夾,檢查是否有文件被存儲(chǔ)到當(dāng)中:

調(diào)用/files接口,獲取所有已上傳文件列表

/files接口將返回所有已上傳的文件信息,我們可以點(diǎn)擊其中任意一個(gè)鏈接地址下載文件。在Postman中,可以通過header選項(xiàng)卡查看響應(yīng)頭中文件的詳細(xì)信息,例如:

你也可以復(fù)制列表中的鏈接地址,并在瀏覽器中訪問該地址,瀏覽器會(huì)彈出一個(gè)下載詢問對(duì)話框,點(diǎn)擊確定按鈕進(jìn)行下載。
Part66 總結(jié)
本章節(jié)介紹了Spring Boot 2.0實(shí)現(xiàn)基于Restful風(fēng)格的文件上傳和下載APIs,并使用Postman工具對(duì)APIs進(jìn)行測(cè)試,達(dá)到了設(shè)計(jì)的預(yù)期結(jié)果。你可以通過下面的鏈接地址獲取本次教程的相關(guān)源代碼。
Github倉(cāng)庫(kù)地址
https://github.com/ramostear/springboot2.0-action
--完-- 推薦閱讀:
怎么接私貨?這個(gè)渠道你100%有用!請(qǐng)收藏!
喜歡文章,點(diǎn)個(gè)在看

