公司最近需要使用阿里OSS
上传文件,但是文件类型不固定,开始的想法是通过Java
写接口,如果文件过大由后端对文件进行分片处理并上传。在调研过程中发现ali-oss
公共模块中提供了分片上传的方法,所以找个工作就交给了前端来做,以减轻后端的压力,于是笔者就开始了漫长的调研过程。
初期考虑只是简单的实现就好,但是为了以后方便维护以及复用情况笔者考虑,使用class
进行封装处理。
使用技术栈:
- vue-cli 3.0
- typescript
依赖:
- ali-oss
- Element-ui(可忽略)
测试用html
结构如下(使用了element-ui
)
<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
公共包,执行一下命令
npm install --save-dev ali-oss
创建文件DockingOSS.ts
class DockingOSS {
}
由于依赖于ali-oss
,要考虑到ali-oss
在初始化时所需要的参数,封装类时所需要的参数,由于使用typescript
就不能再让参数随意填写,而是使用接口对参数进行规范化处理。
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; // 随机名称长度
};
对其进行初始化
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 accessPath(fileName:string):string {
const {path} = this;
return path?`${path}/${fileName}`:fileName;
}
// 获取名称
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空间,以及防止用户误传文件,所以使用这种方式在用户提交数据时,进行文件上传,这样会更好一些。
记录生活分享技术,共同进步共同成长。如果文章中由什么错误,请在评论出提出指正,我会及时做出修改。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。