当前位置: 首页 > news >正文

File对象转MultipartFile 如何new出高仿MultipartFile对象

文章目录

    • 1.问题
    • 2.解决过程
    • 3.解决问题
    • 4. 总结问题 就是 RequestPart的坑
  • @RequestParam和@RequestPart的区别
    • @RequestPart
    • @RequestParam
    • 区别
    • 总结

1.问题

最近遇到个问题:
服务端定义了个上传文件的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

img
百思不得其解.

2.解决过程

后来试了下自己在客户端中照着服务端写个一模一样的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中还有很多内部结构,如下

img

而我自己new出来的却是这个样子:

img

3.解决问题

到此问题答案出来了:应该是自己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包)

参考出处: https://www.cnblogs.com/Big-Boss/p/10729618.html

用这种方式new出来的MultipartFile 就有了FileItem, 完美解决问题.

4. 总结问题 就是 RequestPart的坑

@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)



@RequestParam和@RequestPart的区别

@RequestPart

  • @RequestPart这个注解用在multipart/form-data表单提交请求的方法上。
  • 支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的

@RequestParam

  • @RequestParam支持’application/json’,也同样支持multipart/form-data请求

区别

  • 当请求方法的请求参数类型不是String 或 MultipartFile / Part时,而是复杂的请求域时,@RequestParam 依赖Converter or PropertyEditor进行数据解析, RequestPart参考 ‘Content-Type’ header,依赖HttpMessageConverters 进行数据解析
  • 当请求为multipart/form-data时,@RequestParam只能接收String类型name-value值,@RequestPart可以接收复杂的请求域(像json、xml);@RequestParam 依赖Converter or PropertyEditor进行数据解析, @RequestPart参考'Content-Type' header,依赖HttpMessageConverters进行数据解析

前台请求:
jsonDataPerson对象的json字符串
uploadFile为上传的图片
在这里插入图片描述

后台接收:

  1. @RequestPart可以将jsonDatajson数据转换为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();
}
  1. @RequestParam对于jsonDatajson数据只能用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只能用字符串接收

相关文章:

  • VScode配置运行C/C++、python,及快捷键配置
  • 【threejs】可视化大屏酷炫3D地图附源码
  • 安路FPGA学习备忘录
  • 目标检测算法——YOLOv5改进之结合MobileOne结构
  • Python之“诗词大会”游戏
  • MySQL:索引知识点盘点
  • 大神之路-起始篇 | 第9章.计算机科学导论之【程序设计语言】学习笔记
  • Python 的Tkinter包系列之四:对话框
  • 大神之路-起始篇 | 第8章.计算机科学导论之【数据算法】学习笔记
  • IDET变化检测模型
  • javascript基本语法(持续补充)
  • Spring Boot开发之Mybatis
  • 卷王杯 easy unserialize
  • 常见Web安全漏洞深入解析
  • 如何从一款单片机移植到另一款单片机
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • Create React App 使用
  • docker容器内的网络抓包
  • exports和module.exports
  • Javascript编码规范
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • opencv python Meanshift 和 Camshift
  • PAT A1120
  • React16时代,该用什么姿势写 React ?
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 每天10道Java面试题,跟我走,offer有!
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 什么是Javascript函数节流?
  • 跳前端坑前,先看看这个!!
  • 一个SAP顾问在美国的这些年
  • ​补​充​经​纬​恒​润​一​面​
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • #include到底该写在哪
  • #java学习笔记(面向对象)----(未完结)
  • $.each()与$(selector).each()
  • (2)空速传感器
  • (5)STL算法之复制
  • (55)MOS管专题--->(10)MOS管的封装
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (PySpark)RDD实验实战——取一个数组的中间值
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (三十五)大数据实战——Superset可视化平台搭建
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转) 深度模型优化性能 调参
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (自用)仿写程序
  • .gitattributes 文件
  • .NET MVC 验证码
  • .net的socket示例
  • .net下简单快捷的数值高低位切换