0%

angular结合midway.js 七牛SDK上传图片

angular上传图片到七牛云。

前端使用七牛的Javascript SD,后端因为我用的是Midway.jsnode框架,所以后端使用七牛的Node.js SDK

首先,需要去七牛云的个人中心 -> 密钥管理,创建密钥,然后将AK(AccessKey)SK(SecretKey)保存下来,以备后用。

在服务端生成token

使用七牛的NodeJS SDK

首先,将我们前面获得的AK和SK存放在Midway的配置文件中:

1
2
3
4
5
6
7
8
9
10
11
12
import { EggAppConfig, PowerPartial } from "egg";

export = (appInfo: any) => {
const config: PowerPartial<EggAppConfig> = {};

// 七牛的配置
config.qiniu = {
accessKey: '*****************',
secretKey: '*****************',
};
return config;
};

创建一个七牛的service,提供一个getToken(bucket)方法,用来生成token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import {provide, config} from 'midway';
import * as qiniu from 'qiniu';
import { IQiniuService } from "../../interface";

@provide()
export class QiniuService implements IQiniuService{

@config('qiniu')
qiniuConfig: {accessKey: string, secretKey: string};

/**
* 根据bucket生成token
* @param bucket
*/
async getToken(bucket: string): Promise<string> {
const mac = new qiniu.auth.digest.Mac(this.qiniuConfig.accessKey, this.qiniuConfig.secretKey);
const options = {
scope: bucket,
};
const putPolic = new qiniu.rs.PutPolicy(options);
return putPolic.uploadToken(mac);
}
}

创建一个controller,暴露一个路由给我们前端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { controller, get, provide, inject, IApplicationLocals} from 'midway';
import { Response } from '../../lib/util/response';
import { CodeEnum } from '../../lib/enum/code.enum';
import { IQiniuService } from "../../interface";

@provide()
@controller('/qiniu')
export class QiniuController {

@inject()
qiniuService: IQiniuService;

@get('/token')
async qiniu(ctx: IApplicationLocals) {
const {bucket} = ctx.query;
// 七牛云上的Bucket空间名称
if (!bucket) {
return ctx.body = Response(CodeEnum.PARAMS_ERROR)
}

const token = await this.qiniuService.getToken(bucket);

ctx.body = Response(CodeEnum.SUCCESS, {
token,
});
}
}

我们的api请求路径是:https://domain/qiniu/token

PS:注意做好访问权限控制,不要泄露了token。

前端上传

解决思路

先不考虑其他复杂的情况,我们先来第一步上传,思路是这样:页面上一个按钮叫上传图片,点击按钮,打开选择图片的弹框,然后选好就直接上传,我们只需要拿到上传成功的图片的地址即可。

html内容

构造上传表单,我们需要的很简单,一个按钮,一个隐藏的表单域:

1
2
3
4
5
6
7
8
9
<input #fileUpload
type="file"
id="file-upload"
name="fileUpload"
[accept]="accept"
(change)="fileUploadChange()"
style="display: none"/>

<button mat-button (click)="fileUploadClick()">上传图片</button>

button 的fileUploadClick()方法的作用是,当用户点击上传图片的按钮时候,触发隐藏input的点击事件,达到可以选择图片的效果。

input元素添加模板变量#fileUpload是为了能够在ts中控制模板元素,让我们能够通过ts来触发inputclick事件。

input 元素的accept属性为input[type=file]原生具有的属性,我们可以控制选择文件的类型。比如,我们希望限制为仅图片类型:accept = 'images/*',也可以设置为可以接受文档或xml类型accetp = ".doc, .docx, .xml"

我们给inputchange事件绑上了我们的处理方法fileUploadChange()。当input元素选择好元素后,会触发这个事件。

ts内容

首先我们需要去获得上传的凭证token,所以在进入页面的时候,调用我们的接口:https://domain/qiniu/token,获得token:

1
2
3
4
5
6
7
8
9
10
ngOnInit() {
this.getQiniuToken();
}
getQiniuToken() {
this._commonApi.getQiniuToken().subscribe(res => {
if (res.code === ErrorCodeEnum.SUCCESS) {
this.qiniuToken = res.data.token;
}
});
}

我们需要在页面加载好后获得input元素,让我们可以通过ts来主动控制:

1
2
3
4
5
6
7
8
9
10
@ViewChild('fileUpload', {static: false}) fileUploadEl: ElementRef;
private _fileUpload: HTMLInputElement;

ngAfterViewInit(): void {
this._fileUpload = this.fileUploadEl.nativeElement;
}

fileUploadClick() {
this._fileUpload.click();
}

这样,我们可以通过this._fileUpload.click()来触发input的点击事件,当点击按钮的时候,我们会执行fielUploadClick()方法调用this._fileUpload.click()来调起input的选择文件的弹框。

选择好后会触发input的change()事件,而这个事件会触发我们的fileUploadChange()方法,来看这个方法:

1
2
3
4
5
6
7
8
9
10
11
fileUploadChange() {
if (this._fileUpload.files && this._fileUpload.files.length) {
this.handleUpload(this._fileUpload.files[0]).subscribe(res => {
this.fileUploadPercent = res.percent;
if (res.percent >= 100) {
this.imageList.push(QiniuDCN.says + res.name);
this.fileUploadProgressBar = false;
}
});
}
}

这个方法里面会先检查是否有文件files。如果有的话就执行了一个方法handleUpload方法,在这个方法执行成功后再运行回调函数。所以我们的上传图片的核心逻辑就在这个handleUpload方法里面。

handleUpload()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import * as qiniu from 'qiniu-js';

handleUpload (file): Subject<{ percent: number, name: string }> {
if (!this.qiniuToken) {
return;
}
const config = {
useCdnDomain: true,
};
const putExtra = {
// 文件原名
fname: '',
// 放置自定义变量
params: {},
// 限定上传文件类型,指定null时自动判断文件类型。["image/png", "image/jpeg", "image/gif"]
mimeType: ['image/png', 'image/png', 'image/jpeg', 'image/gif'],
};

// 上传到七牛的文件名称
const name = 'say-say_' + new Date().getTime().toString() + '.' + file.type.split('/')[1];
const subject: Subject<{ percent: number, name: string }> = new Subject();
this.fileUploadProgressBar = true;
qiniu.upload(file, name, this.qiniuToken, putExtra, config).subscribe((res: IQiNiuUploadResult) => {
subject.next({percent: res.total.percent, name});
if (res.total && res.total.percent >= 100) {
subject.complete();
}
}, err => {
subject.complete();
this.fileUploadProgressBar = false;
});
return subject;
}

我们在这个方法中返回了一个Subject,用以更新上传进度。

这里调用了七牛的SDK上传图片,调用的接口过程中会不断返回过来上传的进度。我们创建了一个Subject来将上传进度传递出去,以便在subscribe的时候监听上传进度。当出错或上传进度完成的时候就结束这个Observable。


这个只是实现了基本的上传,后续扩展为带预览上传、多图上传等。