【问题标题】:Spring Boot multipartfile always nullSpring Boot multipartfile 始终为空
【发布时间】:2016-12-24 18:23:30
【问题描述】:

我正在使用 Spring Boot 版本 = '1.4.0.RC1' 和 Spring Boot Stormpath 1.0.2。

我正在尝试使用多部分文件上传,但控制器中的 MultipartFile 始终为空。

当我使用@RequestPart("file") 时,信息:"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present"

当我使用@RequestPart(name = "file", required = false) 时,该部分始终为空。

但是,如果我向控制器添加一个 HttpServletRequest 参数,我可以直接从请求中获取文件部分,因此我知道它确实存在。

这是控制器,在下面的代码中 checkNotNull(part) 总是成功,checkNotNull(imageFile) 总是失败:

@PostMapping("{username}/profilePhoto")
public ResponseEntity<?> saveProfilePhoto(@PathVariable("username") String username,
                                          @RequestPart(name = "file", required = false) MultipartFile imageFile,
                                          HttpServletRequest request) {
    try {
        Part part = request.getPart("file");
        checkNotNull(part);
        checkNotNull(imageFile);
    } catch (IOException | ServletException ex) {
        throw InternalServerErrorException.create();
    }

    // Transfer the multipart file to a temp file
    File tmpFile;
    try {
        tmpFile = File.createTempFile(TMP_FILE_PREFIX, null);
        imageFile.transferTo(tmpFile);
    } catch (IOException ex) {
        log.error("Failed to create temp file", ex);
        throw InternalServerErrorException.create();
    }

    // Execute the use case
    updateUserProfilePhoto.execute(username, tmpFile);

    // Delete the temp file
    FileUtils.deleteQuietly(tmpFile);

    return ResponseEntity.status(HttpStatus.CREATED).build();
}

我的集成测试使用改造:

@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
                              @Part("file") RequestBody profilePhoto);

...

@Test
public void saveProfilePhoto_shouldSavePhoto() throws IOException {
    // Given
    String usernamme = usernames[0];
    Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
    File imageFile = testImageResource.getFile();
    RequestBody body = RequestBody.create(okhttp3.MediaType.parse("image/*"), imageFile);

    // When
    Response<Void> response = getTestApi().uploadProfilePhoto(usernamme, body).execute();

    // Then
    assertThat(response.code()).isEqualTo(201);
}

我正在使用自动配置,所以我唯一的自定义配置类配置了 Stormpath:

@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.apply(stormpath());
    }
}

更新: 这是传出请求。我不确定如何在多部分解析器本身中启用日志记录。

2016-08-18 14:44:14.714 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --> POST http://localhost:8080/users/user1/profilePhoto http/1.1
2016-08-18 14:44:14.714 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Content-Type: multipart/form-data; boundary=fe23ef21-3413-404c-a260-791c6921b2c6
2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Content-Length: 181212
2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Accept: application/json
2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Authorization: Bearer [token]
2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : 
2016-08-18 14:44:14.735 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --fe23ef21-3413-404c-a260-791c6921b2c6
Content-Disposition: form-data; name="file"
Content-Transfer-Encoding: binary
Content-Type: image/*
Content-Length: 180999

file data

--fe23ef21-3413-404c-a260-791c6921b2c6--

2016-08-18 14:44:14.762 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --> END POST (181212-byte body)

对正在发生的事情有任何想法吗?

【问题讨论】:

  • 你能包括请求有效载荷的样子吗?甚至可以将调试级别日志记录添加到您正在使用的MultipartResolver 中,以查看它是否正在解释请求的多部分组件?
  • @shawn-clark 这是传出请求。我不确定如何在多部分解析器本身中启用日志记录。请求标头:接受:application/json 授权:Bearer [auth token] 请求正文:--56436527-d311-4d26-8e67-27fdf6f0edb8 内容处置:form-data; name="file" Content-Transfer-Encoding: binary Content-Type: image/* Content-Length: 180999 [... binary...] --56436527-d311-4d26-8e67-27fdf6f0edb8--
  • 当您使用 spring boot 1.4 时,您可以使用我的解决方案,它可以工作

标签: java spring-mvc spring-boot retrofit multipart


【解决方案1】:

您必须启用Spring Multipart Resolver,因为默认情况下 Spring 不启用多部分功能。

默认情况下,Spring 不做 multipart 处理,因为有些开发者 想自己处理多部分。您启用 Spring 多部分 通过将多部分解析器添加到 Web 应用程序的 上下文。

您需要在配置类中添加以下 bean:

@Bean
public MultipartResolver multipartResolver() {
    return new CommonsMultipartResolver();
}

*** 更新 *** 由于我之前的回答基于 cmets 是不正确的。这是我能够成功运行的更新示例。

@SpringBootApplication
public class StackoverflowWebmvcSandboxApplication {
    public static void main(String[] args) {
        SpringApplication.run(StackoverflowWebmvcSandboxApplication.class, args);
    }

    @Controller
    public class UploadPhoto {
        @PostMapping("{username}/profilePhoto")
        public ResponseEntity<String> saveProfilePhoto(@PathVariable("username") String username,
                @RequestPart(name = "file", required = false) MultipartFile imageFile, HttpServletRequest request) {
            String body = "MultipartFile";
            if (imageFile == null) {
                body = "Null MultipartFile";
            }

            return ResponseEntity.status(HttpStatus.CREATED).body(body);
        }
    }
}

这是一个非常基本的测试,没有什么特别的东西。然后我创建了一个邮递员请求,这里是 curl 调用示例:

curl -X POST -H "Cache-Control: no-cache" -H "Postman-Token: 17e5e6ac-3762-7d45-bc99-8cfcb6dc8cb5" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "file=@" "http://localhost:8080/test/profilePhoto"

响应是MultipartFile,这意味着它不为空,并且对该行进行调试表明该变量已填充了我正在上传的图像。

【讨论】:

  • Spring Boot Web MVC 会自动配置多部分解析器,因此不需要添加该 bean。不过,作为检查,我确实添加了它,但仍然遇到同样的错误。
  • 啊很好...没有检查它的自动配置,但后来在MultipartAutoConfiguration 下找到了它。它看起来默认使用StandardServletMultipartResolver。您说您仍然收到CommonsMultipartResolver 的错误,所以看起来请求的传入方式更多是一个问题。
【解决方案2】:

我发现问题在于我使用 Retrofit 构建请求的方式。

Spring 的多部分解析器要求文件的文件名出现在部分的内容处置字段中。没有这个,它不会将文件添加到多部分请求中。

根据这里找到的信息:https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server,我的 API 接口应该是:

@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
                              @Part MultipartBody.Part profilePhoto);

然后在我的测试中拨打电话时:

// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", imageFile.getName(), requestFile);

// When
Response<Void> response = testApi.uploadProfilePhoto(usernamme, filePart).execute();

【讨论】:

  • 很高兴你成功了。我也是这么想的,但是您的示例请求显示该名称作为“文件”存在。
  • 我昨天在大约 5 小时后发现了关于 Content-Disposition 的问题:/
【解决方案3】:

您必须启用Spring Multipart Resolver,因为默认情况下 Spring 不启用多部分功能。

默认情况下,Spring 不做 multipart 处理,因为有些开发者 想自己处理多部分。您启用 Spring 多部分 通过将多部分解析器添加到 Web 应用程序的 上下文。

您需要在配置类中添加以下 bean:

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

由于 spring boot 1.4+ 使用 servlet 3.0+,我们可以利用 StandardServletMultipartResolver 而不是经典的 CommonsMultipartResolver

【讨论】:

【解决方案4】:

检查您的 application.properties 中是否存在以下行:

spring.http.multipart.enabled = true

【讨论】:

  • 如果您使用的是 Spring 2.0 或更高版本,现在是 spring.servlet.multipart.enabled = true
【解决方案5】:

请参考我在此网址中的回答: Spring Boot multipart upload getting null file object

基本上你必须避免使用默认的 Spring 多部分配置,并避免在请求标头中设置 Content-type 或边界。

【讨论】:

    猜你喜欢
    • 2021-05-29
    • 2012-07-06
    • 2015-09-02
    • 1970-01-01
    • 2020-11-09
    • 2020-04-19
    • 2021-01-18
    • 1970-01-01
    • 2017-11-27
    相关资源
    最近更新 更多