angular - 是否可以在 httpClient.post 中发送 FormData 对象数组?
我有一个 Angular 前端应用和一个 Java 后端,用户可以选择一个文档,然后保存它。
为此,我一直在无问题地将 FormData 对象从前端发送到后端。
这是我目前在前端所拥有的一段代码,它可以将单个文件的内容发送到后端:
public postSaveDocument(entityType: DocumentEntityType, id: number, body: DocumentInputDto): Observable<Upload<UploadWithLockResultsDto>> {
const url = `${DocumentationService.baseUrl}/${getUrlSegment(entityType)}/${id}/documents`;
//Create a FormData object and set the values of the document
const formData = new FormData();
formData.append('documentId', this.documentId.toString());
formData.append('title', this.title);
formData.append('content', this.content, this.content.name);
return this.httpClient.post(url, formData,
{...DocumentationService.options, reportProgress: true, observe: 'events', responseType: 'json'})
.pipe(upload<UploadWithLockResultsDto>());
}
这是我在后台保存该文件的方法:
@PostMapping("/{documentEntityType}/{entityId}/documents")
public ResponseEntity<UploadWithLockResultsDto> saveDocument(
@PathVariable("documentEntityType") DocumentEntityType documentEntityType,
@PathVariable("entityId") Integer entityId,
DocumentInputDto documentInputDto,
) throws AuthException, IOException {
//Save the updates to the database
var uploadWithLockResultsDto = documentService.documentUpdate(documentInputDto, authentication.getName(), authentication.getPrincipal());
return handleUploadWithLockResults(uploadWithLockResultsDto);
}
这对于保存单个文档来说完全可行。
现在我想做的是允许用户一次选择多个文档,并在将它们发送到后端时一次性保存它们。
到目前为止,这方面没有运气。
我尝试过在前端把数据放入一组 FormData 对象中
public postSaveDocuments(entityType: DocumentEntityType, id: number, body: DocumentInputDto[]): Observable<Upload<UploadWithLockResultsDto>> {
const formDataArray: any[] = [];
body.forEach(documentInputDto => {
const formData = new FormData();
formData.append('documentId', this.documentId.toString());
formData.append('title', this.title);
formData.append('content', this.content, this.content.name);
formDataArray.push(formData);
});
return this.httpClient.post(url, formDataArray,
{...DocumentationService.options, reportProgress: true, observe: 'events', responseType: 'json'})
.pipe(upload<UploadWithLockResultsDto>());
}
然后我试着在后端把它作为数组捞起来
@PostMapping("/{documentEntityType}/{entityId}/documents")
public ResponseEntity<UploadWithLockResultsDto> saveDocument(
@PathVariable("documentEntityType") DocumentEntityType documentEntityType,
@PathVariable("entityId") Integer entityId,
DocumentInputDto[] documentInputDtos,
) throws AuthException, IOException {
//Save the docs to the database
var uploadWithLockResultsDto = documentService.updateDocuments(documentInputDtos, authentication.getName(), authentication.getPrincipal());
return handleUploadWithLockResults(uploadWithLockResultsDto);
}
不足为奇,这行不通。它甚至根本没有到达后端。我意识到,从前端发送一组 FormData 对象到后端可能本就不是其设计用途。
有人能告诉我如何修改这个调用,以便从前端发送多个 FormData 对象到后端吗?
非常感谢。
答案#1
你走在正确的路上,但直接从前端将多个 FormData 对象以数组的形式发送到后端是行不通的,因为 FormData 对象是作为一个单一实体发送的。相反,你应该使用多部分表单请求,将文档作为单独的部分包含在一个 FormData 对象中发送。
步骤 1:前端 - 修改 FormData 对象以处理多个文件 要发送多个文件,您可以将每个文档的数据附加到同一个 FormData 对象中,并为每个文档使用唯一的键。这将允许您在一次 HTTP 请求中发送多个文档。
更新的前端代码:
public postSaveDocuments(entityType: DocumentEntityType, id: number, body: DocumentInputDto[]): Observable<Upload<UploadWithLockResultsDto>> {
const url = `${DocumentationService.baseUrl}/${getUrlSegment(entityType)}/${id}/documents`;
// Create a single FormData object for all documents
const formData = new FormData();
// Loop through the document DTOs and append each one
body.forEach((documentInputDto, index) => {
formData.append(`documents[${index}].documentId`, this.documentId.toString());
formData.append(`documents[${index}].title`, this.title);
formData.append(`documents[${index}].content`, this.content, this.content.name);
});
return this.httpClient.post(url, formData, {
...DocumentationService.options,
reportProgress: true,
observe: 'events',
responseType: 'json'
}).pipe(upload<UploadWithLockResultsDto>());
}
步骤2:后端 - 处理多部分表单数据 在后端,你需要处理多个文件及其相关的元数据。你可以通过将数据作为MultipartFile数组或自定义DTO来接收,具体取决于你希望如何组织请求。由于你正在发送元数据和文件,你可以结合使用@RequestParam和@ModelAttribute注解来绑定表单数据和文件内容。
更新后的后端代码:
@PostMapping("/{documentEntityType}/{entityId}/documents")
public ResponseEntity<UploadWithLockResultsDto> saveDocuments(
@PathVariable("documentEntityType") DocumentEntityType documentEntityType,
@PathVariable("entityId") Integer entityId,
@RequestParam("documents") List<MultipartFile> documents,
@RequestParam Map<String, String> documentMetadata) throws AuthException, IOException {
// Parse metadata and files
List<DocumentInputDto> documentInputDtos = new ArrayList<>();
for (int i = 0; i < documents.size(); i++) {
MultipartFile file = documents.get(i);
String documentId = documentMetadata.get("documents[" + i + "].documentId");
String title = documentMetadata.get("documents[" + i + "].title");
DocumentInputDto documentInputDto = new DocumentInputDto();
documentInputDto.setDocumentId(Long.parseLong(documentId));
documentInputDto.setTitle(title);
documentInputDto.setContent(file); // Assuming the DocumentInputDto has a 'content' field for the file
documentInputDtos.add(documentInputDto);
}
// Save the documents to the database
var uploadWithLockResultsDto = documentService.updateDocuments(documentInputDtos, authentication.getName(), authentication.getPrincipal());
return handleUploadWithLockResults(uploadWithLockResultsDto);
}
步骤 3:更新你的 DocumentInputDto
public class DocumentInputDto {
private Long documentId;
private String title;
private MultipartFile content; // The file being uploaded
// Getters and setters...`enter code here`
}
答案#2
你在处理 documents 数组返回为 null 时遇到的问题,很可能是由于 Spring 处理表单数据和多部分文件上传的方式。问题可能在于,你在请求中将文件和表单数据(如元数据)使用相同的名称(documents[])发送,但 Spring 可能没有正确将 MultipartFile 部分映射到 List documents。
让我们来讲解一些你可以进行的调整,以确保 Spring 正确地将文件数据和元数据映射到适当的字段。
第1步:更新前端代码\n您已经在单个 FormData 对象中发送带有元数据的文件。关键问题是确保请求的结构正确,以便后端能够正确识别文件和元数据。文件需要为每个文档分别发送名称字段,并且元数据应遵循相同的命名约定。
这是更新后的前端代码,用于正确追加多个文档:
public postSaveDocuments(entityType: DocumentEntityType, id: number, body: DocumentInputDto[]): Observable<Upload<UploadWithLockResultsDto>> {
const url = `${DocumentationService.baseUrl}/${getUrlSegment(entityType)}/${id}/documents`;
// Create a single FormData object to handle multiple files and their metadata
const formData = new FormData();
// Loop through the document DTOs and append each one to the formData object
body.forEach((documentInputDto, index) => {
// Append metadata fields
formData.append(`documents[${index}].documentId`, documentInputDto.documentId.toString());
formData.append(`documents[${index}].title`, documentInputDto.title);
// Append the file for this document
formData.append(`documents[${index}].content`, documentInputDto.content, documentInputDto.content.name);
});
// Send the FormData as a POST request
return this.httpClient.post(url, formData, {
...DocumentationService.options,
reportProgress: true,
observe: 'events',
responseType: 'json'
}).pipe(upload<UploadWithLockResultsDto>());
}
步骤 2:更新后端代码\nSpring 使用 @RequestParam 来绑定表单数据和文件上传。解决此问题的关键是确保元数据和文件正确绑定到后端参数。以下是如何调整后端以正确处理文件上传和元数据的方法:
MultipartFile 数组:Spring 的 @RequestParam 会将使用相同名称发送的多个文件映射到一个 List。元数据映射:要映射文档元数据(例如 documentId 和 title),可以使用一个 map 或自定义 DTO。以下是更新后的后端方法:
@PostMapping("/{documentEntityType}/{entityId}/documents")
public ResponseEntity<UploadWithLockResultsDto> saveDocuments(
@PathVariable("documentEntityType") DocumentEntityType documentEntityType,
@PathVariable("entityId") Integer entityId,
@RequestParam("documents") List<MultipartFile> documents, // Receive the files
@RequestParam Map<String, String> documentMetadata) throws AuthException, IOException {
// Ensure the documents and metadata are mapped correctly
List<DocumentInputDto> documentInputDtos = new ArrayList<>();
for (int i = 0; i < documents.size(); i++) {
MultipartFile file = documents.get(i);
// Retrieve metadata for each document
String documentId = documentMetadata.get("documents[" + i + "].documentId");
String title = documentMetadata.get("documents[" + i + "].title");
// Create DocumentInputDto from metadata and file
DocumentInputDto documentInputDto = new DocumentInputDto();
documentInputDto.setDocumentId(Long.parseLong(documentId));
documentInputDto.setTitle(title);
documentInputDto.setContent(file); // Assuming 'content' is a MultipartFile field in your DTO
documentInputDtos.add(documentInputDto);
}
// Save the documents to the database (service call)
var uploadWithLockResultsDto = documentService.updateDocuments(documentInputDtos, authentication.getName(), authentication.getPrincipal());
return handleUploadWithLockResults(uploadWithLockResultsDto);
}
步骤 3:确保 DocumentInputDto 正确 确保您的 DocumentInputDto 类有一个用于 MultipartFile 的字段,并且您正确使用了 getter 和 setter。以下是 DocumentInputDto 类的一个简单版本:
public class DocumentInputDto {
private Long documentId;
private String title;
private MultipartFile content; // The file being uploaded
// Getters and setters
public Long getDocumentId() {
return documentId;
}
public void setDocumentId(Long documentId) {
this.documentId = documentId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public MultipartFile getContent() {
return content;
}
public void setContent(MultipartFile content) {
this.content = content;
}
}
步骤 4:处理多部分配置(如有需要) 确保您的 Spring Boot 应用程序已配置以处理多部分文件上传。通常在 application.properties 或 application.yml 文件中进行配置。以下是配置方法:
application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
