后端API接口项目,请参考local-server 项目,使用Spring boot框架实现本地存储服务。
前端使用Angular + ng-zorro实现,项目源码在这里Gitee
模型(model)
需要定义文件元数据(metadata)模型,描述文件信息, 在local.model.ts中定义
export class Metadata extends BaseEntity {
createdBy!: string;
createdDate!: Date;
name!: string;
path!: string;
contentType!: string;
size!: number;
constructor(data?: Partial<Metadata>) {
super(data);
Object.assign(this, data);
if (data?.createdDate != undefined) {
this.createdDate = new Date(data.createdDate);
}
}
}
元数据ID在BaseEntity对象中定义。此模型定义了名称,路径,类型,大小等关键信息
服务(Service)
local.service.ts
@Injectable({ providedIn: 'root' })
export class LocalService {
private baseFileUrl = '/oss/v1/files';
private baseMetadataUrl = '/oss/v1/metadatas';
private upload$ = new Subject<Metadata>();
private remove$ = new Subject<Metadata>();
constructor(private http: BaseHttpService) {}
public get onUpload(): Observable<Metadata> {
return this.upload$.asObservable();
}
public get onRemove(): Observable<Metadata> {
return this.remove$.asObservable();
}
uploadEvent(formData: FormData): Observable<HttpEvent<any>> {
return this.http
.post<any>(`${this.baseFileUrl}/upload`, formData, null, {
observe: 'events',
reportProgress: true
})
.pipe(
tap(e => {
if (e instanceof HttpResponse) {
this.upload$.next(new Metadata(e.body));
}
})
);
}
upload(formData: FormData): Observable<Metadata> {
return this.http.post<Metadata>(`${this.baseFileUrl}/upload/`, formData).pipe(
map(e => new Metadata(e)),
tap(e => {
this.upload$.next(e);
})
);
}
download(metadataId: string): Observable<Blob> {
return this.http.get(`${this.baseFileUrl}/download/${metadataId}`, null, { responseType: 'blob' });
}
downloads(metadataIds: string[]): Observable<Blob> {
const queryParams = { metadataIds: metadataIds };
return this.http.get(`${this.baseFileUrl}/downloads/`, queryParams, { responseType: 'blob' });
}
delete(metadataId: string): Observable<any> {
const url = `${this.baseFileUrl}/${metadataId}`;
return this.http.delete(url).pipe(
tap(e => {
this.remove$.next(new Metadata(e));
})
);
}
public getMetadataById(id: string): Observable<Metadata> {
return this.http.get<Metadata>(`${this.baseMetadataUrl}/${id}`).pipe(map(data => new Metadata(data)));
}
public getMetadataByIds(ids: string[]): Observable<Metadata[]> {
const queryParams = { ids: ids };
return this.http
.get<Metadata[]>(`${this.baseMetadataUrl}/ids`, queryParams)
.pipe(map(data => data.map(e => new Metadata(e))));
}
public updateMetadataById(id: string, name: string): Observable<Metadata> {
const queryParams = { name: name };
return this.http.get<Metadata>(`${this.baseMetadataUrl}/${id}`, queryParams).pipe(map(e => new Metadata(e)));
}
}
服务中有上传,下载,查询元数据的方法。
如何显示文件下载进度
在这里使用了HttpEvent返回对象,local-upload.component.ts文件部分类容:
this.localSrc
.uploadEvent(formData)
.pipe(takeUntil(fileUploadInfo.onCancelRequest))
.subscribe({
next: (e: any) => {
if (e.type === HttpEventType.UploadProgress) {
fileUploadInfo.percent = Math.round((100 * e.loaded) / e.total);
} else if (e instanceof HttpResponse) {
fileUploadInfo.state = 'success';
fileUploadInfo.isUpload = false;
fileUploadInfo.metadata = new Metadata(e.body);
this.uploadEvent.emit(fileUploadInfo.metadata);
}
},
error: (err: any) => {
fileUploadInfo.state = 'error';
fileUploadInfo.isUpload = false;
}
});
if (e.type === HttpEventType.UploadProgress) 判断是否是进度消息
pipe(takeUntil(fileUploadInfo.onCancelRequest)) 实现上传文件过程取消功能
文件上传界面
界面使用ng-zorro的组件nz-upload实现,local-upload.component.html文件内容:
<nz-space>
<nz-upload *nzSpaceItem [nzMultiple]="true" [nzBeforeUpload]="beforeUpload">
<button nz-button>
<span nz-icon nzType="upload"></span>
选择上传文件...
</button>
</nz-upload>
</nz-space>
<nz-list nzGrid>
<div nz-row nzGutter="8">
<div nz-col nzXs="24" nzSm="24" nzMd="12" nzLg="6" nzXl="6" *ngFor="let item of fileUploadList">
<nz-list-item>
<nz-card [nzTitle]="titleTpl">
<div *ngIf="item.metadata.id">
<nz-descriptions [nzColumn]="1">
<nz-descriptions-item nzTitle="名称">{{ item.metadata.name }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="大小">{{ item.metadata.size || 0 | bytes: 0 }}</nz-descriptions-item>
<nz-descriptions-item nzTitle="类型">
{{ item.metadata.contentType | shorten: 40:'...' }}
</nz-descriptions-item>
<nz-descriptions-item nzTitle="创建日期">
{{ item.metadata.createdDate | date: 'yyyy-MM-dd HH:mm' }}
</nz-descriptions-item>
</nz-descriptions>
</div>
<div *ngIf="item.isUpload && !item.metadata.id">
<nz-progress [nzPercent]="item.percent"></nz-progress>
</div>
<div nz-row nzJustify="center">
<div *ngIf="item.isUpload" nz-col>
<a (click)="cancel(item)">取消上传</a>
</div>
<div *ngIf="!item.isUpload && item.metadata.id === undefined">
<a (click)="continue(item)">重新上传</a>
</div>
<div *ngIf="item.metadata.id">
<a (click)="view(item)">查看</a>
<nz-divider nzType="vertical"></nz-divider>
<a (click)="rename(item)">重命名</a>
</div>
<div *ngIf="!item.isUpload">
<nz-divider nzType="vertical"></nz-divider>
<a (click)="delete(item)">删除</a>
</div>
</div>
</nz-card>
<ng-template #titleTpl>
<nz-tag nzColor="warning" *ngIf="item.state === 'cancel'">
<span nz-icon nzType="exclamation" *ngIf="item.state === 'cancel'"></span>
</nz-tag>
<nz-tag nzColor="success" *ngIf="item.state === 'success'">
<span nz-icon nzType="check-circle"></span>
</nz-tag>
<nz-tag nzColor="processing" *ngIf="item.state === 'processing'">
<span nz-icon nzType="sync"></span>
</nz-tag>
<nz-tag nzColor="error" *ngIf="item.state === 'error'">
<span nz-icon nzType="close-circle"></span>
</nz-tag>
{{ item.file.name }}
</ng-template>
</nz-list-item>
</div>
</div>
<nz-list-empty *ngIf="fileUploadList.length === 0"></nz-list-empty>
</nz-list>
功能截图
文件上传
元数据查询