【问题标题】:Spring RestTemplate: post both an image and an object at the same timeSpring RestTemplate:同时发布图像和对象
【发布时间】:2016-03-10 15:11:14
【问题描述】:

我的用户可以在我的服务器上发布食物的照片和食物的相关信息。

例如,假设有人看到好吃的东西,拍了一张照片,然后写下“好吃!”图片下方。照片被发送到服务器,消息“好吃!”包括用户名、日期、位置等在内的一个名为“Post”的对象使用一个 api 调用发送到我的服务器。

我在我的android端写了以下代码:

    final String url = Constants.POST_PICS;
    RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
    //adding StringHttpMessageConverter, formHttpMessageConverter and MappingJackson2HttpMessageConverter to restTemplate
    restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
    FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
    restTemplate.getMessageConverters().add(formHttpMessageConverter);
    restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    //putting both objects into a map
    MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
    map.add("image", new FileSystemResource(file));
    map.add("post", post);
    HttpHeaders imageHeaders = new HttpHeaders();
    //setting content type to multipart as the image is a multipart file
    imageHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
    HttpEntity<MultiValueMap<String, Object>> imageEntity = new HttpEntity<MultiValueMap<String, Object>>(map, imageHeaders);
    ResponseEntity<Post> response = restTemplate.exchange(url, HttpMethod.POST, imageEntity, Post.class);
    return response.getBody();

这是Spring端的代码:

        @RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
public Post uploadPostWithPic(@RequestParam("image") MultipartFile srcFile,
                                  @RequestParam("post") Post post) {
    return serviceGateway.uploadPostWithPic(srcFile, post);
}

我收到一个错误:

请求网络执行期间发生异常:无法 写请求:没有找到适合请求类型的 HttpMessageConverter [模型.帖子]

org.springframework.http.converter.HttpMessageNotWritableException: 无法写入请求:找不到合适的 HttpMessageConverter 请求类型 [Model.Post]

我怀疑这与设置为 MULTIPART_FORM_DATA 的内容类型有关,但我需要将其设置为此,因为我需要将图片传输到服务器。

是否可以同时使用 restTemplate 向上游传输多部分文件和另一个对象?

编辑:

我看过这些帖子:

Resttemplate form/multipart: image + JSON in POST

Sending Multipart File as POST parameters with RestTemplate requests

并根据他们的指导尝试了这段代码:

    final String url = Constants.POST_PIC;
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
    restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
    restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    restTemplate.getMessageConverters().add(new ResourceHttpMessageConverter());

    FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
    formHttpMessageConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
    formHttpMessageConverter.addPartConverter(new ResourceHttpMessageConverter()); // This is hope driven programming
    formHttpMessageConverter.addPartConverter(new ByteArrayHttpMessageConverter());

    restTemplate.getMessageConverters().add(formHttpMessageConverter);

    MultiValueMap<String, Object> multipartRequest = new LinkedMultiValueMap<>();

    byte[] bFile = new byte[(int) imageFile.length()];
    FileInputStream fileInputStream;

    //convert file into array of bytes
    fileInputStream = new FileInputStream(imageFile);
    fileInputStream.read(bFile);
    fileInputStream.close();

    ByteArrayResource bytes = new ByteArrayResource(bFile) {
        @Override
        public String getFilename() {
            return "file.jpg";
        }
    };

    //post portion of the multipartRequest
    HttpHeaders xHeader = new HttpHeaders();
    xHeader.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<Post> xPart = new HttpEntity<>(post, xHeader);
    multipartRequest.add("post", xPart);

    //picture portion of the multipartRequest
    HttpHeaders pictureHeader = new HttpHeaders();
    pictureHeader.setContentType(MediaType.IMAGE_JPEG);
    HttpEntity<ByteArrayResource> picturePart = new HttpEntity<>(bytes, pictureHeader);
    multipartRequest.add("srcFile", picturePart);

    //adding both the post and picture portion to one httpentity for transmitting to server
    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.MULTIPART_FORM_DATA); 
    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity(multipartRequest, header);
    return restTemplate.postForObject(url, requestEntity, Post.class);

另一方面,post = null,我不确定为什么它是 null。

这就是我想要在服务器端做的所有事情:

public Post uploadPostPic(MultipartFile srcFile, Post post) {
    Post savedPost = repo.save(post);
 }

我将它保存到我的存储库中,错误是:

java.lang.IllegalArgumentException: Entity must not be null!

【问题讨论】:

    标签: android spring spring-boot resttemplate


    【解决方案1】:

    试试这样的: 在此处发送 jsonString,稍后使用 objectwriter 将其转换为对象。如果您需要更多解释,请告诉我。

    @RequestMapping(value = "/uploadMultipleFile", method = RequestMethod.POST)
        public @ResponseBody
        String uploadMultipleFileHandler(@RequestParam("name") String[] names,
                @RequestParam("file") MultipartFile[] files) {
    
            if (files.length != names.length)
                return "Mandatory information missing";
    
            String message = "";
            for (int i = 0; i < files.length; i++) {
                MultipartFile file = files[i];
                String name = names[i];
                try {
                    byte[] bytes = file.getBytes();
    
                    // Creating the directory to store file
                    String rootPath = System.getProperty("catalina.home");
                    File dir = new File(rootPath + File.separator + "tmpFiles");
                    if (!dir.exists())
                        dir.mkdirs();
    
                    // Create the file on server
                    File serverFile = new File(dir.getAbsolutePath()
                            + File.separator + name);
                    BufferedOutputStream stream = new BufferedOutputStream(
                            new FileOutputStream(serverFile));
                    stream.write(bytes);
                    stream.close();
    
                    logger.info("Server File Location="
                            + serverFile.getAbsolutePath());
    
                    message = message + "You successfully uploaded file=" + name
                            + "<br />";
                } catch (Exception e) {
                    return "You failed to upload " + name + " => " + e.getMessage();
                }
            }
            return message;
        }
    }
    

    已编辑:

    最终,我不得不使用 jsonString 来解决我的问题。这并不理想,因为网址最终会变得很长,但这是解决我的问题的最快方法:

    请查看 mykong 关于如何将对象转换为 jsonString 并将它们重新转换回对象的建议:

    ObjectMapper mapper = new ObjectMapper();
    Staff obj = new Staff();
    
    //Object to JSON in String
    String jsonInString = mapper.writeValueAsString(obj);
    
    //JSON from String to Object
    Staff obj = mapper.readValue(jsonInString, Staff.class);
    

    http://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/

    【讨论】:

    • 如果 Spring 自动内置了这个功能,我不喜欢这样做/
    【解决方案2】:

    我用这个做了类似的东西:

      HttpHeaders headers = new HttpHeaders();
      headers.add("Accept","application/json");     
      headers.setContentType(MediaType.MULTIPART_FORM_DATA);
      MultiValueMap<String, Object> map =  new LinkedMultiValueMap<String, Object>();
      map.add("image", new FileSystemResource(file));
      map.add("post", post);
      HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(map, headers);
      RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
      restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
      ResponseEntity<Post> response = restTemplate.postForObject(url, requestEntity, Post.class);
    

    【讨论】:

    • 我仍然得到实体不能为空!我的服务器端错误。你能告诉我你的服务器端代码吗?
    • 你有什么样的服务器? php、groovy 等?
    【解决方案3】:

    您需要告诉 Spring 如何将请求参数映射到您的对象。您可以通过像 Alexander 建议的那样实现自定义 HttpMessageConterter 来做到这一点,但我有一个更简单的方法:使用命令对象(有时称为表单支持对象):

    public class PostWithPicCommand() {
       public PostWithPic() {}; //Default constructor is required
    
       //name the variables like the request parameters!
       private Post post;
       private MultipartFile image;
    
       Getter and Setter!
    }
    
    @RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
    public Post uploadPostWithPic(PostWithPicCommand postWithPicCommand
          /*no @Param attribte for postWithPicCommand*/) {    
        ....
    }
    

    并且你需要配置/注册springs Multipart Resolver,并且需要将请求作为多部分请求发送。

    【讨论】:

      【解决方案4】:

      好吧,几周前我遇到了同样的问题。首先要明确multipart/form-data content-type 是什么意思:

      “multipart/form-data”消息包含一系列部分,每个部分 代表一个成功的控制。

      成功的控件对于提交是“有效的”。每一个成功的 控件的控件名称与其当前值配对,作为 提交的表单数据集

      简单来说,使用多部分表单数据,您可以将不同内容类型的数据发送到服务器。这是一个示例:

      POST / HTTP/1.1
      Host: localhost:8000
      User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
      Accept-Language: en-US,en;q=0.5
      Accept-Encoding: gzip, deflate
      Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
      Connection: keep-alive
      Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
      Content-Length: 554
      
      -----------------------------9051914041544843365972754266
      Content-Disposition: form-data; name="text"
      
      text default
      -----------------------------9051914041544843365972754266
      Content-Disposition: form-data; name="file1"; filename="a.txt"
      Content-Type: text/plain
      
      Content of a.txt.
      
      -----------------------------9051914041544843365972754266
      Content-Disposition: form-data; name="file2"; filename="a.html"
      Content-Type: text/html
      
      <!DOCTYPE html><title>Content of a.html.</title>
      
      -----------------------------9051914041544843365972754266--
      

      这里是发送三个不同集合的示例 - 第一个是binary data,第二个是plain text,第三个是html,它们由边界分隔。

      现在Spring's RestTemplate 工作得怎么样了?

      当您将请求标头设置为 multipart/form-data 时,resttemplate 将从注册的消息转换器中获取适当的 HttpMessageConverter,对于 multipart/form-data 将是 FormHttpMessageConverter,请参阅文档 here。

      但是FormHttpMessageConverter 具有partConverters 的属性,它们是为FormHttpMessageConverter 注册的转换器,默认情况下它们是(字符串、字节数组和资源)。这是构造函数的源代码;)

      public FormHttpMessageConverter() {
          this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
          this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
      
          this.partConverters.add(new ByteArrayHttpMessageConverter());
          StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
          stringHttpMessageConverter.setWriteAcceptCharset(false);
          this.partConverters.add(stringHttpMessageConverter);
          this.partConverters.add(new ResourceHttpMessageConverter());
      } 
      

      简单地说,FormHttpMessageConverter 找不到正确的消息转换器来写入对象Post。如果您希望将Post 写为 JSON,您应该将MappingJackson2HttpMessageConverter 添加到partConverters。

      @Produces
          public RestTemplate getRestTemplate() {
              RestTemplate template = new RestTemplate();
              template.getMessageConverters().add(0,createFormHttpConverter());
              return template;
          }
       private static HttpMessageConverter<?> createFormHttpConverter(){
      
              FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
              formHttpMessageConverter.setPartConverters(getPartConverters());
              return formHttpMessageConverter;
          }
      
      private static List<HttpMessageConverter<?>> getPartConverters(){
          RestTemplate template = new RestTemplate();
          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
          List<HttpMessageConverter<?>> messageConverters = template.getMessageConverters();
          messageConverters.add(0,converter);
          return messageConverters;
      }
      

      【讨论】:

      • 感谢您的回答 - 我刚刚尝试过,resttemplate 能够将其发送到服务器,但是当它到达服务器时,图像对象已成功发送,但 post 对象返回 null服务器端。你能告诉我你的代码在服务器端是什么样子的,这样我就可以看到你在 RequestMappings、RequestParams 等方面做了什么。
      【解决方案5】:

      我没有为您的 Post 类看到已注册的 HttpMessageConverter。您可能需要为 MultiValueMap 注册一个 HttpMessageConverter。

      【讨论】:

      • MappingJackson2HttpMessageConverter() 不能正确映射到 json 对象吗?
      • 坦率地说,我希望如此,如果您的帖子有杰克逊注释。我假设您已经在 Spring ConversionService 中为您的“@RequestParam("post") Post post”注册了一个转换器。我会尝试用 String 替换你的 Post 类,看看它是否有效。
      猜你喜欢
      • 1970-01-01
      • 2014-09-22
      • 2023-02-24
      • 2013-04-30
      • 2014-03-10
      • 1970-01-01
      • 1970-01-01
      • 2019-05-19
      • 1970-01-01
      相关资源
      最近更新 更多