长大后想做什么?做回小孩!

0%

SpringBoot文件上传出现的问题

表单的文件信息使用serialize()序列化之后传参引发问题,需要使用FormData对象封装form表单。

jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form id="addUserForm" enctype="multipart/form-data">
<div class="form-group">
<label for="add_username" class="col-sm-2 control-label">username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="add_username" placeholder="username" name="username" />
</div>
</div>
。。。
<div class="form-group">
其他的各种属性的input。。。
</div>
。。。
<div class="form-group">
<label for="add_pic" class="col-sm-2 control-label">pic</label>
<div class="col-sm-10">
<input type="file" class="form-control" id="add_pic" placeholder="pic" name="fileUpload" />
</div>
</div>
</form>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" onclick="user_insert()">添加</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
function user_insert() {
$.post("addUser",$("#addUserForm").serialize(),function (data) {
if (data=="OK"){
alert("添加成功");
window.location.reload();
}else {
alert("添加失败");
window.location.reload();
}
});
}

需要添加form属性enctype=”multipart/form-data”。

需要几点:
  1. method=”post” 是必须的
  2. enctype=”multipart/form-data” 是必须的,表示提交二进制文件
  3. accept=”image/*” 表示只选择图片

注意:这里的form表单没有直接submit提交,而是js使用$.post()提交的,表单的信息使用serialize()序列化之后传参的。这个操作会引发一个问题。。。后面再说。。。

在webapp/WEB-INF下新建一个pic文件夹用来存放上传的图片:

ncO5lD.png

controller:

这里选择用添加新用户时上传头像图片例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RequestMapping(value = "addUser",method = RequestMethod.POST)
public @ResponseBody String addUser(User user, MultipartFile fileUpload, HttpServletRequest request) throws IOException {
// 判断是否上传了图片
if (!fileUpload.isEmpty()){
// 使用UUID生成32位随机字符串作为文件名
// 如果依然怕重复就加上当前时间System.currentTimeMillis()
String rname = UUID.randomUUID().toString();
// 使用getExtension(commons.io包)获取文件名的后缀名(不含.)
String ext = FilenameUtils.getExtension(fileUpload.getOriginalFilename());
// 使用getRealPath获取/pic的真是路径
String path = request.getServletContext().getRealPath("/pic");
// 新建一个file对象,判断/pic文件夹是否存在,不存在就创建一个
File file = new File("path");
if (!file.exists()){
file.mkdirs();
}
// 将文件写入磁盘
fileUpload.transferTo(new File(path,rname+"."+ext));
// 准备将文件名和其他属性一起写入数据库
user.setPic(rname+"."+ext);
}
Integer result = userService.addUser(user);
if (result==1)
return "OK";
else return "FALSE";
}

MultipartFile这个类一般用来接受前台传过来的文件,提供了一些比较便捷的方法。

接下来完善service和dao,直接insert没什么特色就不细说了。

可以在application.properties中加入上传大小限制的配置:

1
2
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

出现问题:

完善上面的编码之后,运行发现添加请求之后处理器方法有一个空指针异常。。fileUpload是null。。。经过排查觉得应该是serialize(),因为上传文件的文件流不能被序列化并传递导致后台接收不到MultipartFile对象。

经过思考和查询,总结了解决这个问题大概有如下几种方法:

  1. 逃避这个问题:serialize()使用的情况比较局限,只能正确序列化check、text、password等等简单的常用类型。不用异步请求。。。就不用serialize()去序列化,直接submit提交form表单。(废话。。。)

  2. 使用异步请求:需要使用FormData对象封装form表单。

    FormData 对象可以把form中所有表单元素的name与value组成一个queryString,提交到后台,在使用Ajax提交时,使用FormData对象可以减少拼接queryString的工作量。

    1
    2
    3
    var formdata=new FromData($("#form")[0]);
    formser.append("test",test);//可增加请求的参数
    FormData还有setget、has等等很多方法

    formData.append(‘a’, 1)意为添加一个键值对,重复添加的键不会被覆盖。

    formData.set(‘a’, 1)意为修改某个键的值,如果不存在则作用等同于append,但是重复添加的值会相互覆盖。

    注意:这里的[0]去掉的话会报错!Failed to construct ‘FormData’: parameter 1 is not of type ‘HTMLFormElement’.

    这里出现了一个很鬼畜的现象:id选择器,为什么可以取出一个数组???

    为什么要加[0]?因为new FormData需要的是一个HtmlElement类型的数据,但是JQ得到的是一个HtmlElement的集合,及时这个集合只有一个元素,也要用[ ]取一个元素。

    JQ是一个伪数组对象,虽然其本身是一个对象,但也能表现出数组的特点:有length,能用下标取值。为什么这么设计?

    为什么这么设计?因为JS有getElementByXX和getElementsByXX,但是JQ却没有这么的直白,举个例子:

    $(“.someClass”) 这个时候将所有匹配到DOM元素对象放在jQuery维护的数组中;

    $(“#id”)这个时候将所有匹配到DOM元素对象放在jQuery维护的数组中;
    在数组的特征外,jQuery还可以调用next(), last()等方法(返回结果也还是jQuery对象,jQuery链式功能),总而言之jQuery得到的是个HTMLElement的集合基础上的封装后的对象。

    将添加用户的js改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function user_insert() {
    var formdata = new FormData($("#addUserForm")[0]);
    $.ajax({
    type:"POST",
    url:"addUser",
    data:formdata,
    processData:false,
    contentType:false,
    //async:false,
    success:function(data){
    if (data=="OK"){
    alert("添加成功");
    window.location.reload();
    }else {
    alert("添加失败");
    window.location.reload();
    }
    }
    });
    }

    需要注意的几点:

    1. processData必须设置为false。因为data值是FormData对象,不需要对数据做处理。
    2. contentType必须设置为false,不设置contentType的值,因为使用form表单构造的FormData对象,且form表单已经声明了属性enctype=”multipart/form-data”,所以这里设置为false。
      1. async:要求为Boolean类型的参数,默认设置为true,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为false。注意,同步请求将锁住浏览器,用户其他操作必须等待请求完成才可以执行。

虽然不是什么很高大上的问题,但是想我一样初学者遇到时还是很棘手的。暂时使用这样的方式完成了上传并解决了小麻烦。上面的controller代码是添加用户的,如果是修改用户还需要在jsp页面传一个修改前pic的值,方便对修改前的图片进行删除。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RequestMapping("updateUser")
public @ResponseBody String updateUser(User user,MultipartFile fileUpload,HttpServletRequest request) throws IOException {
// 判断是否上传了图片
if (!fileUpload.isEmpty()){
// 使用UUID生成32位随机字符串作为文件名
// 如果依然怕重复就加上当前时间System.currentTimeMillis()
String rname = UUID.randomUUID().toString();
// 使用getExtension(commons.io包)获取文件名的后缀名(不含.)
String ext=FilenameUtils.getExtension(fileUpload.getOriginalFilename());
// 使用getRealPath获取/pic的真是路径
String path = request.getServletContext().getRealPath("/pic");
// 新建一个file对象,判断/pic文件夹是否存在,不存在就创建一个
File file = new File("path");
if (!file.exists()){
file.mkdirs();
}
// 将文件写入磁盘
fileUpload.transferTo(new File(path,rname+"."+ext));
// 如果用户修改前不是使用默认图片,就把之前的图片删掉
if (!user.getPic().equals("default.png")){
File originFile = new File(path,user.getPic());
if (originFile.exists()){
originFile.delete();
}
}
// 准备将文件名和其他属性一起写入数据库
user.setPic(rname+"."+ext);
}
Integer result = userService.updateUser(user);
if (result==1)
return "OK";
else return "FALSE";
}