【问题标题】:Spring: JSON data and file in the same requestSpring:同一请求中的 JSON 数据和文件
【发布时间】:2016-01-15 11:31:24
【问题描述】:

我知道如何使用MediaType.MULTIPART_FORM_DATA@FormDataParam("file") FormDataBodyPart bodyPart 创建处理文件的端点,但我想知道我是否也可以在该请求中包含 JSON 数据?比如:

    @POST
    @Path("somepath")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFileAndJSON(@RequestBody SomeModel someModel,
                                      @FormDataParam("file") FormDataBodyPart bodyPart) { 
         return null;
    }

目前,如果我在以下 Postman 请求的“原始”选项卡上添加一些 JSON 数据,我会收到 HTTP 415 Unsupported Media Type 可能是因为我指定我使用 MULTIPART_FORM_DATA,但我也在使用 @RequestBody正在寻找 APPLICATION_JSON 的 JSON 内容。那么如何在同一个请求中处理 JSON 数据和文件呢?我知道可以在两个请求中做到这一点,我只想在一个可能的情况下做到这一点?

【问题讨论】:

标签: java json spring rest multipartform-data


【解决方案1】:

为什么同时使用 Spring 和 Jersey 注释?您应该坚持使用适用于框架的注释。由于您使用的是 Jersey,因此应坚持使用它的 its 注释。

以下是关于您当前的代码和环境需要考虑的事项。

  1. 不能有两个独立的实体。使用您的代码,这就是您期望发生的事情。
  2. 可以将 JSON 作为多部分正文的一部分。为此,您还应该使用 Jersey @FormDataParam 注释 SomeModel

    @POST
    @Path("somepath")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFileAndJSON(
             @FormDataParam("model") SomeModel someModel,
             @FormDataParam("file") FormDataBodyPart bodyPart) { 
    }
    
  3. 在 Jersey 配置中,您需要确保注册 MultiPartFeature。如果你不这样做,body 将无法被反序列化,你会得到异常和错误响应。

  4. 现在是邮递员问题。你可以看到类似的问题here。问题是没有为 JSON 正文部分设置 Content-Type。例如,身体可能看起来像

    --AaB03x
    Content-Disposition: form-data; name="model"
    
    {"some":"model", "data":"blah"}
    --AaB03x
    Content-Disposition: form-data; name="file"; filename="file1.txt"
    Content-Type: text/plain
    
    ... contents of file1.txt ...
    --AaB03x--
    

    如果您在 Postman 中点击 Preview 按钮,您实际上可以看到正文。问题是"model" 部分没有Content-Type,正如您在"file" 部分中看到的那样。发生这种情况是因为您无法在 Postman 中设置单个部分的 Content-Type。您将看到的将从文件扩展名中找到。例如,.txt 文件将使 Postman 将 Content-Type 设置为 text/plain,将 .png 文件设置为 image/png

    如果您查看上面的链接,我建议您可以使用.json 文件而不是输入数据。当然,这只是一个理论。我实际上并没有测试它。

    无论如何,必须设置 Content-Type 以便 Jersey 能够知道将其反序列化为 JSON。如果.json 文件扩展理论没有成功,那么您可以使用不同的客户端,例如 cURL,我在链接中显示了一个示例,或者您可以使用 Jersey 客户端进行测试,如 here 所示。

  5. 不要在 Postman 中将 Content-Type 标头设置为 multipart/form-data。当您使用 form-data 时,它会为您设置。我刚看到一个帖子,有人说设置标题时有错误。现在找不到该帖子,也不是我已确认的内容,但我会忽略它。


更新

所以 OP 能够找到将Content-Type: application/json 设置为“模型”部分的方法。但有时在使用 Javascript 客户端时,您无法设置它。所以不会有Content-Type。如果是这种情况,Jersey 将无法反序列化 JSON,因为它不知道它实际上是在发送 JSON。如果您绝对不能或不知道如何为各个部分设置Content-Type,您可以采取以下措施。

@POST
@Path("somepath")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(@FormDataParam("model") FormDataBodyPart jsonPart,
                                  @FormDataParam("file") FormDataBodyPart bodyPart) { 
     jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
     SomeModel model = jsonPart.getValueAs(SomeModel.class);
}

【讨论】:

  • stackoverflow.com/questions/29406331/… 我使用它来注册 MultiPartFeature 以便能够执行 @FormDataParam("model") SomeModel someModel。我仍然将模型设为空。如果我将类型更改为字符串,我可以将其作为字符串的想法。有什么我可能错过的想法吗?
  • 如果您没有将“模型”部分的Content-Type 设置为application/json,就会发生这种情况。我的答案和链接的答案中也对此进行了解释。
  • 我的理论是,不要输入 JSON,而是尝试使用添加带有 .json 扩展名的文件。它可能Content-Type 设置为application/json。我不确定。这只是一个理论。我无法测试,因为当我更新我的 chrome 时,不知何故我的邮递员被删除了。我试图再次安装它,但它是一个我鄙视的新版本,所以我停止使用 Postman。如果这不起作用,那么您可能需要使用不同的客户端进行测试。使用 cURL,您可以设置各个部分的内容类型。或者对于 Jersey 客户端,该链接有一个示例。
  • 试试这个。代替SomeModel 作为方法参数,使用FormDataBodyPart。然后执行bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE),然后执行SomeModel model = bodyPart.getValueAs(SomeModel.class)。就像你使用 Gson 一样多的工作,但至少你不需要 Gson 依赖
  • 我最终修改了客户端,将 Content-Type application.json 添加到请求的模型部分,然后我就可以用 Jersey 对其进行序列化了 :) 感谢您的帮助,非常感谢!
【解决方案2】:

是的,您可以将其作为多部分表单数据获取。

你会在 angularjs 中得到这样的结果:

$scope.uploadFile = function () {
                var file = $scope.selectedFile[0];
                $scope.upload = $upload.upload({
                    url: 'api/upload',
                    method: 'POST',
                    data: angular.toJson($scope.model),
                    file: file
                }).progress(function (evt) {
                    $scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total, 10);
                }).success(function (data) {
                    //do something
                });
            };

            $scope.onFileSelect = function ($files) {
                $scope.uploadProgress = 0;
                $scope.selectedFile = $files;
            };
public Response uploadFileAndJSON(@RequestParam("data") String data,
                                      @MultiPartFile("file")File file) { 
  you can data as form data and convert it 
  like you want to your object using Gson jar.       
 return null;
    }

查看 angularjs 代码: Angularjs how to upload multipart form data and a file?

https://puspendu.wordpress.com/2012/08/23/restful-webservice-file-upload-with-jersey/

【讨论】:

    猜你喜欢
    • 2017-02-03
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    • 1970-01-01
    • 2018-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多