【SpringMVC】7—文件上传

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

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

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


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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一棵___大树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值