前端对接阿里OSS

公司最近须要使用阿里OSS上传文件,可是文件类型不固定,开始的想法是经过Java写接口,若是文件过大由后端对文件进行分片处理并上传。在调研过程当中发现ali-oss公共模块中提供了分片上传的方法,因此找个工做就交给了前端来作,以减轻后端的压力,因而笔者就开始了漫长的调研过程。javascript

初期考虑只是简单的实现就好,可是为了之后方便维护以及复用状况笔者考虑,使用class进行封装处理。html

使用技术栈:前端

  1. vue-cli 3.0
  2. typescript

依赖:vue

  1. ali-oss
  2. Element-ui(可忽略)

测试用html结构以下(使用了element-ui)java

<div class="home">
    <input type="file" multiple='true' @change="onFileChange" id="file">
    <el-button @click="upload">普通上传</el-button>
    <el-button @click="multipartUpload">分片上传</el-button>
    <el-button @click="stop">中止上传</el-button>
    <el-button @click="resume">中断续传</el-button>
    <el-progress :percentage="percentage"></el-progress>
</div>

前端直接对接阿里oss须要使用ali-oss公共包,执行一下命令vue-cli

npm install --save-dev ali-oss

建立文件DockingOSS.tstypescript

class DockingOSS {

}

因为依赖于ali-oss,要考虑到ali-oss在初始化时所须要的参数,封装类时所须要的参数,因为使用typescript就不能再让参数随意填写,而是使用接口对参数进行规范化处理。npm

interface allOssInterface {
    region:string;  //  地域节点,必填
    accessKeyId:string; //  用户id,必填
    accessKeySecret:string; //  访问密钥,必填
    bucket:string;  //  bucket名称,qjdev-pred-voices
    path?:string;   //  路径,默认为"",用户长传到指定文件夹
    secure?:Boolean;    //  指示OSS客户端使用 HTTPS:true HTTP:false
    parallel?:number;   //  分片数量
    partSize?:number;   //  分片大小
    defaultName?:Boolean;   //  是否使用默认名称
    length?:number; //  随机名称长度
};

对其进行初始化element-ui

class DockingOSS {

    //  ali-oss 实例
    private allOSS:any;
    private parallel:number;
    private partSize:number;
    private defaultName:Boolean;
    private path:string;
    private length:number;

    constructor(data:allOssInterface){
        let {region,
            accessKeyId,
            accessKeySecret,
            bucket,
            secure = true,
            parallel = 3,
            partSize = 1024 * 1024,
            defaultName = false,
            path = "",
            length = 50} = data;
        this.partSize = partSize;
        this.parallel = parallel;
        this.defaultName = defaultName;
        this.path = path;
        this.length = length;
        //  实例化ali-oss
        this.allOSS = new AliOss({region,accessKeyId,accessKeySecret,bucket,secure});
    }
    
}

添加普通上传方法,处于考虑到开发者可能须要把文件上传到不一样的文件夹,以及会使用随机文件名称或者使用固定文件名称,定义了两个方法用来处理上传路径和文件名称。后端

class DockingOSS { 

    /**
     * 普通上传
     * file:文件对象
     * _fileName:固定文件名称
     */
    public upload(file:File,_fileName:string = ""):Promise<any>{
        let fileName = _fileName;
        (!fileName) && (fileName = this.getFileName(file));
        const pathName = this.accessPath(fileName);
        return this.allOSS.put(pathName, file)
    }
    
}

添加获取路径和随机名称方法

class DockingOSS { 

    //  获取名称
    private getFileName(file:File):string{
        let {defaultName} = this;
        const fileName:string = file.name;
        if(defaultName) return fileName;
        return this.randomFileName();
    }

    //  随机文件名称
    private randomFileName():string{
        const data = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F",
                        "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
                        "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r",
                        "s", "t", "u", "v", "w", "x", "y", "z"];
        let nums = "";
        const {length} = this;
        for (let i = 0; i < length; i++) {
            const randomStr:string = (Math.random()*61).toString()
            const r:number = parseInt(randomStr, 10);
            nums += (data[r]).toString();
        }
        return nums;
    }
    
}

添加分片上传上传方法

class DockingOSS { 

    /**
     * 分片上传
     * file:文件对象
     * _fileName:固定文件名称
     * progress:分片上传进度回调函数
     */
    public multipartUpload(file:File, progress:Function,_fileName:string):Promise<void>{
        const {parallel,partSize} = this;
        let fileName = _fileName;
        (!fileName) && (fileName = this.getFileName(file));
        const pathName = this.accessPath(fileName);
        return this.allOSS.multipartUpload(pathName, file, {
            parallel,
            partSize,
            progress
        })
    }
    
}

添加停止上传方法

class DockingOSS { 

    //  停止上传
    public cancel():void{
        this.allOSS.cancel();
    }

}

添加续传方法,因为在续传时须要接收一些参数,须要从中获取到停止上传的文件对象,使用interface对参数进行规范化。

interface checkpointInterface {
    file:File;
    name:string;
    fileSize:number;
    partSize:number;
    uploadId:string;
};

class DockingOSS { 

    /**
     * 分片续传
     * checkpoint:中断上传的文件
     * progress:进度回调函数
     */
    public resume(checkpoint:checkpointInterface, progress:Function):Promise<void>{
        const { uploadId, file } = checkpoint;
        const {parallel,partSize,path} = this;
        return this.allOSS.multipartUpload(uploadId, file, {
            parallel,
            partSize,
            progress,
            checkpoint
        })
    }

}

简易封装就完成了,虽然封装不算太完善可是仍是能够知足大部分项目需求的,在应用过程当中,可能会不少地方用到该类,能够在类中添加单例,已保证整个项目中只存在一个实例,减小对内存的占用(提升性能从小事开始作起)。

实战应用:

<template>
  <div class="home">
    <input type="file" multiple='true' @change="onFileChange" id="file">
    <el-button @click="upload">普通上传</el-button>
    <el-button @click="multipartUpload">分片上传</el-button>
    <el-button @click="stop">中止上传</el-button>
    <el-button @click="resume">中断续传</el-button>
    <el-progress :percentage="percentage"></el-progress>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import AliOss from "ali-oss";

import DockingOSS from '@/assets/DockingOSS';

let dockOss = new DockingOSS({  
  //  地域节点
  region:"*********",
  //  id
  accessKeyId:"************",
  //  访问密钥
  accessKeySecret:"***********",
  //  bucket
  bucket:"***********",
  path:"***********",
  secure: true
})

@Component
export default class HelloWorld extends Vue {
  //    中断上传存储文件内容
  private checkpoints:object = {};
  private percentage:number = 0;
  private filesArr:File[] = [];

  //  文件更改
  private onFileChange(e:Event):void{
    const target = Reflect.get(e,"target");
    const value = Reflect.get(target,"value");
    const tmpcnt = value.lastIndexOf(".")
    const exname = value.substring(tmpcnt + 1)
    const oFile = document.getElementById("file") || document.createElement("input");
    const files:File[] = Reflect.get(oFile,"files");
    const fileList = Array.from(files);
    this.filesArr = fileList;
  }

  //  分片上传
  private multipartUpload() {
    this.percentage = 0;
    this.filesArr.forEach((file:File) => {
        //  当中止上传时须要也会走向catch
        //  错误提示:{status: 0, name: "cancel"}
        //  须要特殊处理
        dockOss.multipartUpload(file,this.onMultipartUploadProgress)
    });
  }

  //  上传进度
  //  checkpoint:返回的文件对象
  //  若是文件过小,小于分片大小的话,则不会checkpoint,为undefined
  //  说明文件直接上传成功了
  private onMultipartUploadProgress(e:number,checkpoint:any){
    Reflect.set(this.checkpoints,checkpoint.uploadId,checkpoint)
    this.percentage = Number((e * 100).toFixed(0));
    if(e === 1){
      Reflect.deleteProperty(this.checkpoints,checkpoint.uploadId);
    }
  }

  //  普通上传
  private upload():void{
    this.filesArr.forEach((file:File) => {
      dockOss.upload(file);
    })
  }

  //  停止上传
  private stop():void{
    dockOss.cancel()
  }

  //  续传
  private resume():void{
    Object.values(this.checkpoints).forEach((checkpoint) => {
      dockOss.resume(checkpoint,this.onMultipartUploadProgress)
    });
  }

}
</script>

对接阿里oss也是没那么困难的,仍是想记录一下,毕竟不用每次都须要翻阅资料了。

须要说明的一点,为何没有直接使用element-ui的上传组件,为了节省oss空间,以及防止用户误传文件,因此使用这种方式在用户提交数据时,进行文件上传,这样会更好一些。

记录生活分享技术,共同进步共同成长。若是文章中由什么错误,请在评论出提出指正,我会及时作出修改。

相关文章
相关标签/搜索