Vue使用AWS s3进行大文件的分片上传、断点续传、下载(将文件上传进度显示)
这篇博客已经将这些aws S3的Api集成为了一个js文件,直接引用就可以了,就不需要这么复杂
将AWS S3大文件文件上传相关的API集成为js文件,功能包括 多文件并行上传、文件分片上传、断点续传、文件分片合成、上传暂停、取消上传、文件上传进度条显示_初八不乖的博客-CSDN博客
效果
流程
AWS S3的官方文档(GetObject - Amazon Simple Storage Service)
一、上传文件
1. npm下载 aws-sdk
npm install @aws-sdk/client-s3
2.将aws-sdk集成到vue中
const {
S3Client,
CreateMultipartUploadCommand,
ListMultipartUploadsCommand,
GetObjectCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand
} = require("@aws-sdk/client-s3");
//这里显示的是后面要用到的s3中的方法,具体怎么使用,后面会讲到
3.向后端获上传文件所需要的信息
(存储文件的bucket,存储节点(endPoint也就是存储服务器的ip)、accessKey(访问用户名)、secretKey(访问密码))
这一步也可以直接在前端获取这些信息(如何获取这些信息请参照博客:minio使用案例(Springboot)_子午谷的博客-CSDN博客_minio代理)
我这里是已经创建了bucket了,如果没有创建bucket要先创建
创建方法:
注意:endpoint不要加端口,有默认端口(https,http协议端口不同),如果需要转换端口,可以考虑使用ngnix,如果在这里加了端口,会报错
var s3 = new S3Client({
endpoint: "https://" + s3Information.endPoint,//存储文件的服务器的地址,无端口
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: "",//访问登录名
secretAccessKey: "",//访问密码
}
});
var params = {
Bucket: "examplebucket"//bucket名,任意取
};
s3.CreateBucketCommand(params, function(err, data) {
if (err) console.log(err, err.stack); // 错误
else console.log(data); // 成功
});
4.建立连接
在每一个文件上传时,都要先与服务器建立连接,并获取唯一的uploadId
//建立连接
//key标识文件的名称
//type为文件的类型
//s3为创建bucket时建立的S3Client
async createMultipartUpload(bucket, key, s3, type) {// string, string
const params = {
Bucket: bucket,
Key: key,
ContentType: type
};
const res = async () => {
try {
const data = await s3.send(new CreateMultipartUploadCommand(params));
return data;
} catch (err) {
console.log('建立连接失败:', err.message)
return 1;
}
}
return res()
},
5.选择文件,并将文件切片,分段上传
注意:s3分片上传文件时,只有最后一个分片的文件可以小于5M,其余分片必须大于等于5M,否则会报错(Your proposed upload is smaller than the minimum allowed object size)
阶段所使用的主要方法和步骤
- 文件分片
const chunkSize = 5 * 1024 * 1024;//定义分片的大小 为5M 采用分片上传时,只能并且只有有最后一个分片的size 小于 指定值(默认5M),不然就会报错
const chunkCount = Math.ceil(this.fileList[i].files.size / chunkSize)//分片数
for (let j = 0; j < chunkCount; j++) {
let start = j * chunkSize;
let end = Math.min(this.fileList[i].files.size, start + chunkSize)
let _chunkFile = this.fileList[i].files.slice(start, end)//分片文件
}
- 文件分片上传至bucket
//上传一个分片
//f为一个文件分片
//uploadId 为建立连接时返回的唯一id
///key为文件名
//num为第几个分片
//s3为S3Client
async uploadPart(f, uploadId, key, bucket, num, s3) {
const params = {
Bucket: bucket,
Key: key,
PartNumber: num,
UploadId: uploadId,
Body: f
};
const res = async () => {
try {
const data = await s3.send(new UploadPartCommand(params));
return data;
} catch (err) {
// return alert("There was an error listing your albums: " + err.message);
console.log('上传分片错误信息', err.message)
return 1;
}
}
return res();
},
6.文件分片均上传成功后,要对分片进行合并
注意:在分片上传完成,并且合并之后,系统会自动断开连接;如果是发异常,如上传分片失败等,需要手动断开连接
//将分片合并
//parts是一个数组,形式为{ ETag: 分片上传成功后返回的唯一标识, PartNumber: 第几个分片 }
async completeMultipartUpload(bucket, key, parts, uploadId, s3) {
const params = {
Bucket: bucket,
Key: key,
MultipartUpload: {
Parts: parts
},
UploadId: uploadId
};
const res = async () => {
try {
const data = await s3.send(new CompleteMultipartUploadCommand(params));
return data
} catch (err) {
console.log("合并分片失败: ", err.message);
return 1
}
}
return res()
},
//取消连接
async abortMultipartUpload(bucket, key, uploadId, s3) {
const params = {
Bucket: bucket,
Key: key,
UploadId: uploadId
};
const res = async () => {
try {
const data = await s3.send(new AbortMultipartUploadCommand(params));
return data
} catch (err) {
console.log("取消连接失败: " + err.message);
return 1
}
}
return res()
},
这个时候,文件就上传完成啦
二、下载文件
1.下载插件
我们使用s3的getObject方法获取文件时,获取的是一个readableStream流,所以要使用插件转换成blob文件进行下载
npm i -S binconv
2.下载
//文件下载
async downLoad(row) {
let key = row.fid;
let s3Information = {
accessKey: "",
bucket: "",
endPoint: "",
secretKey: ""
}
//1、判断在bucket中是否存在该文件
let param = {
fid: key
}
//getDownloadUrl这个方法是为了像后端获取用户上传的bucket等信息,这些都可以在前端完成
await getDownloadUrl(param).then(res => {
if (res.data.hasOwnProperty("returnCode")) {
if (res.data.returnCode == 0) {
s3Information = res.data;
} else {
return this.$message.error(res.data.errMessage);
}
} else {
return this.$message.error("错误请求");
}
}).catch(err => {
return this.$message.error("获取文件下载信息过程错误:" + err)
})
var s3 = new S3Client({
endpoint: "https://" + s3Information.endPoint,
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: s3Information.accessKey,
secretAccessKey: s3Information.secretKey,
}
});
let isExistence = await this.getObject(s3Information.bucket, key, s3, row.name)
if (isExistence == 1) {
return this.$message.error("bucket中不存在该文件");
}
},
放上全部代码
1、前端页面
<template>
<div>
<!-- 搜索 -->
<div style="margin-top:20px;">
<input v-show="false" ref="fileRef" multiple type="file" @change="fileChange">
<el-button style="float:left" type="primary" icon="el-icon-upload" size="medium" @click="uploadFile">上传媒体文件
</el-button>
<el-input size="large" placeholder="请输入内容" v-model="fileName"
style="width: 500px;margin-bottom: 20px; margin-left: 50px;">
<template slot="prepend">文件名</template>
<el-button slot="append" icon="el-icon-search" size="small" @click="search"></el-button>
</el-input>
</div>
<!-- 上传文件弹框 -->
<div>
<el-dialog title="上传文件" :visible.sync="dialogFormVisible" width="40%" :before-close="fileClose">
<div style="width: 100%;height: 500px;overflow: hidden;overflow-y: scroll;">
<div v-for="item in fileList" :key="item.name">
<div v-if="item.appear == true" style="width:100%;display: table;">
<div style="display:table-cell;vertical-align:middle;text-align: center;width: 15%;">
<img src="../../../assets/file.png" style="display:inline-block;" width="50px"
height="60px" />
</div>
<div style="display:table-cell;width: 70%;">
<div style="font-weight:700;font-size:medium">{{item.files.name}}</div>
<div>
<el-progress :percentage="item.percentage" :color="item.color"></el-progress>
</div>
<div>
<span v-if="item.err == true" style="color:#F56C6C">{{item.meassage}}</span>
<span v-if="item.succ == true" style="color:#67C23A">{{item.meassage}}</span>
</div>
</div>
<div style="vertical-align:middle;text-align: center;display:table-cell;width: 15%;">
<img @click="stop(item)" v-if="item.show == 0"
style="display:inline-block;cursor: pointer;" src="../../../assets/stop.png"
width="30px" height="30px">
<img @click="continued(item)" v-if="item.show == 1"
style="display:inline-block;cursor: pointer;" src="../../../assets/continue.png"
width="30px" height="30px">
<img @click="deleted(item)" v-if="item.show == 1"
style="display:inline-block;margin-left: 10px;cursor: pointer;"
src="../../../assets/delete.png" width="30px" height="30px">
<img @click="continued(item)" v-if="item.show == 2 || item.show == 4"
style="display:inline-block;cursor: pointer;" src="../../../assets/retry.png"
width="30px" height="30px">
</div>
</div>
<el-divider v-if="item.appear == true" style="margin-top:0%"></el-divider>
</div>
</div>
</el-dialog>
</div>
<!-- 表格 -->
<div>
<el-table :data="tableData" style="width: 100%;font-size: 10px" @sort-change="change"
@filter-change="filterChange">
<el-table-column prop="fid" label="文件" width="300px">
<template slot-scope="scope">
<el-link style="font-size: 10px" @click="detail(scope.row)">{{ scope.row.fid }}</el-link>
</template>
</el-table-column>
<el-table-column prop="name" label="文件名" width="350px" :show-overflow-tooltip=true>
</el-table-column>
<el-table-column prop="size" label="大小" width="150px"></el-table-column>
<el-table-column prop="transcoding" label="转码生成文件" width="120px"
:filters="[{ text: '否', value: false }, { text: '是', value: true }]" column-key="filterTag">
<template slot-scope="scope">
<span v-if="scope.row.transcoding === true" style="color:#409EFF">是</span>
<span v-if="scope.row.transcoding === false">否</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" sortable="custom" :formatter="tableColumnFormatTime">
</el-table-column>
<el-table-column prop="expireTime" label="过期时间" :formatter="tableColumnFormatTime"></el-table-column>
<el-table-column label="操作" width="150px">
<template slot-scope="scope">
<el-button type="text" style="color:#E6A23C" @click="detail(scope.row)" size="small">详情
</el-button>
<el-button type="text" style="color:#409EFF" @click="downLoad(scope.row)" size="small"> 下载
</el-button>
<el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-delete"
icon-color="red" title="确定删除此媒体文件吗?" @confirm="deleted(scope.row)" style="margin-left:10px">
<el-button type="text" style="color:#F56C6C" slot="reference" :disabled="!scope.row.status"
size="small">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div style="margin-top:20px;float: right;">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[5, 10, 20, 30]" :page-size='pageSize'
layout="total, sizes, prev, pager, next, jumper" :total='total'>
</el-pagination>
</div>
<!-- 详情弹窗 -->
<div>
<el-dialog :visible.sync="dialogTableVisible" v-dialogDrag>
<template #title>
<div>
<span style="font-size:20px;line-height:24px;color: #303133;">文件详情</span><br><br>
<span style="font-size:xx-small;color:grey">提示:双击弹窗头部,可使弹窗全屏展示</span>
</div>
</template>
<el-descriptions title="" direction="vertical" :column="2" border>
<el-descriptions-item label="文件">{{ myDetails.fid }}</el-descriptions-item>
<el-descriptions-item label="文件名">{{ myDetails.name }}</el-descriptions-item>
<el-descriptions-item label="文件大小">{{ myDetails.size }}</el-descriptions-item>
<el-descriptions-item label="文件后缀">{{ myDetails.suffix }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ myDetails.creatAt }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag type="success" v-if="myDetails.status == 1">上传成功</el-tag>
<el-tag type="danger" v-if="myDetails.status == 2">待上传</el-tag>
<el-tag type="danger" v-if="myDetails.status == 0">待上传</el-tag>
<el-tag type="danger" v-if="myDetails.status == -1">已删除</el-tag>
<el-tag type="danger" v-if="myDetails.status == 3">已删除</el-tag>
<el-button type="text" style="color:#409EFF;margin-left: 20px;" @click="downLoad(myDetails)"
size="small" v-if="myDetails.status == 1"> 下载</el-button>
</el-descriptions-item>
<el-descriptions-item label="信息流">
<span style="white-space:pre-wrap">
{{ myDetails.meta }}
</span>
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
<!-- <div>
<button @click="test()">按钮</button>
</div> -->
</div>
</template>
<script>
import { getMediaFileList, getFileDetails, fileUpload, changeStatus, getDownloadUrl } from '../../../utils/api'
import SparkMD5 from "../../../components/spark-md5.min.js";
import * as binconv from 'binconv';
const chunkSize = 5 * 1024 * 1024;//定义分片的大小 为5M 采用分片上传时,只能并且只有有最后一个分片的size 小于 指定值(默认5M),不然就会报错
const {
S3Client,
CreateMultipartUploadCommand,
ListMultipartUploadsCommand,
GetObjectCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand
} = require("@aws-sdk/client-s3");
export default {
data() {
return {
tableData: [],
fileName: '',
order: false,
currentPage: 1,
pageSize: 10,
total: 0,
dialogTableVisible: false,
myDetails: {
id: "",
name: "",
size: "",
suffix: "",
md5: "",
meta: "",
creatAt: "",
status: ''
},
isScreen: 0,
dialogFormVisible: false,
fileList: [],//文件
errorFiles: [],//上传失败文件
}
},
watch: {
},
methods: {
//文件下载
async downLoad(row) {
let key = row.fid;
let s3Information = {
accessKey: "",
bucket: "",
endPoint: "",
secretKey: ""
}
//1、判断在bucket中是否存在该文件
let param = {
fid: key
}
await getDownloadUrl(param).then(res => {
if (res.data.hasOwnProperty("returnCode")) {
if (res.data.returnCode == 0) {
s3Information = res.data;
} else {
return this.$message.error(res.data.errMessage);
}
} else {
return this.$message.error("错误请求");
}
}).catch(err => {
return this.$message.error("获取文件下载信息过程错误:" + err)
})
var s3 = new S3Client({
endpoint: "https://" + s3Information.endPoint,
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: s3Information.accessKey,
secretAccessKey: s3Information.secretKey,
}
});
let isExistence = await this.getObject(s3Information.bucket, key, s3, row.name)
if (isExistence == 1) {
return this.$message.error("bucket中不存在该文件");
}
},
//判断在bucket中是否存在该文件
async getObject(bucket, key, s3, name) {
const params = {
Bucket: bucket,
Key: key
};
const res = async () => {
try {
const data = await s3.send(new GetObjectCommand(params));
//将readableStream 转换成blob
const blob = await binconv.readableStreamToBlob(data.Body);
var newBlob = new Blob([blob], { type: data.ContentType });
var elink = document.createElement('a');
elink.download = name;
elink.style.display = 'none';
const src = URL.createObjectURL(newBlob);
elink.href = src;
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
URL.revokeObjectURL(src)
return 0;
} catch (err) {
console.log("There was an error listing your albums: " + err.message);
return 1;
}
}
return res()
},
//点击选择文件
uploadFile() {
this.$refs.fileRef.dispatchEvent(new MouseEvent('click'))
},
//文件弹窗关闭
fileClose(done) {
for (let i = 0; i < this.fileList.length; i++) {
if (this.fileList[i].err == false && this.fileList[i].succ == false) {
return this.$message.warning("请等待所有文件处理完成后关闭弹窗");
}
}
this.fileList = [];
this.errorFiles = [];
done();
},
//删除还未上传成功文件
deleted(item) {
item.appear = false;
},
//继续上传
async continued(item) {
item.fid = '';
item.err = false;
item.succ = false;
item.message = "";
item.show = 0;
item.color = "#409EFF"
item.s3 = {
endPoint: '',
accessKeyId: '',
secretAccessKey: '',
bucket: ''
};
this.errorFiles.push(item);
//for (let i = 0; i < this.errorFiles.length; i++)
while (this.errorFiles.length != 0) {
let errFile = this.errorFiles.pop();
//1、获取bucket基本信息
let isOk = await this.inputFile(errFile);
//2、建立连接
if (isOk == 0) {
var s3 = new S3Client({
endpoint: errFile.s3.endPoint,
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: errFile.s3.accessKeyId,
secretAccessKey: errFile.s3.secretAccessKey,
}
});
var isConnect = await this.createMultipartUpload(errFile.s3.bucket, errFile.fid, s3, errFile.files.type)
if (isConnect != 1) {
//连接成功
/**
* 3、传送文件
* 3.1将文件分片
* 3.2分片文件循环上传
*/
const chunkCount = Math.ceil(errFile.files.size / chunkSize)//分片数
var isUpload = 0;//用来判断分片是否上传成功
var sharding = [];//成功分片信息
for (let j = 0; j < chunkCount; j++) {
let start = j * chunkSize;
let end = Math.min(errFile.files.size, start + chunkSize)
let _chunkFile = errFile.files.slice(start, end)
isUpload = await this.uploadPart(_chunkFile, isConnect.UploadId, errFile.fid, errFile.s3.bucket, j + 1, s3);
if (isUpload == 1 || errFile.show == 1) {
isUpload = 1;
//断开连接
await this.abortMultipartUpload(errFile.s3.bucket, errFile.fid, isConnect.UploadId, s3)
break;
} else {
//成功
//将分片信息存起来
errFile.percentage = Math.min(99, Math.ceil(errFile.percentage + 100 / chunkCount))
sharding.push({ ETag: isUpload.ETag, PartNumber: j + 1 })
}
}
if (isUpload == 1) {
//取消连接
//向用户输出文件上传失败,并将前端页面改变
errFile.err = true;
sharding = [];
if (errFile.show == 1) {
errFile.meassage = "暂停";
} else {
errFile.show = 2;
errFile.meassage = "上传分片失败";
}
errFile.color = "#F56C6C";
continue;
} else {
//4、合并分片
let isMerge = await this.completeMultipartUpload(errFile.s3.bucket, errFile.fid, sharding, isConnect.UploadId, s3);
if (isMerge == 1) {
//失败,将消息返回给页面
errFile.err = true;
errFile.show = 2;
errFile.color = "#F56C6C";
errFile.meassage = "合并分片失败";
continue;
} else {
//5、将合并成功信息传给后端
let success = await this.upLoadIsOk(errFile.fid);
if (success == 0) {
errFile.succ = true;
errFile.meassage = "上传成功";
errFile.show = 3;
errFile.color = "#67C23A";
errFile.percentage = 100;
await this.init();
} else {
errFile.err = true;
errFile.color = "#F56C6C";
errFile.show = 4;
errFile.meassage = success;
continue;
}
}
}
} else {
errFile.err = true;
errFile.show = 2;
errFile.color = "#F56C6C";
errFile.meassage = "与bucket建立连接失败";
continue;
}
} else {
errFile.err = true;
errFile.color = "#F56C6C";
errFile.meassage = isOk;
errFile.show = 2;
continue;
}
}
},
//暂停上传
stop(item) {
item.show = 1;
},
//文件上传
async fileChange(event) {
// 将上传文件传到文件列表
let files = event.target.files;
this.fileList = [];
for (let i = 0; i < files.length; i++) {
this.fileList.push({
appear: true, color: "#409EFF",
fid: '', percentage: 0, files: files[i], err: false, succ: false, message: "", show: 0, s3: {
endPoint: '',
accessKeyId: '',
secretAccessKey: '',
bucket: ''
}
})
}
this.dialogFormVisible = true;
for (let i = 0; i < this.fileList.length; i++) {
//1、获取bucket基本信息
let isOk = await this.inputFile(this.fileList[i]);
//2、建立连接
if (isOk == 0) {
var s3 = new S3Client({
endpoint: this.fileList[i].s3.endPoint,
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'us-east-1',
forcePathStyle: true,
credentials: {
accessKeyId: this.fileList[i].s3.accessKeyId,
secretAccessKey: this.fileList[i].s3.secretAccessKey,
}
});
var isConnect = await this.createMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, s3, this.fileList[i].files.type)
if (isConnect != 1) {
//连接成功
/**
* 3、传送文件
* 3.1将文件分片
* 3.2分片文件循环上传
*/
const chunkCount = Math.ceil(this.fileList[i].files.size / chunkSize)//分片数
var isUpload = 0;//用来判断分片是否上传成功
var sharding = [];//成功分片信息
for (let j = 0; j < chunkCount; j++) {
let start = j * chunkSize;
let end = Math.min(this.fileList[i].files.size, start + chunkSize)
let _chunkFile = this.fileList[i].files.slice(start, end)
isUpload = await this.uploadPart(_chunkFile, isConnect.UploadId, this.fileList[i].fid, this.fileList[i].s3.bucket, j + 1, s3);
if (isUpload == 1 || this.fileList[i].show == 1) {
isUpload = 1;
//断开连接
await this.abortMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, isConnect.UploadId, s3)
break;
} else {
//成功
//将分片信息存起来
this.fileList[i].percentage = Math.min(99, Math.ceil(this.fileList[i].percentage + 100 / chunkCount))
sharding.push({ ETag: isUpload.ETag, PartNumber: j + 1 })
}
}
if (isUpload == 1) {
//取消连接
//向用户输出文件上传失败,并将前端页面改变
this.fileList[i].err = true;
sharding = [];
if (this.fileList[i].show == 1) {
this.fileList[i].meassage = "暂停";
} else {
this.fileList[i].show = 2;
this.fileList[i].meassage = "上传分片失败";
}
this.fileList[i].color = "#F56C6C";
continue;
} else {
//4、合并分片
let isMerge = await this.completeMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, sharding, isConnect.UploadId, s3);
if (isMerge == 1) {
//失败,将消息返回给页面
this.fileList[i].err = true;
this.fileList[i].show = 2;
this.fileList[i].color = "#F56C6C";
this.fileList[i].meassage = "合并分片失败";
continue;
} else {
//5、将合并成功信息传给后端
let success = await this.upLoadIsOk(this.fileList[i].fid);
if (success == 0) {
this.fileList[i].succ = true;
this.fileList[i].meassage = "上传成功";
this.fileList[i].percentage = 100;
this.fileList[i].show = 3;
this.fileList[i].color = "#67C23A";
await this.init();
} else {
this.fileList[i].err = true;
this.fileList[i].show = 4;
this.fileList[i].color = "#F56C6C";
this.fileList[i].meassage = success;
continue;
}
}
}
} else {
this.fileList[i].err = true;
this.fileList[i].show = 2;
this.fileList[i].color = "#F56C6C";
this.fileList[i].meassage = "与bucket建立连接失败";
continue;
}
} else {
this.fileList[i].err = true;
this.fileList[i].meassage = isOk;
this.fileList[i].show = 2;
this.fileList[i].color = "#F56C6C";
continue;
}
}
this.$refs["fileRef"].value = '';
return
},
//通知后端,文件上传成功
async upLoadIsOk(fid) {
let success = 0;
let param = {
fid: fid
}
await changeStatus(param).then(res => {
if (res.data.hasOwnProperty("returnCode")) {
if (res.data.returnCode == 0) {
return success = 0;
} else {
this.$message.error(fid + res.data.errMessage);
return success = res.data.errMessage;
}
} else {
this.$message.error(fid + "文件请求错误");
return success = "文件请求错误";
}
}).catch(err => {
this.$message.error(fid + "上传文件过程失败");
return success = "上传文件过程失败";
})
return success
},
//取消连接
async abortMultipartUpload(bucket, key, uploadId, s3) {
const params = {
Bucket: bucket,
Key: key,
UploadId: uploadId
};
const res = async () => {
try {
const data = await s3.send(new AbortMultipartUploadCommand(params));
return data
} catch (err) {
console.log("取消连接失败: " + err.message);
return 1
}
}
return res()
},
//将分片合并
async completeMultipartUpload(bucket, key, parts, uploadId, s3) {
const params = {
Bucket: bucket,
Key: key,
MultipartUpload: {
Parts: parts
},
UploadId: uploadId
};
const res = async () => {
try {
const data = await s3.send(new CompleteMultipartUploadCommand(params));
return data
} catch (err) {
console.log("合并分片失败: ", err.message);
return 1
}
}
return res()
},
//上传一个分片
async uploadPart(f, uploadId, key, bucket, num, s3) {
const params = {
Bucket: bucket,
Key: key,
PartNumber: num,
UploadId: uploadId,
Body: f
};
const res = async () => {
try {
const data = await s3.send(new UploadPartCommand(params));
return data;
} catch (err) {
// return alert("There was an error listing your albums: " + err.message);
console.log('上传分片错误信息', err.message)
return 1;
}
}
return res();
},
//建立连接
async createMultipartUpload(bucket, key, s3, type) {// string, string
const params = {
Bucket: bucket,
Key: key,
ContentType: type
};
const res = async () => {
try {
const data = await s3.send(new CreateMultipartUploadCommand(params));
return data;
} catch (err) {
console.log('建立连接失败:', err.message)
return 1;
}
}
return res()
},
//上传文件基本信息,获取bucket基本信息
async inputFile(file) {
let m = await this.getEasyMd5(file.files)
let param = {
fileName: file.files.name,
etag: m.m,
size: file.files.size
}
let isOk = 0;
await fileUpload(param).then(res => {
if (res.data.hasOwnProperty("returnCode")) {
if (res.data.returnCode == 0) {
file.s3.endPoint = "https://" + res.data.endPoint;
file.s3.bucket = res.data.bucket;
file.s3.secretAccessKey = res.data.secretKey;
file.s3.accessKeyId = res.data.accessKey;
file.fid = res.data.fileId;
return isOk = 0;
} else {
this.$message.error(res.data.errMessage);
return isOk = res.data.errMessage;
}
} else {
this.$message.error("请求错误");
return isOk = "请求错误";
}
}).catch(err => {
this.$message.error("上传文件过程失败");
return isOk = "上传过程失败";
})
return isOk
},
//获取md5值
async getEasyMd5(file) {
var blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice;
var chunkSize = 524288; //512KB
if (file.size > chunkSize * 5) {
let l = await new Promise((resolve, reject) => {
var chunks = Math.floor(file.size / chunkSize);
var step = Math.floor(chunks / 5);
var currentChunk = 0;
var spark = new SparkMD5.ArrayBuffer();
var fileReader = new FileReader();
var counter = 0;
fileReader.onload = function (e) {
if (counter < 5) {
spark.append(e.target.result); // Append array buffer
}
currentChunk += step;
var md5_progress = Math.ceil((currentChunk / chunks) * 100);
if (counter < 5) {
counter += 1;
loadNext();
} else {
if (md5_progress != 100) {
}
var m = spark.end();
let temp = {
m: m
}
resolve(temp);
}
};
fileReader.onerror = function () {
};
function loadNext() {
var start = currentChunk * chunkSize;
var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
})
return l;
} else {
let l = await new Promise((resolve, reject) => {
const fileReader = new FileReader();
var spark = new SparkMD5.ArrayBuffer();
fileReader.readAsArrayBuffer(file);
fileReader.onload = (e) => {
spark.append(e.target.result);
var m = spark.end();
let temp = {
m: m
}
resolve(temp);
};
})
return l;
}
},
//过滤转码后的文件
filterChange(filterObj) {
if (filterObj.filterTag.length == 0 || filterObj.filterTag.length == 2) {
this.isScreen = 0;
this.init();
} else {
if (filterObj.filterTag[0] == true) {
this.isScreen = 1;
this.init();
} else {
this.isScreen = 2;
this.init();
}
}
},
//详细信息
detail(row) {
let param = {
fid: row.fid
}
getFileDetails(param).then(res => {
if (res.status == 401) {
this.$message.error("身份验证过期,请重新登录");
}
if (res.status == 200 || res.status == 201) {
if (res.data.returnCode == 0) {
this.myDetails = res.data.file;
// if (res.data.file.status == 1) {
// this.myDetails['status'] = '上传成功';
// } else if (res.data.file.status == 2) {
// this.myDetails['status'] = '系统待上传';
// }
} else {
return this.$message.error(res.data.errMessage);
}
}
}).catch(err => {
return this.$message.error("获取详情过程失败");
})
this.dialogTableVisible = true;
},
//查找
search() {
this.init();
},
//改变当前页数
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1;
this.init();
},
//改变当前页
handleCurrentChange(val) {
this.currentPage = val;
this.init();
},
//初始化
async init() {
let param = {
pageNum: this.currentPage,
pageSize: this.pageSize,
order: 'create_time',
asc: this.order,
status: 1
}
if (this.fileName != null && this.fileName != '') {
param['fileName'] = this.fileName;
}
if (this.isScreen == 1) {
param['transcoding'] = true;
} else if (this.isScreen == 2) {
param['transcoding'] = false;
}
await getMediaFileList(param).then(res => {
if (res.status == 401) {
this.$message.error("身份验证过期,请重新登录");
}
if (res.status == 200 || res.status == 201) {
if (res.data.returnCode == 0) {
this.tableData = res.data.files;
this.total = res.data.total;
} else {
return this.$message.error(res.data.errMessage);
}
}
})
},
//改变排序
change(column) {
if (column.order == "descending") {
this.order = false;
this.init();
} else {
this.order = true;
this.init();
}
},
//格式化时间时区
tableColumnFormatTime(row, column, cellValue, index) {
// yyyy-MM-dd hh:mm:ss
let date = new Date(cellValue);
return date.Format("yyyy-MM-dd hh:mm:ss")
}
},
created() {
this.init();
}
}
</script>
<style lang="less" scoped>
/deep/.el-divider--horizontal {
margin: 0 0;
}
.on {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #FFF;
border: 1px solid #DCDFE6;
color: #606266;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
color: #FFF;
background-color: #409EFF;
border-color: #409EFF;
}
.active {
color: #409EFF;
cursor: pointer;
}
.out {
color: #606266;
}
.dia {
/deep/.el-dialog__body {
align-items: center;
flex-direction: column;
display: flex;
}
}
/deep/.el-link.el-link--default {
color: #6aaef1;
}
</style>
补充
有很多网友想要与后端相关的文档,所以补充一下
这个是封装的请求
//获取媒体文件列表
export function getMediaFileList(params) {
return http.get('/mpms/files', params);
}
//获取媒体文件详情
export function getFileDetails(params) {
return http.get('/mpms/file', params);
}
//上传文件时获取bucket信息
export function fileUpload(params) {
return http.post('/mpms/file',params);
}
//上传文件并校验成功后,将文件状态改变
export function changeStatus(params) {
return http.put('/mpms/file', params);
}
//获取文件下载地址
export function getDownloadUrl(params) {
return http.get('/mpms/file/download', params);
}
这是后端相关的接口文档
仅列出与文件上传相关的fileUpload、changeStatus
fileUpload
changeStatus
更多推荐
所有评论(0)