最近遇到个问题:
服务端定义了个上传文件的restful api接口如下.
@PostMapping
public void updateAvatar(@PathVariable("userName") String userName,
@RequestPart("avatarMpFile") MultipartFile avatarMpFile) throws IOException {
//....
}
在swagger接口中测试接口 上传文件很正常,然而自己写个feign 客户端:
/**
* 修改头像接口
*/
@PostMapping(value = "/api/data/employee/{userName}/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result uploadAvatar(
@PathVariable("userName") String userName,
@RequestPart("avatarMpFile") MultipartFile avatarMpFile);
上传始是服务端终报错:
org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'avatarMpFile' is not present
百思不得其解.
后来试了下自己在客户端中照着服务端写个一模一样的controller ,
@PostMapping(value = "/photoUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation("头像上传 测试用记得删去")
public Result upload(@RequestParam("avatarMpFile")
MultipartFile multipartFile) throws Exception {
return Result.ok(photoUpdateService.modify(multipartFile, UserUtil.getUserName()));
}
自己在swagger上调用客户端,又可以在客户端通过feign 传到服务端了
分析可能是自己新建的 MultipartFile文件出了问题.
之前有问题的multipartFile创建方式为:
通过自定义的dto 实现MultipartFile接口实现的.
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.file.Files;
import javax.activation.MimetypesFileTypeMap;
import org.apache.commons.io.FileUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.annotations.ApiModel;
/**
* 文件dto
* Created on 2019-12-23
*/
@ApiModel(value = "FileDTO")
public class FileDTO implements MultipartFile, Serializable {
private String fileName;
/**
* 文件服务器只接受file参数
*/
private String attachmentName = "file";
private
byte[] fileData;
public FileDTO(String fileName,
byte[] fileData) {
this.fileName = fileName;
this.fileData = fileData;
this.attachmentName = attachmentName;
}
public FileDTO(String attachmentName, String fileName,
byte[] fileData) {
this.fileName = fileName;
this.fileData = fileData;
this.attachmentName = attachmentName;
}
public String getName() {
return attachmentName;
}
@Override
public String getOriginalFilename() {
return fileName;
}
@Override
public String getContentType() {
return new MimetypesFileTypeMap().getContentType(fileName);
}
@Override
public boolean isEmpty() {
return fileData == null || fileData.length == 0;
}
@Override
public long getSize() {
return fileData.length;
}
@Override
public byte[] getBytes() {
return fileData;
}
@Override
public InputStream getInputStream() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fileData);
return byteArrayInputStream;
}
@Override
public void transferTo(File dest) throws IOException {
FileUtils.writeByteArrayToFile(dest, fileData);
if (dest.isAbsolute() && !dest.exists()) {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest.toPath()));
}
}
}
然后传入文件名和文件二进制字节数组即可
// 获取文件二进制byte数组
ByteArrayResource file = simpleFileClient.getFileResource(fileId, "media", fileAppId);
// 新建MultipartFile对象
MultipartFile multipartFile = new FileDTO("filename", file.getByteArray());
这种写法大多数情况都没问题,然而当服务端使用 @RequestPart 接收时就有问题
因为 spring中 使用MultipartFile 接收文件是,MultipartFile中还有很多内部结构,如下
而我自己new出来的却是这个样子:
到此问题答案出来了:应该是自己new出来的 MultipartFile不够完美
于是网上搜索了下" multipartFile FieldName"关键字 , 找到一个更好的方案
MultipartFile mfile = new CommonsMultipartFile(createFileItem(avatarMpFile,"avatarMpFile"));
/**
* 创建FileItem
*/
private FileItem createFileItem(MultipartFile file, String fieldName) {
FileItemFactory factory = new DiskFileItemFactory(16, null);
FileItem item = factory.createItem(fieldName, "text/plain", true, file.getName());
int bytesRead = 0;
byte[] buffer = new byte[8192];
try {
InputStream fis = file.getInputStream();
OutputStream os = item.getOutputStream();
while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
return item;
}
(CommonsMultipartFile,FileItemFactory 需要依赖commons-fileupload-1.3.2.jar包)
用这种方式new出来的MultipartFile 就有了FileItem, 完美解决问题.
@RequestBody 、@requestParam,@RequestPart区别
@RequestBody
1、@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application.xml等。一般情况下来说常用其来处理application/json类型。
通过@requestBody可以将请求体中的JSON字符串绑定到相应的bean上,当然,也可以将其分别绑定到对应的字符串上。
例如说以下情况:
$.ajax({
url:"/login",
type:"POST",
data:'{"userName":"admin","pwd","admin123"}',
content-type:"application/json charset=utf-8",
success:function(data){
alert("request success ! ");
}
});
@requestMapping("/login")
public void login(@requestBody String userName,@requestBody String pwd){
System.out.println(userName+" :"+pwd);
}
这种情况是将JSON字符串中的两个变量的值分别赋予了两个字符串,但是呢假如我有一个User类,拥有如下字段:
String userName;
String pwd;
那么上述参数可以改为以下形式:@requestBody User user 这种形式会将JSON字符串中的值赋予user中对应的属性上
需要注意的是,JSON字符串中的key必须对应user中的属性名,否则是请求不过去的。
@RequestPart
/**
* 单文件上传
* @param file
*/
@RequestMapping("uploadFile")
public JsonResult uploadFile(@RequestPart("file") MultipartFile file, @RequestParam String bucket){
String fileUrl = aliossService.uploadFile(file, bucket);
Map<String,String> result = new HashMap<>();
result.put("fileUrl",fileUrl);
return success(result);
}
@RequestParam
/**
* 上传字符串
* @param stringFile
* @param bucket
* @return
*/
@RequestMapping("uploadStringFile")
public JsonResult uploadStringFile(@RequestParam("stringFile") String stringFile, @RequestParam("bucket") String bucket){
String fileUrl = aliossService.uploadStringFile(stringFile, bucket);
Map<String,String> result = new HashMap<>();
result.put("fileUrl",fileUrl);
return success(result);
}
1.@RequestPart这个注解用在multipart/form-data表单提交请求的方法上。
2.支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的。
3.@RequestParam也同样支持multipart/form-data请求。
4.他们最大的不同是,当请求方法的请求参数类型不再是String类型的时候。
5.@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)
@RequestPart
这个注解用在multipart/form-data
表单提交请求的方法上。MultipartFile
,属于Spring的MultipartResolver
类。这个请求是通过http协议
传输的@RequestParam
支持’application/json’,也同样支持multipart/form-data
请求multipart/form-data
时,@RequestParam
只能接收String类型
的name-value
值,@RequestPart
可以接收复杂的请求域(像json、xml
);@RequestParam
依赖Converter or PropertyEditor
进行数据解析, @RequestPart
参考'Content-Type' header
,依赖HttpMessageConverters
进行数据解析前台请求:
jsonData
为Person
对象的json
字符串
uploadFile
为上传的图片
后台接收:
@RequestPart
可以将jsonData
的json数据
转换为Person对象
@RequestMapping("jsonDataAndUploadFile")
@ResponseBody
public String jsonDataAndUploadFile(@RequestPart("uploadFile") MultiPartFile uploadFile,
@RequestPart("jsonData") Person person) {
StringBuilder sb = new StringBuilder();
sb.append(uploadFile.getOriginalFilename()).append(";;;"));
return person.toString() + ":::" + sb.toString();
}
@RequestParam
对于jsonData
的json数据
只能用String字符串
来接收@RequestMapping("jsonDataAndUploadFile")
@ResponseBody
public String jsonDataAndUploadFile(@RequestPart("uploadFile") MultiPartFile uploadFile,
@RequestParam("josnData") String jsonData) {
StringBuilder sb = new StringBuilder();
sb.append(uploadFile.getOriginalFilename()).append(";;;"));
return person.toString() + ":::" + sb.toString();
}
当请求头中指定Content-Type:multipart/form-data时,传递的json参数,@RequestPart注解可以用对象来接收,@RequestParam只能用字符串接收