起因

后端

功能需求,需要提交文件加业务属性数据,所以后端就写了实体类用作接口参数,实体类中包含了多个MultipartFile类型及其他数据字段,接口为请求类型为post,未加其他注解参数。

爬坑过程

第一次

前端写了个表单提交,在表单中所有文件选择框都选择文件时,可以正常提交,如果表单中有文件框没有选择,则后端就会报错。此时我给出了第一个建议,建议改为ajax选择性提交,因为默认的表单提交,在后端用实体类接收时,会有一个参数赋值过程,MultipartFile如果为空值,赋值就会报错。改成ajax之后,文件选择框都不选择文件的时候,可以调用成功,但是选择文件后,前端会报错‘Failed to execute ‘arrayBuffer’ on ‘Blob’: Illegal invocation’。

第二次

翻阅关于"Failed to execute ‘arrayBuffer’ on ‘Blob’: Illegal invocation"错误的解决,有把文件类型从实体类拿出来放在请求参数中,加上@RequestPart注解来取值的,也有在*Mapping的注解中加headers="content-type=multipart/form-data"参数的,都试过,依然不行。

第三次

把后端接口恢复到最初,大致如下

@PutMapping(value = "/demo")
public xxx demo(XXDTO xxDTO){
     return xxx;
}

前端ajax增加如下属性

contentType: false,
processData: false,
cache: false,

ajax全代码示例如下

var id = $("#id").val();
var name= $("#name").val();
var file1 = $("#file1").prop('files');
var file2 = $("#file2").prop('files');

var formData = new FormData();
formData.append("id", id);
formData.append("name", name);
if(file1!=null && file1!='' && file1!=undefined && file1.length != 0){
	formData.append("file1", file1[0]);
}
if(file2!=null && file2!='' && file2!=undefined && file2.length != 0){
	formData.append("file2", file2[0]);
}
		
$.ajax({
	type: "put",
	url: "/demo",
	data: formData,
	dataType: "json",
	contentType: false,
	processData: false,
	cache: false,
	success: function(data){
		// 成功
	},	
	error:function(data){
		// 失败
	}
});

如上修改之后,接口调用全部正常,至此问题解决。

总结

1、后端将MultipartFile包在实体类中,框架接收到参数,在有值的情况下,是可以成功找到参数解析器进行赋值的。如果前端表单中的文件选择框不选择资源进行提交,则是空串或"null",后端框架无法正常赋值。
在GenericConversionService这个类中有这么一段代码

@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
	Assert.notNull(targetType, "Target type to convert to cannot be null");
	if (sourceType == null) {
		return true;
	}
	GenericConverter converter = getConverter(sourceType, targetType);
	return (converter != null);
}

在前端请求数据中file为空值或"null"值时,后端在参数解析的时候类型无法匹配,则会导致报错。我在调试过程中,跟踪到这里时,
targetType值为@io.swagger.annotations.ApiModelProperty org.springframework.web.multipart.MultipartFile
sourceType值为java.lang.String
明显类型不匹配,在对象参数赋值的时候,肯定是会报错的。如果传值时是空值则不提交这个参数,后端在解析时默认是不会解析非必传参数的。
其中更多细节代码感兴趣的同学可以自行学习。

2、前端ajax请求属性设置
官方解释

contentType	发送数据到服务器时所使用的内容类型。默认是:"application/x-www-form-urlencoded"。
processData	布尔值,规定通过请求发送的数据是否转换为查询字符串。默认是 true。
cache	布尔值,表示浏览器是否缓存被请求页面。默认是 true

主要是contentType 和 processData
processData 默认为true,也就是会将请求参数转换为contentType匹配的格式进行请求,而contentType默认为"application/x-www-form-urlencoded",这个时候加上文件上传,格式化后请求参数中的文件数据则会错乱,导致无效。但是将contentType设置为"multipart/form-data"的话,常规数据又会无效。通过这个意思去理解,那么想要得效果就是希望ajax请求的时候不要去转换请求数据,请求的时候是什么就传什么,交给后端去解析请求参数就可以了。因此将processData设置为false,不让它去转换发送数据,将contentType设置为false,不指定内容类型。

如有错误和不足之处,希望各位同行可以纠正和补充,避免误导。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐