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


    1.问题

    最近遇到个问题:
    服务端定义了个上传文件的restful api接口如下.

    @PostMapping
    public void updateAvatar(@PathVariable("userName") String userName,
                @RequestPart("avatarMpFile") MultipartFile avatarMpFile) throws IOException {
    //....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上传始是服务端终报错:

    org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'avatarMpFile'  is not present
    
    • 1

    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()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    自己在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()));
            }
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    然后传入文件名和文件二进制字节数组即可

    // 获取文件二进制byte数组
    ByteArrayResource file = simpleFileClient.getFileResource(fileId, "media", fileAppId);
    // 新建MultipartFile对象
    MultipartFile multipartFile = new FileDTO("filename", file.getByteArray());
    
    • 1
    • 2
    • 3
    • 4

    这种写法大多数情况都没问题,然而当服务端使用 @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    (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 ! ");
            }
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
        @requestMapping("/login")
        public void login(@requestBody String userName,@requestBody String pwd){
          System.out.println(userName+" :"+pwd);
        }
    
    • 1
    • 2
    • 3
    • 4

    这种情况是将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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    总结

    当请求头中指定Content-Type:multipart/form-data时,传递的json参数,@RequestPart注解可以用对象来接收,@RequestParam只能用字符串接收

  • 相关阅读:
    Django ORM 多表操作
    Tomcat启动闪退问题解决方法
    基于Springboot实现商品进销存管理系统
    SpringBoot的搭建(两种方式)
    mysql for update 死锁问题排查
    Spring Boot配置Tomcat容器、Jetty容器、Undertow容器
    叶子识别 颜色的特征提取 缺陷检测等
    冒泡排序(C语言详解)
    20.0、C语言——字符串函数的使用和剖析
    用c语言实现矩阵转置
  • 原文地址:https://blog.csdn.net/qq_43842093/article/details/127399338