Spring Boot的multipartfile始终为null。

19

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

我尝试使用multipart文件上传,但在控制器中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();
}

我的集成测试使用了Retrofit:

@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
@shawn-clark 这是一个出站请求。我不确定如何在多部分解析器本身中启用日志记录。请求头: 接受:application/json 授权:Bearer [auth token] 请求正文: --56436527-d311-4d26-8e67-27fdf6f0edb8 内容-Disposition: form-data; name="file" 内容传输编码:二进制 内容类型:image/* 内容长度:180999[... 二进制...]--56436527-d311-4d26-8e67-27fdf6f0edb8-- - JabariP
由于您正在使用Spring Boot 1.4,您可以使用我的解决方案,它是可行的。 - rajadilipkolli
5个回答

5

请检查您的application.properties文件中是否含有以下行:

spring.http.multipart.enabled = true

2
如果您正在使用Spring 2.0或更高版本,则现在是 spring.servlet.multipart.enabled = true - eduardog3000
1
spring.servlet.multipart.enabled 默认为 true - undefined

5

默认情况下,Spring不启用多部分功能,因为一些开发人员希望自己处理多部分。您需要启用Spring Multipart Resolver以启用Spring的多部分处理。

默认情况下,Spring不进行多部分处理,因为一些开发人员希望自己处理多部分。您可以通过向Web应用程序上下文添加多部分解析器来启用Spring多部分处理。

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

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

*** 更新 *** 根据评论,我的先前答案不正确。这是一个更新的示例,我已成功运行。

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

这是一个非常基础的测试,没有特殊的东西。然后我创建了一个 Postman 请求,以下是示例 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是不必要的。尽管如此,我还是添加了它进行检查,但仍然出现了相同的错误。 - JabariP
啊,不错...我没有检查它的自动配置,但后来在MultipartAutoConfiguration下找到了它。默认情况下,它似乎使用StandardServletMultipartResolver。你说你仍然在使用CommonsMultipartResolver时遇到错误,所以看起来更多是请求方式的问题。 - Shawn Clark

4

默认情况下,Spring不会处理multipart,因为某些开发者希望自己处理多部分内容。您需要启用Spring的multipart功能,可以添加一个multipart resolver到Web应用程序上下文中。

要将其添加到配置类中,请添加以下bean:

你需要启用Spring Multipart Resolver,因为Spring默认不启用multipart功能。

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

自从Spring Boot 1.4+开始使用servlet 3.0+,我们可以利用StandardServletMultipartResolver来代替传统的CommonsMultipartResolver


你正在使用哪个版本的Spring Boot? - rajadilipkolli
这是一个工作样例代码:https://github.com/rajadilipkolli/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/configuration/WebMvcConfig.java#L99-L112 - rajadilipkolli

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

很高兴你搞定了。我也在想同样的事情,但是你的示例请求显示名称为“file”。 - Shawn Clark
昨天我花了约5个小时才弄清楚Content-Disposition的相关内容 :/ - Ryan Pelletier

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接