angular上传图片到七牛云。
前端使用七牛的Javascript SD,后端因为我用的是Midway.js
node框架,所以后端使用七牛的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 13
| 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};
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; 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来触发input
的click
事件。
input 元素的accept
属性为input[type=file]
原生具有的属性,我们可以控制选择文件的类型。比如,我们希望限制为仅图片类型:accept = 'images/*'
,也可以设置为可以接受文档或xml类型accetp = ".doc, .docx, .xml"
。
我们给input
的change
事件绑上了我们的处理方法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: {}, 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。
这个只是实现了基本的上传,后续扩展为带预览上传、多图上传等。