Spring RestTemplate: разместить изображение и объект одновременно

мои пользователи могут опубликовать фотографию продуктов питания и сообщение о том, что продукты питания собираются на моем сервере.

например, скажем, кто-то видит что-то вкусное, фотографирует его, а затем пишет "вкусно!- под картиной. Фотография отправляется на сервер, а сообщение " вкусно!"включая имя пользователя, дату, местоположение и т. д. отправляется в объект "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();

это код на стороне весны:

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

я получаю сообщение об ошибке:

во время выполнения сетевого запроса произошло исключение :не удалось запрос на запись: не найден подходящий HttpMessageConverter для типа запроса [Модель.Post]

org.springframework.http.конвертер.HttpMessageNotWritableException: Не удалось написать запрос: не найден подходящий HttpMessageConverter для тип запроса [Модель.Post]

Я подозреваю, что это связано с типом контента, установленным в MULTIPART_FORM_DATA, но мне нужно, чтобы он был установлен на это, поскольку мне нужно передать изображение на сервер.

возможно ли одновременно перенести составной файл и другой объект вверх по течению с помощью restTemplate?

EDIT:

Я посмотрел на эти сообщения:

форма Resttemplate / multipart: изображение + JSON внутри Пост

отправка составного файла в качестве параметров POST с запросами RestTemplate

и попробовал в соответствии с их руководством этот код:

    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!

5 ответов


попробуйте что-то вроде этого: Отправить 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 для решения моей проблемы. Это не идеально, так как url-адрес станет очень длинным, но это самый быстрый способ решить мою проблему:

пожалуйста, посмотрите на совет от 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/


Я не вижу зарегистрированного HttpMessageConverter для вашего класса Post. Вероятно, вам придется зарегистрировать HttpMessageConverter для MultiValueMap.


oookay,я столкнулся с той же проблемой несколько недель назад. Прежде всего, чтобы было ясно, что multipart/form-data content-type означает:

сообщение" multipart/form-data " содержит ряд частей, каждая из которых представление успешного контроля.

успешный контроль "действителен"для представления. Каждый успешный контроль наименование контроля в сочетании с его текущего значения в рамках представленный набор данных формы

в простых словах, с 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 см. doc здесь.

но FormHttpMessageConverter есть свойство partConverters и они являются преобразователями, зарегистрированными для FormHttpMessageConverter и по умолчанию они (string, bytearray и resource). вот исходный код конструктора ;)

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 to 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;
}

вам нужно рассказать Spring, как сопоставить параметры запроса с вашими объектами. Вы можете сделать это, реализовав custom HttpMessageConterter как предложил Александр, но у меня есть гораздо более простой способ: Используйте command-object (иногда называемый form-backing object):

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, и вам нужно отправить запрос как составной запрос.


Я сделал что-то подобное, используя это:

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