我有一个工作上传后端与 Postman 。但我不能让它在Angular 上工作。
后台代码:
package com.demo.web.api.file;
import static java.nio.file.Files.copy;
import static java.nio.file.Paths.get;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.esotericsoftware.minlog.Log;
@RestController
@RequestMapping(value = "/files")
public class FileUploadService {
@PostMapping(value = "/upload", consumes = MediaType.ALL_VALUE)
public ResponseEntity<List<String>> uploadFiles(@RequestParam("files") MultipartFile[] files) {
Log.info("Processing file upload...");
List<String> exceptions = new ArrayList<>();
// Upload directory
final String DIRECTORY = System.getProperty("user.home") + "/Documents/Uploads";
List<String> fileNames = new ArrayList<>();
for (MultipartFile file : files) {
String fileName = file.getOriginalFilename();
try {
Path fileStorage = get(DIRECTORY, fileName).toAbsolutePath().normalize();
copy(file.getInputStream(), fileStorage, REPLACE_EXISTING);
} catch (Exception e) {
exceptions.add(e.getMessage());
e.printStackTrace();
}
fileNames.add(fileName);
}
if (!exceptions.isEmpty()) {
return ResponseEntity.badRequest().body(exceptions);
}
return ResponseEntity.ok().body(fileNames);
}
}
以下是Postman中的一个示例执行,它返回上传的文件列表:
如上图所示,后端代码工作正常。
上传.Component.ts
import { Component } from '@angular/core';
import { AppConfigService } from 'src/shared/services/app-config.service';
import getClassNameForExtension from 'font-awesome-filetypes';
import { HttpClient } from '@angular/common/http';
import { NgxSpinnerService } from 'ngx-spinner';
@Component({
selector: 'my-app',
templateUrl: './upload.component.html',
styleUrls: ['./upload.component.scss'],
})
export class UploadComponent {
files = [];
totalSize: number = 0;
maxUploadSize: number;
fileExtensions: Array<string> = [];
hasInvalidFile: boolean = false;
constructor(
private appConfigService: AppConfigService,
private spinner: NgxSpinnerService,
private http: HttpClient
) {}
ngOnInit() {
this.maxUploadSize = this.appConfigService.configData.maxUploadSize;
if (this.appConfigService.configData.fileExtensions) {
const extensions =
this.appConfigService.configData.fileExtensions.split(',');
extensions.forEach((ext) => {
this.fileExtensions.push(ext.trim());
});
}
}
onFileDropped($event) {
this.prepareFilesList($event);
}
fileBrowseHandler(files) {
this.prepareFilesList(files);
}
deleteFile(index: number) {
let newSize = 0;
this.files.splice(index, 1);
let allValid = true;
this.files.forEach((file) => {
if (file.invalidFileExtension) {
allValid = false;
}
newSize += file.size;
});
this.hasInvalidFile = !allValid;
this.totalSize = newSize;
}
uploadFilesSimulator(index: number) {
setTimeout(() => {
if (index === this.files.length) {
return;
} else {
const progressInterval = setInterval(() => {
if (this.files[index]) {
if (this.files[index].progress === 100) {
clearInterval(progressInterval);
this.uploadFilesSimulator(index + 1);
} else {
this.files[index].progress += 5;
}
}
}, 200);
}
}, 1000);
}
prepareFilesList(files: Array<any>) {
for (const file of files) {
// const ext = file.name.substr(file.name.lastIndexOf('.') + 1);
file.progress = 0;
const extension = file.name.split('.').pop();
file.extension = extension;
const className = getClassNameForExtension(extension);
file.className = className;
if (
this.fileExtensions.length > 0 &&
!this.fileExtensions.includes(extension)
) {
file.invalidFileExtension = true;
this.hasInvalidFile = true;
}
this.files.push(file);
this.totalSize += file.size;
}
this.uploadFilesSimulator(0);
}
/**
* Format size in bytes
* @param bytes (File size in bytes)
* @param decimals (Decimals point)
*/
formatBytes(bytes) {
if (bytes === 0) {
return '0 Bytes';
}
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
upload() {
const formData = new FormData();
for (const file of this.files) {
formData.append('files', file);
}
this.spinner.show();
this.http.post(`http://localhost:16080/files/upload`, formData);
}
}
上传组件.html
<div class="ui-g" style="height: 87vh">
<!-- Spinner -->
<ngx-spinner
bdColor="rgba(189,188,188,0.6)"
size="medium"
color="#4a4848"
type="ball-beat"
[fullScreen]="true"
>
</ngx-spinner>
<div
class="ui-lg-12 ui-md-12 ui-g-12"
style="padding: 1.25%; padding-bottom: 0; height: 99%"
>
<mat-card
class="ui-lg-12 ui-md-12 ui-g-12 app-card"
style="height: 100%; overflow: auto"
>
<!-- Card-header -->
<div class="app-card-header flex-align-center ui-lg-12 ui-md-12 ui-g-12">
<!-- Breadcrumbs -->
<div class="ui-lg-12 ui-md-12 ui-g-12 p-0 d-flex align-center">
<i
[matTooltip]="''"
matTooltipClass="mat-tool-cust"
class="fas fa-table-cells-large app-icon-head ocular-app-card-header-icon"
></i>
<span style="padding: 0 8px"> Data Processing </span>
</div>
</div>
<!-- Card Content -->
<div class="upload-container" (fileDropped)="onFileDropped($event)">
<form method="post" enctype="multipart/form-data">
<input
type="file"
#fileDropRef
id="fileDropRef"
multiple
(change)="fileBrowseHandler($event.target.files)"
/>
<i class="fas fa-regular fa-upload"></i>
<h3>Drag and drop files here</h3>
<h3>or</h3>
<label
for="fileDropRef"
class="app-btn1 mat-raised-button mat-button-base"
>Click here to browse files</label
>
</form>
</div>
<div class="files-list">
<div class="single-file" *ngFor="let file of files; let i = index">
<div class="file-icon">
<i class="fa {{ file.className }}" style="font-size: 20px"></i>
</div>
<div class="info">
<h4
class="name"
[ngClass]="file.invalidFileExtension ? 'strike-through' : ''"
>
{{ file?.name }}
</h4>
<p class="size">
{{ formatBytes(file?.size) }}
</p>
<app-progress [progress]="file?.progress"></app-progress>
</div>
<div class="delete-ctn" (click)="deleteFile(i)">
<i class="fas fa-trash action-icons" matTooltip="Delete"></i>
</div>
</div>
</div>
<div
class="upload-btn-container"
*ngIf="
files.length > 0 && (hasInvalidFile || totalSize > maxUploadSize)
"
>
<div style="justify-content: center; color: red">
Files cannot be uploaded. Some files may not be in the supported
format ({{ fileExtensions }}) or the total allowable upload size may
have exceeded {{ formatBytes(maxUploadSize) }}.
</div>
</div>
<div
class="upload-btn-container"
*ngIf="
files.length > 0 && !hasInvalidFile && totalSize <= maxUploadSize
"
>
<button
type="submit"
(click)="upload()"
mat-raised-button
class="app-btn2 save-btn"
>
Upload Files
</button>
</div>
</mat-card>
</div>
</div>
有效载荷
我收到了一个错误
nested exception is org.springframework.web.multipart.MultipartException: Current request is not a multipart request] with root cause
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
我可以看到与Postman的有效负载略有不同,因为它提供了完整的路径。但出于安全目的,这在Java脚本中是不可能实现的。
2条答案
按热度按时间ljsrvy3e1#
提供标题Content-Type:文件,它应该可以解决问题。
或
c3frrgcw2#
我的原始代码正在按预期运行。问题出在我们的项目中,有一个HttpInterceptor。如果没有传递,它会自动附加一个标头
'Content-Type', 'application/json'
。我在http调用中添加了这一点。
在拦截器中,我添加了一个条件以保留先前的逻辑。
在注解掉设置Content-Type的部分之后,它就像预期的那样工作了。