背景
这个问题是在我执行任务时出现的 POST multipart/form-data
文件上载的终结点,其中包含一些元信息。我不是春靴生态系统的Maven;这个问题很可能是通过一个简单的修复方法解决的,而我只是缺少了一个合适的搜索词。
问题陈述
为了实现一个带有附加元信息的文件上传端点,我编写了以下代码 @RestController
:
@RestController
@RequestMapping(Resource.ROOT)
@AllArgsConstructor(onConstructor = @__({@Inject}))
public class Resource {
public static final String ROOT = "/test";
private final Logger logger;
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<Void> test(@Valid final Request request) {
logger.info("request = {}", request);
return ResponseEntity.ok().build();
}
}
与 Request
指定为:
@Value
@AllArgsConstructor
public class Request {
@NotNull
String name;
@NotNull
MultipartFile file;
}
还有一个快乐小径测试:
@SpringBootTest
@AutoConfigureMockMvc
class TestCase {
@Autowired
private MockMvc mockMvc;
@Test
void shouldReturnOk() throws Exception {
// GIVEN
final byte[] content = Files.readAllBytes(Path.of(".", "src/test/resources/PenPen.png"));
final String name = "name";
// WHEN
// @formatter:off
mockMvc.perform(MockMvcRequestBuilders
.multipart(Resource.ROOT)
.file("file", content)
.param("name", name))
// THEN
.andExpect(status().isOk());
// @formatter:on
}
}
完整的mre可以在bitbucket、branch上找到 problem-with-immutable-request
.
运行测试时( ./mvnw test)
,它失败,端点返回 400 BAD REQUEST
而不是 200 OK
. 读取日志会发现请求参数 file
是 null
:
...
Content type = text/plain;charset=UTF-8
Body = file: must not be null.
...
我部分理解为什么是这样 null
. 有了这部分知识,我就可以通过做这个领域来规避这个问题 file
在 Request
可变:
@ToString
@Getter
@AllArgsConstructor
public class Request {
@NotNull
private final String name;
@Setter
@NotNull
private MultipartFile file;
}
“修复”问题的代码可以在bitbucket、branch上找到 problem-solved-by-making-field-mutable
.
然而,这使得 Request
易变的,我想阻止。为了进一步研究,我展开了 Request
并添加了一些日志记录:
public class Request {
private static final Logger LOGGER = LoggerFactory.getLogger(Request.class);
@NotNull
private final String name;
@NotNull
private MultipartFile file;
public Request(final String name, final MultipartFile file) {
this.name = name;
this.setFile(file);
}
public @NotNull String getName() {
return this.name;
}
public @NotNull MultipartFile getFile() {
return this.file;
}
public String toString() {
return "Request(name=" + this.getName() + ", file=" + this.getFile() + ")";
}
public void setFile(final MultipartFile file) {
LOGGER.info("file = {}", file);
this.file = file;
}
}
展开版本的代码可以在bitbucket、branch上找到 lombok-unrolled-for-debugging
.
当查看现在成功的测试的日志语句时,我们可以看到 Request::setFile
调用两次:
2020-09-05 09:42:31.049 INFO 11012 --- [ main] d.turing85.springboot.multipart.Request : file = null
2020-09-05 09:42:31.056 INFO 11012 --- [ main] d.turing85.springboot.multipart.Request : file = org.springframework.mock.web
第一个调用来自构造函数调用。我想,第二个调用来自spring的表单参数Map机制。
我知道有可能在端点上单独定义表单参数并构造 Request
方法中的示例:
public class Resource {
...
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<Void> test(
@RequestPart(name = "name") final String name,
@RequestPart(name = "file") final MultipartFile file) {
final Request request = new Request(name, file);
logger.info("request = {}", request);
return ResponseEntity.ok().build();
}
}
然而,这将导致其他问题。例如,我们必须为添加一个额外的异常Map器 MissingServletRequestPartException
并将返回的http响应与的现有响应对齐 BindException
. 如果可能的话,我想避免这种情况。
对这个主题的搜索出现了springbootcontroller-upload multipart和json-to-dto。然而,这个解决方案并不适合我,因为我不使用mvc(我认为)。
问题
有没有可能 Request
不变的,这样Spring就可以通过 MultipartFile
而不是事后通过setter设置它?编写自定义Map器/转换器是可以接受的,但我没有发现为特定端点或特定类型编写Map器的可能性。
1条答案
按热度按时间7ajki6be1#
它仍在使用restapi调用。但我真的没有得到你的不变性的担心。
如果您定义setter多部分数据,您可以使用modeldattribute。
上面的代码使用modelattribute。
而且你给出的绝对路径,我猜是错的。你可以用classloader获取文件。