Q1:使用了Blob的方式
你提到的问题是正确的。当使用 Blob 包装 JSON 数据并将其添加到 FormData 中时,即使 Blob 的类型被设置为 application/json,前端在开发者工具中仍然会显示为二进制文件,而不是可读的 JSON 字符串。这是因为 FormData 将 Blob 视为文件处理,因此无法在开发者工具中直接显示其内容。
原因分析
FormData 是为上传文件设计的,它能处理文本字段和文件类型,但对于通过 Blob 包装的 JSON 数据,它会视作二进制数据,因此在开发者工具中显示为“文件”类型。
可能的解决方案与权衡
目前,没有一种能够在浏览器开发者工具中显示 JSON 字符串,同时在后端直接解析为对象的完美解决方案。这是因为 FormData 的设计决定了它如何处理数据。
方案 1: 直接传递 JSON 字符串
- 优点: 前端开发者工具中能看到 JSON 数据的实际内容。
- 缺点: 后端需要将接收到的字符串手动转换为对象。
1 | formData.append("metadata", JSON.stringify(myObject)); // 字符串形式传递 |
**方案 2: 使用 Blob 和 **application/json
- 优点: 后端能够直接将 JSON 数据解析为对象。
- 缺点: 前端开发者工具中显示为二进制文件,不易查看内容。
1 | formData.append("metadata", new Blob([JSON.stringify(myObject)], { type: 'application/json' })); |
总结
如果一定要选择一种方式,只能在这两种方案中取舍:
- 如果你希望前端能清楚看到 JSON 数据内容,且愿意在后端手动解析 JSON 字符串,可以选择方案 1。
- 如果你希望后端自动解析 JSON 数据为对象,且能接受在前端看到的是二进制文件,可以选择方案 2。
这种取舍源于 FormData 的底层设计限制,不能同时满足两个需求。
Q2:具体讲一讲FormData的底层设计
FormData 是一种用于构建包含键值对的数据集的接口,特别适用于 multipart/form-data 格式的数据,这种格式常用于文件上传。FormData 的设计使得它能够灵活地处理各种类型的数据,包括文本、文件、以及二进制数据。
FormData 的底层设计与工作原理
- 键值对的结构
FormData的核心是一个键值对的数据结构。每一个键对应一个值,这个值可以是普通的字符串、二进制数据、文件、甚至是Blob对象。- 每个键可以在
FormData中对应多个值,例如多个文件上传。
- 数据类型的处理
- 字符串:当你将字符串添加到
FormData时,它会作为纯文本处理,并且在请求中以Content-Disposition: form-data; name="key"的形式被发送。 - 文件和 Blob:当你添加
File或Blob对象时,它们会被视为二进制数据。在 HTTP 请求中,这些数据将被发送为Content-Disposition: form-data; name="key"; filename="file"的形式,并且带有相应的Content-Type(如application/json、image/png等)。
- 字符串:当你将字符串添加到
- 多部分数据的构建
FormData通过multipart/form-data格式来发送数据。这种格式将每个字段的内容封装在一个“部分”(part)中,每个部分由唯一的边界(boundary)分隔。- 每个部分包含字段的元数据(如
Content-Disposition和Content-Type)以及实际的数据内容。
Blob** 对象的处理**- 当你使用
Blob对象并指定Content-Type(如application/json),FormData会将其视为一个二进制数据块。虽然它内部可能是可读的 JSON 字符串,但浏览器开发者工具在显示时只会将其当作文件处理,显示为二进制内容。
- 当你使用
- 发送数据
- 在将
FormData对象与XMLHttpRequest或fetchAPI 一起使用时,数据会被序列化为multipart/form-data格式,并附加到 HTTP 请求的主体中发送。服务器端将根据Content-Type头部的multipart/form-data来解析这些部分。
- 在将
底层限制的原因
FormData的设计主要是为了支持文件上传和多部分表单提交,而不是为了在前端方便查看或调试数据。因此,对于二进制数据(如Blob),它不会显示内容的实际值,而是将其处理为文件数据。- 浏览器开发者工具只显示
FormData内部数据的简化视图,对于Blob数据,它不会解码或显示其中的内容,这就是为什么 JSON 数据被Blob包装后在开发者工具中显示为二进制文件。
设计的初衷
FormData 的设计初衷是为了处理各种形式的数据(尤其是文件)上传,保证在数据传输过程中数据的完整性和类型的准确性。因此,它对数据的处理方式偏向于灵活与通用,而不是在调试时的可读性。
Q3:对于FormData数据,后端除了使用RequestPart注解接收,还有其他接收和解析方式吗
除了使用 @RequestPart 注解接收和解析 FormData 数据外,Spring 后端还可以通过以下几种方式来处理 FormData 数据。这些方法在特定情况下可能会更合适,具体取决于你传递的数据类型和需求:
1. 使用 HttpServletRequest 直接解析
- 直接访问请求体:你可以使用
HttpServletRequest来手动解析FormData数据。通过request.getParts()可以获取表单中的所有部分,并手动处理每个部分的数据。 - 适用场景:当你需要更低层次的控制或需要处理非常复杂的
multipart请求时,可以使用这种方法。
1 |
|
2. **使用 **MultipartHttpServletRequest
- **专门处理 **
multipart/form-data:Spring 提供了MultipartHttpServletRequest,它继承自HttpServletRequest,并提供了更方便的方法来处理multipart/form-data请求。 - 适用场景:当你想要简化
multipart数据处理,同时保留对每个部分的细粒度控制时,这种方法非常有用。
1 |
|
3. **使用 @ModelAttribute 与 **@RequestParam
- 结合使用:你可以使用
@ModelAttribute来绑定简单表单数据,同时使用@RequestParam来处理文件上传。这种方式更适合没有复杂 JSON 数据结构的情况。 - 适用场景:当你处理的
FormData主要是简单的键值对数据和文件时,这种方式非常适合。
1 |
|
4. 使用 @RequestBody 配合自定义解析器
- 处理 JSON 数据:虽然
@RequestBody主要用于处理整个请求体作为 JSON 格式的情况,但你可以通过自定义消息转换器(MessageConverter)来处理multipart请求,并将部分内容作为 JSON 对象解析。 - 适用场景:当你需要高度自定义的 JSON 解析时,这种方法可能是有效的,但它比
@RequestPart更复杂且不常用。
1 |
|
总结
@RequestPart是最常见和简便的处理FormData中multipart数据的方法,尤其是在同时处理文件和 JSON 数据时。HttpServletRequest** 与 **MultipartHttpServletRequest提供了更低层次的控制,适用于需要直接操作请求部分的情况。@ModelAttribute** 和 **@RequestParam适用于表单字段简单、文件上传场景,而不包含复杂的 JSON 结构。
根据你项目的需求,可以选择最适合的方式来处理 FormData 数据。