如果您在互联网上,那么您肯定会上传或下载一些文件,如电影、歌曲或任何文件,如 pdf、图像等。这些是任何应用程序或软件的非常常用的功能。
所以,不用浪费太多时间,让我们进入主题,即如何在 Spring Boot 应用程序中实现上传和下载功能。
在这里,我们将构建一个用于上传和下载文件的 Restful API,我们还将使用 Javascript 开发一个非常简单的前端来上传和下载文件。
别担心,我不会使用大量的 javascript。只需五行代码即可实现我们的 Restful API。它是完全可选的。如果您不感兴趣或者如果您想在您的应用程序中添加前端代码,您可以跳过,然后请从本文最后一节下载源代码。
以下是最终的应用程序。
那么,让我们开始吧
注意:视频教程可在本文底部找到。
第 1 步:创建一个 Spring Boot 应用程序。
您可以从任何提到的选项创建。
- 使用 Spring Initialzr 创建一个项目。
- 使用 Spring Boot CLI 创建项目。
从 Spring Initialzr 创建一个项目。
- 转到https://start.spring.io/。
- 输入组名称。com.pixeltrice
- 给出工件 ID。上传-下载-文件-with-spring-boot
- 添加Spring Web依赖项。
- 单击生成项目并开始下载。
- 下载完成后,解压缩文件并将其导入 IDE,例如 Eclipse。
- 按照以下步骤在 Eclipse 中导入文件。
选择 File -> Import -> Existing Maven Projects -> Browse -> 选择文件夹 upload-download-files-with-spring-boot-> Finish。
从 Spring Boot CLI 创建项目。
- 在您的系统中下载并安装 Spring Boot CLI。请按照文章从 Spring Boot CLI 运行应用程序。
- 打开命令提示符并键入以下命令。
spring init --name=upload-download-files-with-spring-boot --dependencies=web upload-download-files-with-spring-boot
3. 按 Enter 并检查项目的以下路径
Project extracted to 'C:\Users\1302143\upload-download-files-with-spring-boot'
伟大的!您成功完成了第一步。
步骤 2:在 application.properties 文件中配置服务器和文件存储属性
在这里,我们将配置以下内容。
- 在 Spring Boot Application 中启用分段文件上传。
- 定义文件的最大上传大小。
- 配置上传文件的保存目录。
转到 application.properties 文件。
# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=300MB
# Max Request Size
spring.servlet.multipart.max-request-size=315MB
## File Storage Properties
# All files uploaded through the REST API will be stored in this directory
file.upload-dir=C:/Users/1302143/uploads
注意:您可以更改路径file.upload-dir 的值。如果您是开发人员并希望在您的项目中实现上传功能,请根据您的要求更改上传存储文件位置。
第 3 步:创建 Pojo 或模型类并绑定或分配 application.properties 文件的属性
Spring Boot 为我们提供了一个非常独特的功能,可以自动将属性值绑定或分配给 Pojo 类中存在的参数。
为此,我们需要在 Pojo 类中使用@ConfigurationProperties注解,如下所示。
package com.pixeltrice.uploaddownloadfileswithspringboot;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "file")
public class FileStoragePojo {
private String uploadDir;
public String getUploadDir() {
return uploadDir;
}
public void setUploadDir(String uploadDir) {
this.uploadDir = uploadDir;
}
}
因此,在上面的代码中,prefix = “file”用于查找以“file”开头的属性,该属性存在于application.properties 文件中,并将该值分配给FileStoragePojo的 uploadDir变量。java类。
在我们的例子中,一旦你运行 Spring Boot 应用程序,uploadDir 将被赋值为 C:/Users/1302143/uploads,因为在application.properties文件中,我们只有一个以前缀“file”开头的属性,即
file.upload-dir=C:/Users/1302143/uploads
注意:如果您以后想在 application.properties 中添加更多以“file”开头的属性,那么您只需在 Pojo 类中添加参数,这样它就会自动为该参数分配值。
第 4 步:在主类中启用配置属性
为了启用该属性,我们需要在主类或带有@SpringBootApplication标记的类中添加@EnableConfigurationProperties注解,或者您可以将@EnableConfigurationProperties添加到任何其他配置类中。
package com.pixeltrice.uploaddownloadfileswithspringboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({
FileStoragePojo.class
})
public class UploadDownloadFilesWithSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(UploadDownloadFilesWithSpringBootApplication.class, args);
}
}
恭喜!您已经完成了所有配置部分,现在我们可以继续编写实际的代码行来上传和下载文件。
第 5 步:创建一个用于存储和检索文件的服务类。
我们将创建一个名为FileStorageService.java的类,以便将文件存储在我们的系统上并检索它。
package com.pixeltrice.uploaddownloadfileswithspringboot;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
@Service
public class FileStorageService {
private final Path fileStorageLocation;
@Autowired
public FileStorageService(FileStoragePojo fileStoragePojo) {
this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Unable to create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
public Resource loadFileAsResource(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if(resource.exists()) {
return resource;
} else {
throw new MentionedFileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
throw new MentionedFileNotFoundException("File not found " + fileName, ex);
}
}
}
服务类中存在的每一行代码的解释。
- FileStorageService 构造函数的解释。
public FileStorageService(FileStoragePojo fileStoragePojo) {
this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Unable to create the directory where the uploaded files will be stored.", ex);
}
}
- 在上面的代码行中,我们定义了一个参数化的构造函数,我们在其中传递了一个我们已经在上一段中创建的FileStoragePojo对象。
- 指定上传后文件将存储的路径。
this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
.toAbsolutePath().normalize();
- 在将保存上传文件的路径中创建文件夹或目录。
Files.createDirectories(this.fileStorageLocation);
2.storeFile()方法说明
public String storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
- 在第一行代码中,我们从文件保存的路径中获取文件的实际名称,例如,假设我们在以下路径C:/Users/1302143/uploads/中上传了一个名为 springBoot.pdf 的文件springBoot.pdf。
现在在路径上方,我们使用以下代码行提取文件的原始名称 springBoot.pdf。
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
所以fileName = springBoot.pdf
同样,你可以很容易地得到另一行代码的解释,我以注释的形式提到过。
注意: storeFile() 方法的全部目的是返回原始文件名。
3. loadFileAsResource(String fileName) 方法说明
该方法将以资源对象的形式返回存储或上传的文件。
public Resource loadFileAsResource(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if(resource.exists()) {
return resource;
} else {
throw new MentionedFileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
throw new MentionedFileNotFoundException("File not found " + fileName, ex);
}
}
精彩的!我对 Service 类中编写的代码有一些想法。如果您在上面的代码中注意到我们正在使用某种异常处理类,例如FileStorageException和MentionedFileNotFoundException。
实际上这些异常不是预定义的类,我们必须自己创建它。
注意:编写在FileStorageService.java服务类中的代码我们将在后面的部分中创建 API 时使用。
第 6 步:创建异常处理类
- FileStorageException.java
package com.pixeltrice.uploaddownloadfileswithspringboot;
public class FileStorageException extends RuntimeException {
public FileStorageException(String message) {
super(message);
}
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
2. MentionedFileNotFoundException.java
package com.pixeltrice.uploaddownloadfileswithspringboot;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class MentionedFileNotFoundException extends RuntimeException {
public MentionedFileNotFoundException(String message) {
super(message);
}
public MentionedFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
在上面的代码中,我使用了 @ResponseStatus(HttpStatus.NOT_FOUND),如果没有找到提到的文件,它将返回 404 状态。
第 7 步:为 API 创建一个控制器类以上传和下载文件。
package com.pixeltrice.uploaddownloadfileswithspringboot;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@RestController
public class FileStorageController {
@Autowired
private FileStorageService fileStorageService;
@PostMapping("/upload-single-file")
public UploadFileResponse uploadSingleFile(@RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/download-file/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
@PostMapping("/upload-multiple-files")
public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file -> uploadSingleFile(file))
.collect(Collectors.toList());
}
@GetMapping("/download-file/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
// Load file as Resource
Resource resource = fileStorageService.loadFileAsResource(fileName);
// Try to determine file's content type
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
System.out.print("Could not determine file type.");
}
// Fallback to the default content type if type could not be determined
if(contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
}
控制器类中存在的每个 api 的说明。
- 上传文件
@PostMapping("/upload-single-file")
public UploadFileResponse uploadSingleFile(@RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/download-file/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
上面的 api 用于上传单个文件并存储在系统上的上述位置。
2.上传多个文件
@PostMapping("/upload-multiple-files")
public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file -> uploadSingleFile(file))
.collect(Collectors.toList());
}
通过使用上述 api,我们可以一起上传多个文件。
3. 下载文件的API。
@GetMapping("/download-file/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
// Load file as Resource
Resource resource = fileStorageService.loadFileAsResource(fileName);
// Try to determine file's content type
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
System.out.print("Could not determine file type.");
}
// Fallback to the default content type if type could not be determined
if(contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
如果您在上面的代码中注意到我们使用了一个名为UploadFileResponse的对象作为返回类型。基本上我们需要创建这个类,它只负责在该 API 被触发或调用时正确返回响应。
UploadFileResponse.java
package com.pixeltrice.uploaddownloadfileswithspringboot;
public class UploadFileResponse {
private String fileName;
private String fileDownloadUri;
private String fileType;
private long size;
public UploadFileResponse(String fileName, String fileDownloadUri, String fileType, long size) {
this.fileName = fileName;
this.fileDownloadUri = fileDownloadUri;
this.fileType = fileType;
this.size = size;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileDownloadUri() {
return fileDownloadUri;
}
public void setFileDownloadUri(String fileDownloadUri) {
this.fileDownloadUri = fileDownloadUri;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}
第 8 步:运行应用程序并从 POSTMAN 进行测试
- 上传单个文件
在这一步中,我们从POSTMAN中调用API http://localhost:8080/upload-single-file,并以Key-Value对的形式传递参数,如图所示。一旦 API 被调用,您将得到如下所示的响应。
验证文件是否上传到 application.properties 文件C:/Users/1302143/uploads中提到的路径
您可以在上图中看到图像已保存在上述路径中。
2.上传多个文件
调用 API http://localhost:8080/upload-multiple-files并上传多个文件,它可以是任何图像、pdf 或任何其他格式。
这里我上传了三个.txt、pdf、png类型的文件,让我们验证它存储在提到的路径C:/Users/1302143/uploads 上。
伟大的!您可以在上图中看到,所有三个文件都已保存在上述位置。
3. 下载文件
转到 POSTMAN 并输入以下 API http://localhost:8080/download-file/Core_java.pdf 确保 fileName 必须正确。
而不是点击发送按钮,首先选择发送和下载,如下图所示。您的文件将被下载。
好吧!最后,我们构建了用于下载和上传多个文件的 RestFul API。
注意:如果您想要前端代码,请从以下链接下载源代码。
只需在eclipse工作空间下的路径src\main\resources\static下添加三个前端文件,分别是 main.css、index.html和main.js。
Eclipse 中文件夹的最终结构。
概括
非常感谢您阅读本文。在本文中,我们学习了如何实现使用 Spring Boot 上传和下载文件的功能。如果您有任何疑问或疑问,请随时在评论框中提问。
你也可以看看我的文章。
- 从头开始使用 Hibernate 和 Postgresql 构建 Spring Boot Restful CRUD API
- 使用 OAuth2 和 JWT 的 Spring Boot 安全性
- 在外部 Tomcat 服务器上部署 Spring Boot 应用程序
- 如何使用 Spring Boot 应用程序发送电子邮件
- 从 Spring Boot CLI 运行应用程序
- 如何使用 Thymeleaf 开发 Spring Boot Web 应用程序