Java SpringBoot实现文件上传下载
1. 业务概述
业务开发中文件上传与下载在Web应用中是一个比较常见的功能。通常采用两张方式:
1.上传至自定义的文件服务器。
2.上传至云服务器,例如阿里云OSS,AWS等
阿里云OSS:https://help.aliyun.com/product/31815.html?spm=a2c4g.750001.list.24.7a184c07xKtHUD
AWS S3:https://aws.amazon.com/cn/s3/
云服务相关文档很详细就不在介绍,本文主要是springboot实现文件的上传下载功能。
2. 环境准备
JDK: Java 1.8
Framework: Spring Boot 2.5.0(Only Using Spring Web MVC)
Maven: Maven 3.5.0+
IDE: IntelliJ IDEA 2019.2
springfox-swagger2 2.6.1
3. 实现验证
工程目录结构说明如下:
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依赖配置文件。
3.1 文件对象 UploadFile
package com.example.category.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 文件对象
*
* @author zrj
* @since 2021/8/17
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UploadFile {
private String fileName;
private String url;
}
3.2 文件上传接口 FileStorageService
package com.example.category.service;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.stream.Stream;
/**
* 文件上传接口
*
* @author zrj
* @since 2021/8/17
**/
public interface FileStorageService {
/**
* 文件初始化
*/
void init();
/**
* 文件上传
*/
void fileUpload(MultipartFile multipartFile);
/**
* 文件下载
*/
Resource load(String filename);
/**
* 文件加载
*/
Stream<Path> load();
/**
* 文件全部删除
*/
void clear();
/**
* 文件删除
*/
void fileDeleted(String fileName);
}
3.3 文件上传实现类 FileStorageServiceImpl
package com.example.category.service.impl;
import com.example.category.service.FileStorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.core.io.Resource;
import java.io.File;
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.util.stream.Stream;
/**
* 文件上传实现类
*
* @author zrj
* @since 2021/8/17
**/
@Slf4j
@Service("fileStorageService")
public class FileStorageServiceImpl implements FileStorageService {
private final Path path = Paths.get("fileStorage");
@Override
public void init() {
log.info("文件管理初始化...path=" + path);
try {
boolean exists = new File(path.toUri()).exists();
if (!exists) {
log.info("文件路径不存在创建");
Files.createDirectory(path);
}
} catch (IOException e) {
throw new RuntimeException("Could not initialize folder for upload!");
}
}
@Override
public void fileUpload(MultipartFile multipartFile) {
log.info("文件上传...path=" + path);
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) {
log.info("文件下载...path=" + path);
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() {
log.info("文件下载...path=" + path);
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() {
log.info("文件clear...path=" + path);
if (false) {
FileSystemUtils.deleteRecursively(path.toFile());
}
}
@Override
public void fileDeleted(String fileName) {
log.info("文件删除...fileName=" + fileName);
}
}
3.4 文件上传初始化配置 FileUploadConfiguration
package com.example.category.config;
import com.example.category.service.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
/**
* 文件上传初始化配置
*
* @author zrj
* @since 2021/8/17
**/
@Service
public class FileUploadConfiguration implements CommandLineRunner {
@Autowired
FileStorageService fileStorageService;
@Override
public void run(String... args) throws Exception {
fileStorageService.clear();
fileStorageService.init();
}
}
3.5 文件控制器 FileUploadController
3.6 统一结果集 Response
package com.example.category.controller;
import com.example.category.entity.Response;
import com.example.category.entity.UploadFile;
import com.example.category.service.FileStorageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件控制器
*
* @author zrj
* @since 2021/8/17
**/
@RestController
@RequestMapping("/file")
@Api(tags = "文件管理", description = "文件上传")
public class FileUploadController {
@Autowired
FileStorageService fileStorageService;
/**
* 文件上传
*/
@PostMapping("/fileUpload")
@ApiOperation(value = "文件上传", httpMethod = "POST")
public Response<String> fileUpload(@RequestParam("file") MultipartFile file) {
try {
fileStorageService.fileUpload(file);
return Response.success("fileUpload success", file.getOriginalFilename());
} catch (Exception e) {
return Response.success("fileUpload failed", file.getOriginalFilename());
}
}
/**
* 文件列表
*/
@GetMapping("/fileList")
@ApiOperation(value = "文件列表", httpMethod = "GET")
public Response<List<UploadFile>> fileList() {
List<UploadFile> fileList = 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 Response.success("fileList success", fileList);
}
/**
* 文件下载
* {filename:.+}:正则表达式匹配, 语法: {varName:regex} 前面是变量名,后面是表达式。
* 匹配出现过一次或多次.的字符串 如: "hello.png"
*/
@GetMapping("/files/{filename:.+}")
@ApiOperation(value = "文件下载", httpMethod = "GET")
public ResponseEntity<Resource> getFile(@PathVariable("filename") String filename) {
// 根据文件名读取文件
Resource file = fileStorageService.load(filename);
// @ResponseBody 用于直接返回结果(自动装配)
// ResponseEntity 可以定义返回的 HttpHeaders 和 HttpStatus (手动装配)
// ResponseEntity.ok 相当于设置 HttpStatus.OK (200)
// CONTENT_DISPOSITION 该 标志将通知浏览器启动下载
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + file.getFilename() + "\"")
.body(file);
}
/**
* 文件删除
*/
@PostMapping("/fileDeleted")
@ApiOperation(value = "文件删除", httpMethod = "POST")
public Response<String> fileDeleted(@RequestParam("fileName") String fileName) {
try {
fileStorageService.fileDeleted(fileName);
return Response.success("fileDeleted success", fileName);
} catch (Exception e) {
return Response.success("fileDeleted failed", fileName);
}
}
}
3.7 配置文件 application.yml
spring.servlet.multipart.max-request-size=50MB: 单次请求所能上传文件的总文件大小
spring.servlet.multipart.max-file-size=50MB:单个文件所能上传的文件大小
3.8 全局异常处理 FileUploadExceptionAdvice
package com.example.category.exception;
import com.example.category.entity.Message;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.naming.SizeLimitExceededException;
/**
* @author zrj
* @since 2021/8/17
**/
@ControllerAdvice
public class FileUploadExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(SizeLimitExceededException.class)
public ResponseEntity<Message> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
return ResponseEntity.badRequest().body(new Message("Upload file too large."));
}
}