文件上传与下载
场景
在项目中一般来说都需要进行文件的上传和下载,文件存储分为两种情况,一种为直接存储在服务器后端中,另一种则是可以存储在其他的文件服务中,如Minio,下篇博客将会介绍如何使用。
文件上传
文件上传可以通过File类以及各种文件流和缓冲流进行读取和写入,文件的存储路径,可以使用一个Properties类读取配置文件。
@RequestMapping(value = "/upload", method = RequestMethod.POST)
// 接口返回类型随意,接口参数固定为MultipartFile类型,注解的参数一般为file,因为前端的input文件标签会以file为名,如要修改需和前端同步
public Result<Map> fileUpload(@RequestParam("file") MultipartFile file) throws IOException {
String dirPath = sysCommonProperties.getPath();
File dirFile = new File(dirPath);
String fileName = null;
if (!dirFile.exists()) {
dirFile.mkdirs();
}
else {
// MultipartFile可以直接获取文件的Stream流,放入缓冲流中快速读取
BufferedInputStream bis = new BufferedInputStream(file.getInputStream());
// 通过UUID为文件重命名,以防同名文件覆盖,但是不能改变原文件后缀名,通过getOriginalFilename方法获得原始名称
fileName = UUID.randomUUID() + Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().indexOf("."));
// 创建新文件的out流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dirPath + "/" + fileName));
// 以byte[]方式读取文件
byte[] buffer = new byte[1024];
int readCount = 0;
while((readCount=bis.read(buffer))!=-1)
bos.write(buffer, 0, readCount);
// 写完后记得flush,不然会导致最后一次数据丢失
bos.flush();
bos.close();
bis.close();
}
Map<String, String> r = new HashMap<String, String>();
r.put("fileName", fileName);
return Result.ok(r);
}
文件下载
// 请求方式为GET
@RequestMapping(value = "/download", method = RequestMethod.GET)
// 接口参数根据需求自定义,接口返回类型一般使用ResponseEntity<byte[]>,响应体数据类型是byte[]
public ResponseEntity<byte[]> downloadFile(@RequestParam(value = "name") String name) throws IOException {
String filePath = sysCommonProperties.getPath() + "/" + name;
// 使用文件系统类FileSystemResource
Resource resource = new FileSystemResource(filePath);
if (name == null || name.isEmpty()) {
throw new CustomException(ResultCodeEnum.DATA_ERROR);
}
if (resource.exists()) {
File file = resource.getFile();
// 设置响应头内容,ContentType和attachment是必填项,attachment为文件名字
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setContentDispositionFormData("attachment", file.getName());
// 通过Files工具类获得文件的所有byte
return new ResponseEntity<>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Resource类和ResourceLoader类复习
Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口是为了统一各种类型不同的资源而定义的,Spring 提供了若干 Resource 接口的实现类,这些实现类可以轻松地加载不同类型的底层资源,并提供了获取文件名、URL 地址以及资源内容的操作方法。
假设有一个文件地位于 Web 应用的类路径下,您可以通过以下方式对这个文件资源进行访问: 通过 FileSystemResource 以文件系统绝对路径的方式进行访问; 通过 ClassPathResource 以类路径的方式进行访问; 通过 ServletContextResource 以相对于Web应用根目录的方式进行访问。 相比于通过 JDK 的 File 类访问文件资源的方式,Spring 的 Resource 实现类无疑提供了更加灵活的操作方式,您可以根据情况选择适合的 Resource 实现类访问资源。下面,我们分别通过 FileSystemResource 和 ClassPathResource 访问同一个文件资源:
Resource
String filePath = "D:/masterSpring/chapter23/webapp/WEB-INF/classes/conf/file1.txt";
// 使用系统文件路径方式加载文件
Resource res1 = new FileSystemResource(filePath);
// 使用类路径方式加载文件
Resource res2 = new ClassPathResource("conf/file1.txt");
InputStream ins1 = res1.getInputStream();
InputStream ins2 = res2.getInputStream();
访问类路径下的资源还可以使用类加载器方式
Thread.currentThread().getContextClassLoader().getResourceAsStream("classpath:/***.**");
ResourceLoader
而Spring框架为了更方便的获取资源,尽量弱化程序员对各个Resource接口实现类的感知与分辨,降低学习与使用成本,定义了另一个接口,就是:ResourceLoader接口。 (1)此接口有一个特别重要的方法:Resource getResource(String location)。返回的对象,就是Spring容器中Resource接口的实例 (2)Spring内所有的ApplicationContext实例(包括Spring自启动容器或者用户手动创建的其他容器),都实现了这个方法
public class ResourceTest implements ApplicationContextAware{
ApplicationContext applicationContext ;
public void getResourceTest() {
//通过applicationContext,只一步getResource(),就可以获取资源
Resource resource = applicationContext.getResource("spring-mvc.xml");
//TODO: 用此resource来获取想要的资源
//......
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
因为ApplicationContext在内置的Resource实现类的类型是根据ApplicationContext创建容器的方式决定的。
如果用ClassPathXmlApplicationContext启动的Spring容器,则底层Resource是ClassPathResource实例
如果用FileSystemXmlApplicationContext启动的Spring容器,则底层Resource是FileSystemResource实例
如果用XmlWebApplicationContext启动的Spring容器,则底层Resource是ServletContextResource实例
如果要强制使用不同的方式加载文件:
//强制使用ClassPathResource
Resource resource = applicationContext.getResource("classpath:spring-mvc.xml");
//强制使用UrlResource
Resource resource = applicationContext.getResource("file:book.xml");
与BeanNameAware、ApplicationContextAware这些接口类似,Spring会自动调用:implements了ResourceLoaderAware接口类的实现方法:setResourceLoader(),将ApplicationContext的ResourceLoader注入进去。之后对它getResource(),就可以获取到系统的Resource了
public class ResourceBean implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}