Spring MVC实现文件上传功能

一、文件上传

在实际的项目开发中,文件的上传和下载可以说是最常用的功能之一,例如个人头像图片的上传与下载、邮件附件的上传和下载等。本节我们将对 Spring MVC 中的文件上传功能进行讲解。

随着我们互联网的发展,我们的用户从直接访问网站获取信息。变为希望将自己本地的资源发送给服务器,让服务器提供给其他人使用或者查看。还有部分的用户希望可以将本地的资源上传服务器存储起来,然后再其他的电脑中可以通过访问网站来获取上传的资源,这样用户就可以打破空间的局限性,再任何时候只要有网有电脑就可以对自己的资源进行操作,比如:云存储,云编辑

1.如何在页面中显示一个按钮?

  • 用户可以点击该按钮后选择本地要上传的文件在页面中使用input标签,type值设置为”file”即可

2.确定上传请求的发送方式

  • 上传成功后的响应结果在当前页面显示,使用ajax请求来完成资源的发送

3.上传请求的请求数据及其数据格式

  • 请求数据:上传的文件本身普通数据:用户名,Id,密码等,建议上传功能中不携带除上传资源以外的数据
  • 数据格式:传统的请求中,请求数据是以键值对的格式来发送给后台服务器的,但是在上传请求中,没有任何一个键可以描述上次的数据,因为数据本身是非常大的键就相当于一个变量,我们使用一个变量存储一个10g的电影显然是不可能 的。在上传请求中,将请求数据以二进制流的方式发送给服务器。

4.在ajax中如何发送二进制流数据给服务器

  • 创建FormData的对象,将请求数据存储到该对象中发送
  • 将processData属性的值设置为false,告诉浏览器发送对象请求数据
  • 将contentType属性的值设置为false,设置请求数据的类型为二进制类型。
  • 正常发送ajax即可

5.上传成功后后台服务器应该响应什么结果给浏览器

  • 并且浏览器如何处理后台服务器处理完成后,响应一个json对象给浏览器,示例格式如: { state:true,msg:“服务器繁忙”,url:”上传成功的资源的请求地址”}

6.文件上传依赖的jar

其他的依赖跟上一个章节一致

<!--文件上传依赖-->
    <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.8.0</version>
    </dependency>

7.配置文件上传组件

这个在springmvc.xml文件中配置

   <!--文件上传解析组件
    id必须为multipartResolver
    springmvc默认使用该id找该组件
    -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

8.准备用户表

准备数据,作为后期保存数据使用

DROP TABLE IF EXISTS `player`;
CREATE TABLE `player`  (
  `id` int(25) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '账号',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',
  `photo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
  `filetype` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '类型',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

9.前端代码

前端利用了jquery完成,这里只是完成了图片的上传,并没有完成回显

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        /*function ()这是页面加载函数*/
        $(function () {
            /*获取id为uploadFile的元素*/
            $("#uploadFile").click(function () {
                //获取需要上传的文件 根据input标签的id属性获取
                var photoFile = $("#photo")[0].files[0]

                console.log(photoFile)
                /*如果不上传文件,没有选中,photoFile则是一个没有内容的为undefined,所以要进行判断*/
                if(photoFile==undefined){
                    alert("您还未选择图片")
                    return;
                }

                //将文件转换成formData对象
                var formData =new FormData();

                //这里添加后的时候,是一个kv的形式 value是上传的文件,key是后台的一个controller控制器方法的参数名相同multipartFile
                formData.append("multipartFile",photoFile)
                //通过Ajax向后台发送文件
                $.ajax({
                    type:"post",/*类型一定要使用post,因为图片转为二进制传递,get无法传递二进制*/
                    data:formData,/*提交的图片数据*/
                    url:"fileUpload.do", /*提交给后台的controller地址*/
                    /*为false表示为后台提交的是一个对象,而不是字符串*/
                    processData:false,
                    /*设置为false表示请求的数据为二进制类型*/
                    contentType:false,
                    success:function (result) {
                        //接受后台的响应result接受响应

                        //图片回显
                    }
                })

                //接受后台的
            })
        })
    </script>
</head>
<body>
<form method="post" action="addPlayer">
    <p>账户:<input type="text" name="name"></p>
    <p>密码:<input type="password" name="password"></p>
    <p>昵称:<input type="text" name="nickname"></p>
    <p>头像:<br>
        <%--这个id是为了后端获取提交数据的--%>
        <input id="photo" type="file">
        <%--javascript:void(0)取消超链接功能--%>
        <a id="uploadFile" href="javascript:void(0)">立即上传</a>
    </p>
    <p><input type="submit" value="注册"></p>
</form>
</body>
</html>

10.controller代码

这里将文件保存到了本地磁盘中

package com.augus.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {
    /**
     * 打开页面
     * @return
     */
    @RequestMapping("/register")
    public String testRegister(){
        return "index";
    }

    @ResponseBody
    @RequestMapping("/fileUpload.do")
    public String fileUpload(MultipartFile multipartFile, HttpServletRequest request) throws IOException {
        //指定文件存储路径
        File file = new File("d:/images");

        //获取文件名
        String originalFilename = multipartFile.getOriginalFilename();

        //文件保存位置和需要保存的文件名称
        File file1 = new File(file, originalFilename);

        //文件保存
        multipartFile.transferTo(file1);
        return "ok";
    }
}

11.测试

选择图片,点击 立即上传,即可在路径下面看到上传的文件

 点击立即上传即可看到上传的图片

二、中文文件名编码和文件存储路径问题

上面实现的文件上传中的几个问题:

  • 中文文件名编码问题:已经通过过滤器解决
  • 文件位置存储问题:放在当前项目下,作为静态资源,这样可以通过URL访问

这里注意上传的文件是在编译后的文件中,并不是你的代码目录中

@Controller
public class FileUploadController {
    /**
     * 打开页面
     * @return
     */
    @RequestMapping("/register")
    public String testRegister(){
        return "index";
    }

    @ResponseBody
    @RequestMapping("/fileUpload.do")
    public String fileUpload(MultipartFile multipartFile, HttpServletRequest request) throws IOException {
        //指定文件存储路径为项目部署环境下的upload目录(编译后的)
        String realPath = request.getServletContext().getRealPath("/upload");
        System.out.println(realPath);
        File filePath = new File(realPath);

        //如果不存在则创建目录
        if(!filePath.exists()){
            filePath.mkdirs();
        }

        //获取文件名
        String originalFilename = multipartFile.getOriginalFilename();

        //文件保存位置和需要保存的文件名称
        File file1 = new File(filePath, originalFilename);

        //文件保存
        multipartFile.transferTo(file1);
        return "ok";
    }
}

在SpringMVC中配置静态资源放行

<mvc:resources mapping="/upload/**" location="/upload/"></mvc:resources>

重新编译启动项目,上传图片,在编译后的项目中即可看到上传的图片

可以通过URL地址直接访问上传的图片http://localhost:8080/springmvc_test04_war_exploded/upload/1.jpg

三、文件名冲突问题

目前上传文件后,如果再次上传同名文件,就会出现覆盖,这时候就要使用UUID对文件名进行重命名,关于UUID说明如下:

UUID全称:Universally Unique Identifier,即通用唯一识别码。

UUID是由一组32位数的16进制数字所构成,是故UUID理论上的总数为16^32 = 2^128,约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。

UUID的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的32个字符,如:550e8400-e29b-41d4-a716-446655440000。

代码如下:

@Controller
public class FileUploadController {
    /**
     * 打开页面
     * @return
     */
    @RequestMapping("/register")
    public String testRegister(){
        return "index";
    }

    /**
     * 处理ajax发送的请求。保存文件
     * @param multipartFile
     * @param request
     * @return
     * @throws IOException
     */
    @ResponseBody
    @RequestMapping("/fileUpload.do")
    public String fileUpload(MultipartFile multipartFile, HttpServletRequest request) throws IOException {
        //指定文件存储路径为项目部署环境下的upload目录(编译后的)
        String realPath = request.getServletContext().getRealPath("/upload");
        System.out.println(realPath);
        File filePath = new File(realPath);

        //如果不存在则创建目录
        if(!filePath.exists()){
            filePath.mkdirs();
        }

        //获取文件名
        String originalFilename = multipartFile.getOriginalFilename();

        //避免文件名冲突,使用UUID替换文件名
        String uuid = UUID.randomUUID().toString();

        //获取图片后缀名 例如.jpg
        // lastIndexOf(".")截取文件中最后一个点,防止有多个.
        String photoType = originalFilename.substring(originalFilename.lastIndexOf("."));

        //组合成新的文件名
        String fileName = uuid.concat(photoType);

        //文件保存位置和需要保存的文件名称
        File file1 = new File(filePath, fileName);

        //文件保存
        multipartFile.transferTo(file1);
        return "ok";
    }
}

在之前的代码基础上,主要是添加如下内容

四、控制文件类型和控制文件大小

对于上传的文件大小和文件类型比较加以控制,这里在controller中完成即可

@Controller
public class FileUploadController {
    /**
     * 打开页面
     * @return
     */
    @RequestMapping("/register")
    public String testRegister(){
        return "index";
    }

    /**
     * 处理ajax发送的请求。保存文件
     * @param multipartFile
     * @param request
     * @return
     * @throws IOException
     */
    @ResponseBody
    @RequestMapping("/fileUpload.do")
    public Map<String,String> fileUpload(MultipartFile multipartFile, HttpServletRequest request) throws IOException {
        HashMap<String, String> map = new HashMap<String, String>();

        //控制文件大小
        if(multipartFile.getSize()>1024*1024*5){
            map.put("message","文件大小不能超过5M");
            return map;
        }

        //指定文件存储路径为项目部署环境下的upload目录(编译后的)
        String realPath = request.getServletContext().getRealPath("/upload");
        System.out.println(realPath);
        File filePath = new File(realPath);

        //如果不存在则创建目录
        if(!filePath.exists()){
            filePath.mkdirs();
        }

        //获取文件名
        String originalFilename = multipartFile.getOriginalFilename();

        //避免文件名冲突,使用UUID替换文件名
        String uuid = UUID.randomUUID().toString();

        //获取图片后缀名 例如.jpg
        // lastIndexOf(".")截取文件中最后一个点,防止有多个.
        String photoType = originalFilename.substring(originalFilename.lastIndexOf("."));

        //控制文件类型
        if(!photoType.equals(".jpg")){
            map.put("message", "文件类型必须是.jpg");
            return map;
        }

        //组合成新的文件名
        String fileName = uuid.concat(photoType);

        //文件保存位置和需要保存的文件名称
        File file1 = new File(filePath, fileName);

        //文件保存
        multipartFile.transferTo(file1);

        // 上传成功之后,把文件的名字和文件的类型返回给浏览器
        map.put("message", "上传成功");
        map.put("fileName", fileName);
        map.put("fileType", multipartFile.getContentType());

        return map;
    }
}

也可以在springmvc配置文件中控制上传文件的大小,但是不推荐

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置上传文件的默认编码格式-->
        <property name="defaultEncoding" value="UTF-8"/>
        <!--在这里控制上传文件的大小  不推荐
        这时候没办法给前端返回提示信息,推荐在controller中配置-->
        <!--<property name="maxUploadSize" value="10240000000"></property>-->
    </bean>

五、上传图片回显问题

后端已经将图片的文件名响应给浏览器,修改前端代码只要是一下两部分内容:

 代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        /*function ()这是页面加载函数*/
        $(function () {
            /*获取id为uploadFile的元素*/
            $("#uploadFile").click(function () {
                //获取需要上传的文件 根据input标签的id属性获取
                var photoFile = $("#photo")[0].files[0]

                console.log(photoFile)
                /*如果不上传文件,没有选中,photoFile则是一个没有内容的为undefined,所以要进行判断*/
                if(photoFile==undefined){
                    alert("您还未选择图片")
                    return;
                }

                //将文件转换成formData对象
                var formData =new FormData();

                //这里添加后的时候,是一个kv的形式 value是上传的文件,key是后台的一个controller控制器方法的参数名相同multipartFile
                formData.append("multipartFile",photoFile)
                //通过Ajax向后台发送文件
                $.ajax({
                    type:"post",/*类型一定要使用post,因为图片转为二进制传递,get无法传递二进制*/
                    data:formData,/*提交的图片数据*/
                    url:"fileUpload.do", /*提交给后台的controller地址*/
                    /*为false表示为后台提交的是一个对象,而不是字符串*/
                    processData:false,
                    /*设置为false表示请求的数据为二进制类型*/
                    contentType:false,
                    success:function (result) {
                        //接受后台的响应result接受响应
                        alert(result.message)
                        //图片回显 根据id找到图片显示地址,为img,然后设置src属性的值,在取响应回来的数据
                        $("#headImage").attr("src","upload/"+result.fileName)
                    }
                })

                //接受后台的
            })
        })
    </script>
</head>
<body>
<form method="post" action="addPlayer">
    <p>账户:<input type="text" name="name"></p>
    <p>密码:<input type="password" name="password"></p>
    <p>昵称:<input type="text" name="nickname"></p>
    <p>头像:<br>
        <%--这个id是为了后端获取提交数据的--%>
        <input id="photo" type="file"><br>

        <%--图片回显展示--%>
        <img id="headImage" style="width: 200px;height: 200px" alt="你还未上传图片"><br>

        <%--javascript:void(0)取消超链接功能--%>
        <a id="uploadFile" href="javascript:void(0)">立即上传</a>
    </p>
    <p><input type="submit" value="注册"></p>
</form>
</body>
</html>

六.进度条问题

在上传文件的时候一般情况下都会设置进度条,主要是三部分:

 前端代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style>
        .progress {
            width: 200px;
            height: 10px;
            border: 1px solid #ccc;
            border-radius: 10px;
            margin: 10px 0px;
            overflow: hidden;
        }
        /* 初始状态设置进度条宽度为0px */
        .progress > div {
            width: 0px;
            height: 100%;
            background-color: yellowgreen;
            transition: all .3s ease;
        }
    </style>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        /*function ()这是页面加载函数*/
        $(function () {
            /*获取id为uploadFile的元素*/
            $("#uploadFile").click(function () {
                //获取需要上传的文件 根据input标签的id属性获取
                var photoFile = $("#photo")[0].files[0]

                console.log(photoFile)
                /*如果不上传文件,没有选中,photoFile则是一个没有内容的为undefined,所以要进行判断*/
                if(photoFile==undefined){
                    alert("您还未选择图片")
                    return;
                }

                //将文件转换成formData对象
                var formData =new FormData();

                //这里添加后的时候,是一个kv的形式 value是上传的文件,key是后台的一个controller控制器方法的参数名相同multipartFile
                formData.append("multipartFile",photoFile)
                //通过Ajax向后台发送文件
                $.ajax({
                    type:"post",/*类型一定要使用post,因为图片转为二进制传递,get无法传递二进制*/
                    data:formData,/*提交的图片数据*/
                    url:"fileUpload.do", /*提交给后台的controller地址*/
                    /*为false表示为后台提交的是一个对象,而不是字符串*/
                    processData:false,
                    /*设置为false表示请求的数据为二进制类型*/
                    contentType:false,
                    success:function (result) {
                        //接受后台的响应result接受响应
                        alert(result.message)
                        //图片回显 根据id找到图片显示地址,为img,然后设置src属性的值,在取响应回来的数据
                        $("#headImage").attr("src","upload/"+result.fileName)
                    },xhr:function () {
                        var xhr = new XMLHttpRequest();
                        //使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
                        xhr.upload.addEventListener("progress",function (e) {
                            //loaded代表上传了多少
                            //total代表总数为多少
                            var progressRate = (e.loaded/e.total)*100+"%";

                            //通过设置进度条的宽度达到效果
                            $(".progress>div").css("width",progressRate);
                        })
                        return xhr;
                    }
                })
                //接受后台的
            })
        })
    </script>
</head>
<body>
<form method="post" action="addPlayer">
    <p>账户:<input type="text" name="name"></p>
    <p>密码:<input type="password" name="password"></p>
    <p>昵称:<input type="text" name="nickname"></p>
    <p>头像:<br>
        <%--这个id是为了后端获取提交数据的--%>
        <input id="photo" type="file"><br>

        <%--图片回显展示--%>
        <img id="headImage" style="width: 200px;height: 200px" alt="你还未上传图片"><br>

        <%--进度条--%>
        <div class="progress">
            <div></div>
        </div>

        <%--javascript:void(0)取消超链接功能--%>
        <a id="uploadFile" href="javascript:void(0)">立即上传</a>
    </p>
    <p><input type="submit" value="注册"></p>
</form>
</body>
</html>
posted @ 2019-10-28 09:50  酒剑仙*  阅读(300)  评论(0编辑  收藏  举报