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 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。
这个只是实现了基本的上传,后续扩展为带预览上传、多图上传等。