Spring Boot 使用MultipartFile将图像上传到服务器会导致HttpClientErrorException $BadRequest:400错误请求:

sdnqo3pr  于 2023-01-02  发布在  Spring
关注(0)|答案(1)|浏览(465)

取自ThumbSnap

ThumbSnap提供了一个免费的API,可用于以编程方式上传和托管图像和视频。

必填字段:
media-二进制图像/视频数据。应作为多部分/格式数据格式的HTTP POST发送
密钥-ThumbSnap提供的API密钥成功发布图像后,将返回一个JSON格式的文档,其中包含一个“url”,该URL是ThumbSnap上上传照片页面的完整URL。此URL必须链接到应用程序使用的任何ThumbSnap托管的缩略图或图像。
响应示例成功上传图像将返回如下JSON文档:

{
  "data": {
    "id": "soLHmGdX",
    "url": "https://thumbsnap.com/soLHmGdX",
    "media": "https://thumbsnap.com/i/soLHmGdX.png",
    "thumb": "https://thumbsnap.com/t/soLHmGdX.jpg",
    "width": 224,
    "height": 224
  },
  "success": true,
  "status": 200
}

我想在Sping Boot 驱动的应用程序中使用此服务,我已转换此响应:

{
  "data": {
    "id": "soLHmGdX",
    "url": "https://thumbsnap.com/soLHmGdX",
    "media": "https://thumbsnap.com/i/soLHmGdX.png",
    "thumb": "https://thumbsnap.com/t/soLHmGdX.jpg",
    "width": 224,
    "height": 224
  },
  "success": true,
  "status": 200
}

DTO为:

public record ThumbSnapResponseDTO(
        @JsonProperty(value = "data")
        DataDTO data,
        boolean success,
        int status
) {
        public record DataDTO(
                String id,
                String url,
                String media,
                String thumb,
                int width,
                int height
        ) {}
}

接下来是具有以下逻辑的服务类:

@Slf4j
@Service
@RequiredArgsConstructor
public class ThumbSnapServiceImpl implements ThumbSnapService {

     private final RestTemplate restTemplate;
     private final ObjectMapper objectMapper;

    @Async
    @Override
    public Future<ThumbSnapResponseDTO> uploadImage(byte[] imageData, String filename) throws IOException {
        HttpHeaders headers = new HttpHeaders();
        // headers.setContentType(MediaType.MULTIPART_FORM_DATA); // First tried this
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // Second this also does not work

        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("key", THUMB_SNAP_API_KEY);
        body.add("media", new ByteArrayResource(imageData) {
            @Override
            public String getFilename() {
                return filename;
            }
        });

        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
        restTemplate.getMessageConverters().add(new MultiPartMessageConverter(objectMapper));

        ResponseEntity<ThumbSnapResponseDTO> response =
                restTemplate.exchange(
                        THUMB_SNAP_BASE_URL,
                        HttpMethod.POST,
                        requestEntity,
                        ThumbSnapResponseDTO.class
                );

        CompletableFuture<ThumbSnapResponseDTO> future = new CompletableFuture<>();
        log.info("REST RESPONSE {} ", response.getBody());
        future.complete(response.getBody());
        return future;
    }
}

最后但并非最不重要的是实际使用此服务的Controller方法:

// ThumbSnap
private final ThumbSnapService thumbSnapService;

@Override
@PostMapping("/{portfolioId}/upload")
public ResponseEntity<?> saveToThumbSnap(
        @PathVariable(name = "portfolioId") Long portfolioId,
        @RequestPart("media") MultipartFile file
) throws ExecutionException, InterruptedException, IOException {
    // Logign
    String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
    long size = file.getSize();

    log.info("File is {} - - and size is {} ", fileName, size);
    // Get Data as bytes
    byte[] imageData = file.getBytes();
    
    Future<ThumbSnapResponseDTO> response = thumbSnapService.uploadImage(imageData, fileName);
    ThumbSnapResponseDTO result = response.get();

    var portfolio = portfolioService.findById(portfolioId).orElseThrow(
            () -> new EntityNotFoundException(String.format("Record not found with id = %s", portfolioId))
    );

    // Check if Image Uploaded Successfully
    if (!result.success())
        throw new RuntimeException("Unable to upload image CONTROLLER");

    var portfolioItemImage = new PortfolioItemImage();
    //set img URL
    portfolioItemImage.setImgUrl(result.data().url());
    // Save mapping
    portfolioItemImage.setPortfolio(portfolio);

    return ResponseEntity.status(HttpStatus.OK).body(
            portfolioItemImageMapper.asDTO(portfolioItemImageService.save(portfolioItemImage))
    );
}

当我在PostMan REST客户端中尝试将post请求发送到上述端点时,我得到了错误:

{
    "code": "EXECUTION",
    "message": "org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: \"No API key specified. Upload stopped\""
}

屏幕截图如下:

我已经检查了API_KEY无数次,但是没有效果

问题是:

下面的代码是否有问题:

MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("key", THUMB_SNAP_API_KEY);
body.add("media", new ByteArrayResource(imageData) {
    @Override
    public String getFilename() {
        return filename;
    }
});

HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
restTemplate.getMessageConverters().add(new MultiPartMessageConverter(objectMapper));

ResponseEntity<ThumbSnapResponseDTO> response =
        restTemplate.exchange(
                THUMB_SNAP_BASE_URL,
                HttpMethod.POST,
                requestEntity,
                ThumbSnapResponseDTO.class
        );

或者有任何其他的方法来完成这一点。我怎样才能正确地发送请求,你能请建议一个解决方案吗?

wswtfjt7

wswtfjt71#

至于上面的代码,除了@PostMapping之外,其他一切看起来都很好。您必须按如下所示更改该行:

@PostMapping(path = "/{userId}/upload",
            consumes =  MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )

consume属性用于指定方法可以在请求正文中处理的媒体类型。MediaType.MULTIPART_FORM_DATA_VALUE值表示方法可以接受 multipart/form 数据格式的数据,该格式通常用于文件上载操作,而MediaType.APPLICATION_JSON_VALUE值表示方法可以返回application/json的数据。
现在它应该按预期/预期工作。

相关问题