> 文章列表 > 【SpringMVC】7—文件上传

【SpringMVC】7—文件上传

【SpringMVC】7—文件上传

⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记链接👉https://github.com/A-BigTree/Code_Learning
⭐⭐⭐⭐⭐⭐

如果可以,麻烦各位看官顺手点个star~😊

如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆


文章目录

  • 7 文件上传
    • 7.1 表单
    • 7.2 SpringMVC环境
      • 7.2.1 依赖
      • 7.2.2 配置
    • 7.3 处理方法接受数据
    • 7.4 上传多个文件
      • 7.4.1 请求参数名不同
        • 表单
        • 处理方法
      • 7.4.2 请求参数相同
        • 表单
        • 处理方法
    • 7.5 文件转存
      • 7.5.1 底层机制
      • 7.5.2 本地转存
        • 实现方法
        • 缺陷
      • 7.5.3 文件服务器(采纳)
        • 总体机制
        • 好处
      • 7.5.4 文件服务器类型
        • 上传到其他模块

7 文件上传

7.1 表单

  • 第一点:请求方式必须是POST
  • 第二点:请求体的编码方式必须是multipart/form-data(通过form 标签的enctype属性设置);
  • 第三点:使用input标签、type属性设置为file来生成文件上传框;

如果没有将enctype属性设置为multipart/form-data,则运行时会抛出异常。

7.2 SpringMVC环境

7.2.1 依赖

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version>
</dependency>

7.2.2 配置

在SpringMVC的配置文件中加入multipart类型数据的解析器:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 由于上传文件的表单请求体编码方式是 multipart/form-data 格式,所以要在解析器中指定字符集 --><property name="defaultEncoding" value="UTF-8"/></bean>

CommonsMultipartResolver的bean的id,必须是:multipartResolver 如果不是这个值,会在上传文件时报错。

7.3 处理方法接受数据

@RequestMapping("/simple/upload")
public String doUpload(// 表单提交的数据仍然是请求参数,所以使用 @RequestParam 注解接收@RequestParam("nickName") String nickName,// 对于上传的文件使用 MultipartFile 类型接收其相关数据@RequestParam("picture") MultipartFile picture) throws IOException {String inputName = picture.getName();logger.debug("文件上传表单项的 name 属性值:" + inputName);// 获取这个数据通常都是为了获取文件本身的扩展名String originalFilename = picture.getOriginalFilename();logger.debug("文件在用户本地原始的文件名:" + originalFilename);String contentType = picture.getContentType();logger.debug("文件的内容类型:" + contentType);boolean empty = picture.isEmpty();logger.debug("文件是否为空:" + empty);long size = picture.getSize();logger.debug("文件大小:" + size);byte[] bytes = picture.getBytes();logger.debug("文件二进制数据的字节数组:" + Arrays.asList(bytes));InputStream inputStream = picture.getInputStream();logger.debug("读取文件数据的输入流对象:" + inputStream);Resource resource = picture.getResource();logger.debug("代表当前 MultiPartFile 对象的资源对象" + resource);return "target";
}

7.4 上传多个文件

7.4.1 请求参数名不同

表单

<form th:action="@{/save/head/picture}" method="post" enctype="multipart/form-data">昵称:<input type="text" name="nickName" value="龙猫" /><br/>头像:<input type="file" name="headPicture" /><br/>背景:<input type="file" name="backgroundPicture" /><br/><button type="submit">保存</button>
</form>

处理方法

@RequestMapping("/save/head/picture")
public String saveHeadPicture(@RequestParam("nickName") String nickName,// MultipartFile 是专门接收上传文件的类型// 浏览器端的表单用一个名字携带一个文件:使用单个 MultipartFile 类型变量接收@RequestParam("headPicture") MultipartFile headPicture,// 如果有另外一个名字携带了另外一个文件,那就用另外一个 MultipartFile 接收@RequestParam("backgroundPicture") MultipartFile backgroundPicture) throws IOException {log.debug("[普通表单项] nickName = " + nickName);log.debug("[文件表单项] 请求参数名 = " + headPicture.getName());log.debug("[文件表单项] 原始文件名 = " + headPicture.getOriginalFilename());log.debug("[文件表单项] 判断当前上传文件是否为空 = " + (headPicture.isEmpty()?"空":"非空"));log.debug("[文件表单项] 当前上传文件的大小 = " + headPicture.getSize());log.debug("[文件表单项] 当前上传文件的二进制内容组成的字节数组 = " + headPicture.getBytes());log.debug("[文件表单项] 能够读取当前上传文件的输入流 = " + headPicture.getInputStream());log.debug("[另一个文件] 原始文件名 = " + backgroundPicture.getOriginalFilename());return "target";
}

7.4.2 请求参数相同

表单

<form th:action="@{/save/multi/file}" method="post" enctype="multipart/form-data">文件一:<input type="file" name="fileList" /><br/>文件二:<input type="file" name="fileList" /><br/>文件三:<input type="file" name="fileList" /><br/><button type="submit">保存</button>
</form>

处理方法

@RequestMapping("/save/multi/file")
public String saveMultiFile(// 浏览器端的表单用一个名字携带多个文件:使用 List<MultipartFile> 类型变量接收@RequestParam("fileList") List<MultipartFile> fileList) {for (MultipartFile multipartFile : fileList) {String originalFilename = multipartFile.getOriginalFilename();logger.debug("originalFilename = " + originalFilename);}return "target";
}

7.5 文件转存

7.5.1 底层机制

在这里插入图片描述

7.5.2 本地转存

在这里插入图片描述

实现方法

  1. 创建保存文件的目录;

这个目录如果是空目录,那么服务器部署运行时很容易会忽略这个目录。为了避免这个问题,在这个目录下随便创建一个文件,随便写点内容即可。

  1. 编写转存代码;
……// 1、准备好保存文件的目标目录
// ①File 对象要求目标路径是一个物理路径(在硬盘空间里能够直接找到文件的路径)
// ②项目在不同系统平台上运行,要求能够自动兼容、适配不同系统平台的路径格式
//      例如:Window系统平台的路径是 D:/aaa/bbb 格式
//      例如:Linux系统平台的路径是 /ttt/uuu/vvv 格式
//      所以我们需要根据『不会变的虚拟路径』作为基准动态获取『跨平台的物理路径』
// ③虚拟路径:浏览器通过 Tomcat 服务器访问 Web 应用中的资源时使用的路径
String destFileFolderVirtualPath = "/head-picture";// ④调用 ServletContext 对象的方法将虚拟路径转换为真实物理路径
String destFileFolderRealPath = servletContext.getRealPath(destFileFolderVirtualPath);// 2、生成保存文件的文件名
// ①为了避免同名的文件覆盖已有文件,不使用 originalFilename,所以需要我们生成文件名
// ②我们生成文件名包含两部分:文件名本身和扩展名
// ③声明变量生成文件名本身
String generatedFileName = UUID.randomUUID().toString().replace("-","");// ④根据 originalFilename 获取文件的扩展名
String fileExtname = originalFilename.substring(originalFilename.lastIndexOf("."));// ⑤拼装起来就是我们生成的整体文件名
String destFileName = generatedFileName + "" + fileExtname;// 3、拼接保存文件的路径,由两部分组成
//      第一部分:文件所在目录
//      第二部分:文件名
String destFilePath = destFileFolderRealPath + "/" + destFileName;// 4、创建 File 对象,对应文件具体保存的位置
File destFile = new File(destFilePath);// 5、执行转存
picture.transferTo(destFile);……

缺陷

  • Web 应用重新部署时通常都会清理旧的构建结果,此时用户以前上传的文件会被删除,导致数据丢;
  • 项目运行很长时间后,会导致上传的文件积累非常多,体积非常大,从而拖慢 Tomcat 运行速度;
  • 当服务器以集群模式运行时,文件上传到集群中的某一个实例,其他实例中没有这个文件,就会造成数据不一致;
  • 不支持动态扩容,一旦系统增加了新的硬盘或新的服务器实例,那么上传、下载时使用的路径都需要跟着变化,导致 Java 代码需要重新编写、重新编译,进而导致整个项目重新部署;

7.5.3 文件服务器(采纳)

总体机制

在这里插入图片描述

好处

  • 不受 Web 应用重新部署影响
  • 在应用服务器集群环境下不会导致数据不一致
  • 针对文件读写进行专门的优化,性能有保障
  • 能够实现动态扩容

在这里插入图片描述

7.5.4 文件服务器类型

  • 第三方平台:
    • 阿里的OSS对象存储服务;
    • 七牛云;
  • 自己搭建服务器:FastDFS等;

上传到其他模块

这种情况肯定出现在分布式架构中,常规业务功能不会这么做,采用这个方案的一定是特殊情况,这种情况极其少见。

在这里插入图片描述

MultipartFile接口中有一个对应方法:

/*** Return a Resource representation of this MultipartFile. This can be used* as input to the {@code RestTemplate} or the {@code WebClient} to expose* content length and the filename along with the InputStream.* @return this MultipartFile adapted to the Resource contract* @since 5.1*/
default Resource getResource() {return new MultipartFileResource(this);
}