跟着官方httpClient的官方教程做上传文件的component,但是教程的服务端用的是mock的服务,没有带参数,我想上传到springboot,参数名是file,结果试了好久都不行,查了stackoverflow才知道httpclient也要通过formdata上传文件。
原文地址:https://stackoverflow.com/questions/53569024/angular-httpclient-post-two-files-multipart-form-data
文件上传服务
其实基本上是官方的教程,参见:https://angular.io/guide/http#listening-to-progress-events
import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest,HttpHeaders ,HttpEvent, HttpEventType,HttpErrorResponse} from '@angular/common/http';
import { map, tap, last, catchError} from 'rxjs/operators';
import {of} from 'rxjs';
@Injectable(
{
providedIn:'root'
}
)
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
@Injectable({
providedIn: 'root'
})
export class FileUploadService {
constructor(private client:HttpClient,private message:MessageService) {
}
upload(file:File){
//创建formdata,并把数据添加到formdataz中
let formData = new FormData();
formData.append("file",file);
//创建原始的HttpRequest对象
//设置reportProgress为true以监听文件上传事件
let req :HttpRequest<any> =
new HttpRequest('POST', 'http://localhost:8080/upload',formData, {
//不用设置请求头,angular会自动设置的
// headers:new HttpHeaders({'Content-Type':'multipart/form-data'),
reportProgress: true
});
return this.client.request(req).pipe(
//把进度信息添加到消息服务中
tap(event=>this.getMessage(event)),
//只返回最后一条消息给调用方
last(),
//处理错误
catchError(this.handleError(file))
)
}
getMessage(event:HttpEvent<any>){
//跟进度相关的事件有三种
//sent 代表开始上传
//UploadProgress代表上传中
//可以通过event.loaded获得上传了多少
//通过event.total获得要上传的总数
//DownloadProgress代表已上传完毕
if( event.type==HttpEventType.Sent){
this.message.add("file has been sent");
}
if(event.type == HttpEventType.UploadProgress){
this.message.add(`file has been loaded ${(event.loaded/event.total).toFixed(2)}`)
}
if(event.type == HttpEventType.DownloadProgress){
this.message.add("file has been uploaded")
}
}
private handleError(file: File) {
const userMessage = `${file.name} upload failed.`;
return (error: HttpErrorResponse) => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
const message = (error.error instanceof Error) ?
error.error.message :
`server returned code ${error.status} with body "${error.error}"`;
this.message.add(`${userMessage} ${message}`);
// Let app keep running but indicate failure.
return of(userMessage);
};
}
}
组件
import { Component, OnInit } from '@angular/core';
import {FileUploadService,MessageService} from './file-upload.service';
@Component({
selector: 'app-file-upload',
templateUrl: './file-upload.component.html',
styleUrls: ['./file-upload.component.css']
})
export class FileUploadComponent implements OnInit {
messages:string[];
constructor(private fileUpload:FileUploadService,private message:MessageService) {
this.messages = message.messages;
}
ngOnInit() {
}
pick(ele:HTMLInputElement){
let file:File = ele.files[0];
this.fileUpload.upload(file).subscribe(e=>alert(e));
}
}
template
<input type="file" #upload />
<input type="button" value="upload" (click)="pick(upload)">
<ul>
<li *ngFor="let message of messages">{{message}}</li>
</ul>
后台比较简单:
@PostMapping("/upload")
public Map<String,String> upload(@RequestParam("file") Part file) {
return new HashMap<String,String>(){{
put("file",file.getSubmittedFileName());
}};
}
但是由于上传的文件比较大,需要加上application.properties中的配置
spring.servlet.multipart.max-file-size=300MB
spring.servlet.multipart.enabled=true
spring.servlet.multipart.maxRequestSize=330MB
最后由于angular的服务器和springboot的服务器不在同一个端口上运行,需要解决跨域问题,为此写一个filter
@Component
//标记问filter的注解
@WebFilter
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request1 = (HttpServletRequest)request;
HttpServletResponse response1 = (HttpServletResponse) response;
response1.setHeader("Access-Control-Allow-Origin", "*");
response1.setHeader("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, DELETE,PUT");
response1.setHeader("Access-Control-Max-Age", "3600");
response1.setHeader("Access-Control-Allow-Headers","*");
// "Content-Type, x-requested-with, X-Custom-Header, Authorization");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
最后附上network