SpringMVC文件上传到本地仓库

目录

1.前置maven依赖

2.编写springmvc.xml注册文件解析器

3.在web.xml中配置SpringMVC的前端控制器

4.编写前后端代码


1.前置maven依赖

这一步最主要的是在maven中导入两个与文件上传相关的依赖(别忘了提前写好SpringMVC的相关依赖)。

XML 复制代码
    <!-- 文件上传依赖 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.15.1</version>
    </dependency>

2.编写springmvc.xml注册文件解析器

在springmvc配置文件中创建一个id值为multipartResolver的bean对象,class指向"org.springframework.web.multipart.commons.CommonsMultipartResolver",并配置其中相关的属性值:

XML 复制代码
<!-- 文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 编码,解决中文文件名乱码 -->
    <property name="defaultEncoding" value="UTF-8"/>
    <!-- 单个文件最大 10MB -->
    <property name="maxUploadSizePerFile" value="10485760"/>
    <!-- 单次请求所有文件总和最大20MB -->
    <property name="maxUploadSize" value="20971520"/>
    <!-- 小于2KB存在内存,超过写入临时文件 -->
    <property name="fileSizeThreshold" value="2048"/>
    <!-- 延迟解析,方便全局捕获上传超限异常 -->
    <property name="resolveLazily" value="true"/>
</bean>

如果觉得太麻烦,快速实验:

XML 复制代码
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--只配置一个限制文件上传大小就行了-->
        <property name="maxUploadSize" value="10485760"/>
    </bean>

3.在web.xml中配置SpringMVC的前端控制器

这一步如果已经写好了web.xml就不用再配置了:

XML 复制代码
  <!--配置前端控制器,对浏览器发送的请求进行统一处理-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--加载springmvc.xml配置文件的位置和名称,配置的是Spring配置-->
    <init-param>
      <!--contextConfigLocation:上下文配置路径,固定值-->
      <param-name>contextConfigLocation</param-name>
      <!--classpath:类路径,值得是Java和resources文件夹-->
      <!--springmvc.xml:指的是配置文件的名称:需要配置springmvc.xml,在下面-->
      <param-value>classpath:Springmvc.xml</param-value>
    </init-param>
    <!--配置启动加载-->
    <load-on-startup>1</load-on-startup>
  </servlet>

以上都是一些和配置相关的代码,下面就可以开始我们的文件上传试验了。

4.编写前后端代码

我们首先在前端创建一个一个简单的input标签上传文件和一个input按钮,并使用jquery类选择器选中按钮:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/jquery.js"></script>
</head>
<body>
<div>文件上传实验</div>
<input class="file-value" type="file"><br><br>
<input class="file-input-button" type="button" value="上传">
<script>
    $(".file-input-button").on("click",function(){
        
    })
</script>
</body>
</html>

后端在Controller层创建一个接口和问价接收方法设置接口的url为:"input_file",前端使用ajax请求访问后端接口,此时还不要着急传文件到后端。

后端input_file方法的参数类型设置为MultipartFile类型,返回类型设置为String,如果想要设置传递参数码,可以将返回类型设置为HashMap类型。

后端Controller层:

java 复制代码
@Controller
public class AlmController {
    @ResponseBody
    @RequestMapping("/input_file")
    public String input_file(@RequestParam("uploadFile") MultipartFile uploadFile) {

    }
}

回到前端,在"file-input-button"的类选则器中,创建一个file元素,并另file元素指向"file-value"标签中选中的文件:

html 复制代码
let file = $(".file-value")[0].files[0];

解释:jQuery 选择器,获取页面上 class="file-value" 的 <input type="file"> 标签。

返回的不是原生 DOM 对象,是 jQuery 包装对象。

这个包装对象中并没有我们需要的files属性,所以需要想办法从包装对象中取出原生的DOM对象,然后调用袁尚DOM对象的files属性,才能获取文件完整的内容。

所以第一个索引0表示从jquery包装对象中拿到第一个类名为"file-value"的DOM对象,然后使用这个DOM对象的files属性。然后有泪一个索引0 获取第一个选中文件 File 对象,里面包含:

文件名、大小、后缀、文件二进制数据,现在需要想办法将获得的文件上传到后端,肯定不能是以字符串形式上传的,所以这里还有问题:文件从前端上传到后端的类型是什么类型?

Formdata格式

Formdata是浏览器内置 API,专门用来模拟表单提交,可以携带:普通文本、图片 / 文件、数组参数,自动处理 multipart/form-data 格式,是前端文件上传标准方案。

普通 {key:file} JSON 对象发送时,文件会被转成 object File 字符串,后端拿不到二进制。只有 FormData 能携带二进制流,自动封装成后端 MultipartFile 能解析的请求体。

那我们清楚了,在前端创建Formdata格式对象,将Formdata格式对象返回后端,再由后端的MultipartFile类型数据来接收并使用相关方法获取文件。

前端创建Formdata对象并发起ajax请求:

html 复制代码
        let fd = new FormData();
        fd.append("uploadFile", file);

        $.ajax({
            url:"/input_file",
            type:"POST",
            contentType:false,// 1. 禁止jQuery自动设置Content-Type,交给FormData自己生成
            processData:false,// 2. 禁止jQuery把data转成字符串(FormData不能序列化)
            // FormData 本身就是完整请求体载体,data 直接赋值 fd 即可,不要再包一层 {}
            data: fd,

            //处理请求失败
            success:function(res){
                alert("上传成功:" + res);
            },
            error:function(){
                alert("上传失败");
            }
        })

MultipartFile类型数据

MultipartFile是SpringMVC 封装的接口,用来接收前端通过 multipart/form-data 上传的文件。

作用是用来接收浏览器传过来的文件,提供方法获取文件名、文件大小、文件二进制流,还能直接保存到服务器本地。

使用前提: 前端用 FormData 上传文件,后端参数写 @RequestParam("Formdata参数名") MultipartFile xxx

常用方法:

isEmpty():判断有没有传文件

getOriginalFilename():拿到用户本地的文件名

getInputStream():拿到文件数据流

transferTo(File):把文件存到服务器硬盘

MultipartFile对象中的文件信息分两种存储形式,由 fileSizeThreshold 阈值控制:
文件很小(小于阈值)
文件二进制数据直接存内存字节数组,全部放在 MultipartFile 对象里,没有临时磁盘文件。
文件很大(大于阈值)
不会全放内存,浏览器上传后写入服务器临时磁盘文件;
MultipartFile 对象只保存这个临时文件的路径、文件信息,真正的二进制存在硬盘临时文件里,用到时再去磁盘读取。

由此我们可以知晓,MultipartFile对象已经帮助我们保存了文件的相关信息(二进制流的形式),我们现在需要做的就是找到一个文件夹目录最为仓库将MultipartFile对象中的文件信息读取出来,顺便使用UUID给文件重新命名防止文件名发生冲突。

所以后端的思路大致就是:

1.获得原文件名并拼接上UUID

2.通过transferTo方法将文件保存到指定的目录下,并使用新创的文件名

中间可以穿插判断文件后缀是否合法等操作。

后端input_file方法代码:

java 复制代码
    if (uploadFile.isEmpty()) {
        return "未接收到文件";
    }
    String savePath = 改成自己的文件夹路径;
    File dir = new File(savePath);
    if (!dir.exists()) {
        dir.mkdirs();
    }

    // 1. 获取原始文件名,只用来截取后缀,不再参与路径拼接
    String originalName = uploadFile.getOriginalFilename();
    String suffix = originalName.substring(originalName.lastIndexOf("."));
    // 2. UUID生成全新唯一文件名,杜绝重名+路径穿越
    String storeFileName = UUID.randomUUID().toString().replace("-", "") + suffix;

    // 3. 安全拼接路径,存储名完全由后端控制,不受前端文件名影响
    File target = new File(savePath + storeFileName);
    try {
        uploadFile.transferTo(target);
        // 数据库可以保存 storeFileName + originalName
        return "success upload,存储文件名:" + storeFileName;
    } catch (IOException e) {
        e.printStackTrace();
        return "lose upload";
    }

快速实验(直接使用原来的文件名保存,无法防止文件重名):

后端input_file方法代码:

java 复制代码
    @ResponseBody
    @RequestMapping("/input_file")
    public String input_file(@RequestParam("uploadFile") MultipartFile uploadFile) {
        if (uploadFile.isEmpty()) {
            return "未接收到文件";
        }
        // 保存文件到本地,可自行修改路径
        String savePath = 改成自己的文件夹路径;
        File dir = new File(savePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File target = new File(savePath + uploadFile.getOriginalFilename());
        try {
            uploadFile.transferTo(target);
            return "success upload";
        } catch (IOException e) {
            e.printStackTrace();
            return "lose upload";
        }
    }

前端完整代码(jquery需要自己引入):

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../js/jquery.js"></script>
</head>
<body>
<div>文件上传实验</div>
<input class="file-value" type="file"><br><br>
<input class="file-input-button" type="button" value="上传">
<script>
    $(".file-input-button").on("click",function(){
        let file = $(".file-value")[0].files[0];
        if(!file) return alert("请选择文件");

        let fd = new FormData();
        fd.append("uploadFile", file);

        $.ajax({
            url:"/input_file",
            type:"POST",
            contentType:false,
            processData:false,
            data: fd,
            success:function(res){
                alert("上传成功:" + res);
            },
            error:function(){
                alert("上传失败");
            }
        })
    })
</script>
</body>
</html>

或者前端可以使用form表单格式提交也是一样的,但是必须加上 enctype="multipart/form-data",method=post

html 复制代码
<form action="/input_file" method="post" enctype="multipart/form-data">
    文件:<input type="file" name="uploadFile">
    <button type="submit">上传</button>
</form>