当前位置:   article > 正文

Vue使用AWS s3进行大文件的分片上传、断点续传、下载(将文件上传进度显示)_aws toolkit在vue文件

aws toolkit在vue文件

这篇博客已经将这些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中

  1. const {
  2. S3Client,
  3. CreateMultipartUploadCommand,
  4. ListMultipartUploadsCommand,
  5. GetObjectCommand,
  6. UploadPartCommand,
  7. CompleteMultipartUploadCommand,
  8. AbortMultipartUploadCommand
  9. } = require("@aws-sdk/client-s3");
  10. //这里显示的是后面要用到的s3中的方法,具体怎么使用,后面会讲到

3.向后端获上传文件所需要的信息

(存储文件的bucket,存储节点(endPoint也就是存储服务器的ip)、accessKey(访问用户名)、secretKey(访问密码))

这一步也可以直接在前端获取这些信息(如何获取这些信息请参照博客:minio使用案例(Springboot)_子午谷的博客-CSDN博客_minio代理

我这里是已经创建了bucket了,如果没有创建bucket要先创建

创建方法:
注意:endpoint不要加端口,有默认端口(https,http协议端口不同),如果需要转换端口,可以考虑使用ngnix,如果在这里加了端口,会报错

  1. var s3 = new S3Client({
  2. endpoint: "https://" + s3Information.endPoint,//存储文件的服务器的地址,无端口
  3. s3ForcePathStyle: true,
  4. signatureVersion: 'v4',
  5. region: 'us-east-1',
  6. forcePathStyle: true,
  7. credentials: {
  8. accessKeyId: "",//访问登录名
  9. secretAccessKey: "",//访问密码
  10. }
  11. });
  12. var params = {
  13. Bucket: "examplebucket"//bucket名,任意取
  14. };
  15. s3.CreateBucketCommand(params, function(err, data) {
  16. if (err) console.log(err, err.stack); // 错误
  17. else console.log(data); // 成功
  18. });

4.建立连接

在每一个文件上传时,都要先与服务器建立连接,并获取唯一的uploadId

  1. //建立连接
  2. //key标识文件的名称
  3. //type为文件的类型
  4. //s3为创建bucket时建立的S3Client
  5. async createMultipartUpload(bucket, key, s3, type) {// string, string
  6. const params = {
  7. Bucket: bucket,
  8. Key: key,
  9. ContentType: type
  10. };
  11. const res = async () => {
  12. try {
  13. const data = await s3.send(new CreateMultipartUploadCommand(params));
  14. return data;
  15. } catch (err) {
  16. console.log('建立连接失败:', err.message)
  17. return 1;
  18. }
  19. }
  20. return res()
  21. },

5.选择文件,并将文件切片,分段上传

注意:s3分片上传文件时,只有最后一个分片的文件可以小于5M,其余分片必须大于等于5M,否则会报错(Your proposed upload is smaller than the minimum allowed object size)

阶段所使用的主要方法和步骤

  • 文件分片
  1. const chunkSize = 5 * 1024 * 1024;//定义分片的大小 为5M 采用分片上传时,只能并且只有有最后一个分片的size 小于 指定值(默认5M),不然就会报错
  2. const chunkCount = Math.ceil(this.fileList[i].files.size / chunkSize)//分片数
  3. for (let j = 0; j < chunkCount; j++) {
  4. let start = j * chunkSize;
  5. let end = Math.min(this.fileList[i].files.size, start + chunkSize)
  6. let _chunkFile = this.fileList[i].files.slice(start, end)//分片文件
  7. }
  • 文件分片上传至bucket
  1. //上传一个分片
  2. //f为一个文件分片
  3. //uploadId 为建立连接时返回的唯一id
  4. ///key为文件名
  5. //num为第几个分片
  6. //s3为S3Client
  7. async uploadPart(f, uploadId, key, bucket, num, s3) {
  8. const params = {
  9. Bucket: bucket,
  10. Key: key,
  11. PartNumber: num,
  12. UploadId: uploadId,
  13. Body: f
  14. };
  15. const res = async () => {
  16. try {
  17. const data = await s3.send(new UploadPartCommand(params));
  18. return data;
  19. } catch (err) {
  20. // return alert("There was an error listing your albums: " + err.message);
  21. console.log('上传分片错误信息', err.message)
  22. return 1;
  23. }
  24. }
  25. return res();
  26. },

6.文件分片均上传成功后,要对分片进行合并

注意:在分片上传完成,并且合并之后,系统会自动断开连接;如果是发异常,如上传分片失败等,需要手动断开连接

  1. //将分片合并
  2. //parts是一个数组,形式为{ ETag: 分片上传成功后返回的唯一标识, PartNumber: 第几个分片 }
  3. async completeMultipartUpload(bucket, key, parts, uploadId, s3) {
  4. const params = {
  5. Bucket: bucket,
  6. Key: key,
  7. MultipartUpload: {
  8. Parts: parts
  9. },
  10. UploadId: uploadId
  11. };
  12. const res = async () => {
  13. try {
  14. const data = await s3.send(new CompleteMultipartUploadCommand(params));
  15. return data
  16. } catch (err) {
  17. console.log("合并分片失败: ", err.message);
  18. return 1
  19. }
  20. }
  21. return res()
  22. },
  1. //取消连接
  2. async abortMultipartUpload(bucket, key, uploadId, s3) {
  3. const params = {
  4. Bucket: bucket,
  5. Key: key,
  6. UploadId: uploadId
  7. };
  8. const res = async () => {
  9. try {
  10. const data = await s3.send(new AbortMultipartUploadCommand(params));
  11. return data
  12. } catch (err) {
  13. console.log("取消连接失败: " + err.message);
  14. return 1
  15. }
  16. }
  17. return res()
  18. },

这个时候,文件就上传完成啦

二、下载文件

1.下载插件

我们使用s3的getObject方法获取文件时,获取的是一个readableStream流,所以要使用插件转换成blob文件进行下载

npm i -S binconv

地址:GitHub - nwtgck/binconv-npm: Converters for Blob, Uint8Array, ReadableStream, ArrayBuffer, string in JavaScript/TypeScript

2.下载

  1. //文件下载
  2. async downLoad(row) {
  3. let key = row.fid;
  4. let s3Information = {
  5. accessKey: "",
  6. bucket: "",
  7. endPoint: "",
  8. secretKey: ""
  9. }
  10. //1、判断在bucket中是否存在该文件
  11. let param = {
  12. fid: key
  13. }
  14. //getDownloadUrl这个方法是为了像后端获取用户上传的bucket等信息,这些都可以在前端完成
  15. await getDownloadUrl(param).then(res => {
  16. if (res.data.hasOwnProperty("returnCode")) {
  17. if (res.data.returnCode == 0) {
  18. s3Information = res.data;
  19. } else {
  20. return this.$message.error(res.data.errMessage);
  21. }
  22. } else {
  23. return this.$message.error("错误请求");
  24. }
  25. }).catch(err => {
  26. return this.$message.error("获取文件下载信息过程错误:" + err)
  27. })
  28. var s3 = new S3Client({
  29. endpoint: "https://" + s3Information.endPoint,
  30. s3ForcePathStyle: true,
  31. signatureVersion: 'v4',
  32. region: 'us-east-1',
  33. forcePathStyle: true,
  34. credentials: {
  35. accessKeyId: s3Information.accessKey,
  36. secretAccessKey: s3Information.secretKey,
  37. }
  38. });
  39. let isExistence = await this.getObject(s3Information.bucket, key, s3, row.name)
  40. if (isExistence == 1) {
  41. return this.$message.error("bucket中不存在该文件");
  42. }
  43. },

放上全部代码

1、前端页面

  1. <template>
  2. <div>
  3. <!-- 搜索 -->
  4. <div style="margin-top:20px;">
  5. <input v-show="false" ref="fileRef" multiple type="file" @change="fileChange">
  6. <el-button style="float:left" type="primary" icon="el-icon-upload" size="medium" @click="uploadFile">上传媒体文件
  7. </el-button>
  8. <el-input size="large" placeholder="请输入内容" v-model="fileName"
  9. style="width: 500px;margin-bottom: 20px; margin-left: 50px;">
  10. <template slot="prepend">文件名</template>
  11. <el-button slot="append" icon="el-icon-search" size="small" @click="search"></el-button>
  12. </el-input>
  13. </div>
  14. <!-- 上传文件弹框 -->
  15. <div>
  16. <el-dialog title="上传文件" :visible.sync="dialogFormVisible" width="40%" :before-close="fileClose">
  17. <div style="width: 100%;height: 500px;overflow: hidden;overflow-y: scroll;">
  18. <div v-for="item in fileList" :key="item.name">
  19. <div v-if="item.appear == true" style="width:100%;display: table;">
  20. <div style="display:table-cell;vertical-align:middle;text-align: center;width: 15%;">
  21. <img src="../../../assets/file.png" style="display:inline-block;" width="50px"
  22. height="60px" />
  23. </div>
  24. <div style="display:table-cell;width: 70%;">
  25. <div style="font-weight:700;font-size:medium">{{item.files.name}}</div>
  26. <div>
  27. <el-progress :percentage="item.percentage" :color="item.color"></el-progress>
  28. </div>
  29. <div>
  30. <span v-if="item.err == true" style="color:#F56C6C">{{item.meassage}}</span>
  31. <span v-if="item.succ == true" style="color:#67C23A">{{item.meassage}}</span>
  32. </div>
  33. </div>
  34. <div style="vertical-align:middle;text-align: center;display:table-cell;width: 15%;">
  35. <img @click="stop(item)" v-if="item.show == 0"
  36. style="display:inline-block;cursor: pointer;" src="../../../assets/stop.png"
  37. width="30px" height="30px">
  38. <img @click="continued(item)" v-if="item.show == 1"
  39. style="display:inline-block;cursor: pointer;" src="../../../assets/continue.png"
  40. width="30px" height="30px">
  41. <img @click="deleted(item)" v-if="item.show == 1"
  42. style="display:inline-block;margin-left: 10px;cursor: pointer;"
  43. src="../../../assets/delete.png" width="30px" height="30px">
  44. <img @click="continued(item)" v-if="item.show == 2 || item.show == 4"
  45. style="display:inline-block;cursor: pointer;" src="../../../assets/retry.png"
  46. width="30px" height="30px">
  47. </div>
  48. </div>
  49. <el-divider v-if="item.appear == true" style="margin-top:0%"></el-divider>
  50. </div>
  51. </div>
  52. </el-dialog>
  53. </div>
  54. <!-- 表格 -->
  55. <div>
  56. <el-table :data="tableData" style="width: 100%;font-size: 10px" @sort-change="change"
  57. @filter-change="filterChange">
  58. <el-table-column prop="fid" label="文件" width="300px">
  59. <template slot-scope="scope">
  60. <el-link style="font-size: 10px" @click="detail(scope.row)">{{ scope.row.fid }}</el-link>
  61. </template>
  62. </el-table-column>
  63. <el-table-column prop="name" label="文件名" width="350px" :show-overflow-tooltip=true>
  64. </el-table-column>
  65. <el-table-column prop="size" label="大小" width="150px"></el-table-column>
  66. <el-table-column prop="transcoding" label="转码生成文件" width="120px"
  67. :filters="[{ text: '否', value: false }, { text: '是', value: true }]" column-key="filterTag">
  68. <template slot-scope="scope">
  69. <span v-if="scope.row.transcoding === true" style="color:#409EFF"></span>
  70. <span v-if="scope.row.transcoding === false"></span>
  71. </template>
  72. </el-table-column>
  73. <el-table-column prop="createTime" label="创建时间" sortable="custom" :formatter="tableColumnFormatTime">
  74. </el-table-column>
  75. <el-table-column prop="expireTime" label="过期时间" :formatter="tableColumnFormatTime"></el-table-column>
  76. <el-table-column label="操作" width="150px">
  77. <template slot-scope="scope">
  78. <el-button type="text" style="color:#E6A23C" @click="detail(scope.row)" size="small">详情
  79. </el-button>
  80. <el-button type="text" style="color:#409EFF" @click="downLoad(scope.row)" size="small"> 下载
  81. </el-button>
  82. <el-popconfirm confirm-button-text='删除' cancel-button-text='取消' icon="el-icon-delete"
  83. icon-color="red" title="确定删除此媒体文件吗?" @confirm="deleted(scope.row)" style="margin-left:10px">
  84. <el-button type="text" style="color:#F56C6C" slot="reference" :disabled="!scope.row.status"
  85. size="small">删除</el-button>
  86. </el-popconfirm>
  87. </template>
  88. </el-table-column>
  89. </el-table>
  90. </div>
  91. <!-- 分页 -->
  92. <div style="margin-top:20px;float: right;">
  93. <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
  94. :current-page="currentPage" :page-sizes="[5, 10, 20, 30]" :page-size='pageSize'
  95. layout="total, sizes, prev, pager, next, jumper" :total='total'>
  96. </el-pagination>
  97. </div>
  98. <!-- 详情弹窗 -->
  99. <div>
  100. <el-dialog :visible.sync="dialogTableVisible" v-dialogDrag>
  101. <template #title>
  102. <div>
  103. <span style="font-size:20px;line-height:24px;color: #303133;">文件详情</span><br><br>
  104. <span style="font-size:xx-small;color:grey">提示:双击弹窗头部,可使弹窗全屏展示</span>
  105. </div>
  106. </template>
  107. <el-descriptions title="" direction="vertical" :column="2" border>
  108. <el-descriptions-item label="文件">{{ myDetails.fid }}</el-descriptions-item>
  109. <el-descriptions-item label="文件名">{{ myDetails.name }}</el-descriptions-item>
  110. <el-descriptions-item label="文件大小">{{ myDetails.size }}</el-descriptions-item>
  111. <el-descriptions-item label="文件后缀">{{ myDetails.suffix }}</el-descriptions-item>
  112. <el-descriptions-item label="创建时间">{{ myDetails.creatAt }}</el-descriptions-item>
  113. <el-descriptions-item label="状态">
  114. <el-tag type="success" v-if="myDetails.status == 1">上传成功</el-tag>
  115. <el-tag type="danger" v-if="myDetails.status == 2">待上传</el-tag>
  116. <el-tag type="danger" v-if="myDetails.status == 0">待上传</el-tag>
  117. <el-tag type="danger" v-if="myDetails.status == -1">已删除</el-tag>
  118. <el-tag type="danger" v-if="myDetails.status == 3">已删除</el-tag>
  119. <el-button type="text" style="color:#409EFF;margin-left: 20px;" @click="downLoad(myDetails)"
  120. size="small" v-if="myDetails.status == 1"> 下载</el-button>
  121. </el-descriptions-item>
  122. <el-descriptions-item label="信息流">
  123. <span style="white-space:pre-wrap">
  124. {{ myDetails.meta }}
  125. </span>
  126. </el-descriptions-item>
  127. </el-descriptions>
  128. </el-dialog>
  129. </div>
  130. <!-- <div>
  131. <button @click="test()">按钮</button>
  132. </div> -->
  133. </div>
  134. </template>
  135. <script>
  136. import { getMediaFileList, getFileDetails, fileUpload, changeStatus, getDownloadUrl } from '../../../utils/api'
  137. import SparkMD5 from "../../../components/spark-md5.min.js";
  138. import * as binconv from 'binconv';
  139. const chunkSize = 5 * 1024 * 1024;//定义分片的大小 为5M 采用分片上传时,只能并且只有有最后一个分片的size 小于 指定值(默认5M),不然就会报错
  140. const {
  141. S3Client,
  142. CreateMultipartUploadCommand,
  143. ListMultipartUploadsCommand,
  144. GetObjectCommand,
  145. UploadPartCommand,
  146. CompleteMultipartUploadCommand,
  147. AbortMultipartUploadCommand
  148. } = require("@aws-sdk/client-s3");
  149. export default {
  150. data() {
  151. return {
  152. tableData: [],
  153. fileName: '',
  154. order: false,
  155. currentPage: 1,
  156. pageSize: 10,
  157. total: 0,
  158. dialogTableVisible: false,
  159. myDetails: {
  160. id: "",
  161. name: "",
  162. size: "",
  163. suffix: "",
  164. md5: "",
  165. meta: "",
  166. creatAt: "",
  167. status: ''
  168. },
  169. isScreen: 0,
  170. dialogFormVisible: false,
  171. fileList: [],//文件
  172. errorFiles: [],//上传失败文件
  173. }
  174. },
  175. watch: {
  176. },
  177. methods: {
  178. //文件下载
  179. async downLoad(row) {
  180. let key = row.fid;
  181. let s3Information = {
  182. accessKey: "",
  183. bucket: "",
  184. endPoint: "",
  185. secretKey: ""
  186. }
  187. //1、判断在bucket中是否存在该文件
  188. let param = {
  189. fid: key
  190. }
  191. await getDownloadUrl(param).then(res => {
  192. if (res.data.hasOwnProperty("returnCode")) {
  193. if (res.data.returnCode == 0) {
  194. s3Information = res.data;
  195. } else {
  196. return this.$message.error(res.data.errMessage);
  197. }
  198. } else {
  199. return this.$message.error("错误请求");
  200. }
  201. }).catch(err => {
  202. return this.$message.error("获取文件下载信息过程错误:" + err)
  203. })
  204. var s3 = new S3Client({
  205. endpoint: "https://" + s3Information.endPoint,
  206. s3ForcePathStyle: true,
  207. signatureVersion: 'v4',
  208. region: 'us-east-1',
  209. forcePathStyle: true,
  210. credentials: {
  211. accessKeyId: s3Information.accessKey,
  212. secretAccessKey: s3Information.secretKey,
  213. }
  214. });
  215. let isExistence = await this.getObject(s3Information.bucket, key, s3, row.name)
  216. if (isExistence == 1) {
  217. return this.$message.error("bucket中不存在该文件");
  218. }
  219. },
  220. //判断在bucket中是否存在该文件
  221. async getObject(bucket, key, s3, name) {
  222. const params = {
  223. Bucket: bucket,
  224. Key: key
  225. };
  226. const res = async () => {
  227. try {
  228. const data = await s3.send(new GetObjectCommand(params));
  229. //将readableStream 转换成blob
  230. const blob = await binconv.readableStreamToBlob(data.Body);
  231. var newBlob = new Blob([blob], { type: data.ContentType });
  232. var elink = document.createElement('a');
  233. elink.download = name;
  234. elink.style.display = 'none';
  235. const src = URL.createObjectURL(newBlob);
  236. elink.href = src;
  237. document.body.appendChild(elink);
  238. elink.click();
  239. document.body.removeChild(elink);
  240. URL.revokeObjectURL(src)
  241. return 0;
  242. } catch (err) {
  243. console.log("There was an error listing your albums: " + err.message);
  244. return 1;
  245. }
  246. }
  247. return res()
  248. },
  249. //点击选择文件
  250. uploadFile() {
  251. this.$refs.fileRef.dispatchEvent(new MouseEvent('click'))
  252. },
  253. //文件弹窗关闭
  254. fileClose(done) {
  255. for (let i = 0; i < this.fileList.length; i++) {
  256. if (this.fileList[i].err == false && this.fileList[i].succ == false) {
  257. return this.$message.warning("请等待所有文件处理完成后关闭弹窗");
  258. }
  259. }
  260. this.fileList = [];
  261. this.errorFiles = [];
  262. done();
  263. },
  264. //删除还未上传成功文件
  265. deleted(item) {
  266. item.appear = false;
  267. },
  268. //继续上传
  269. async continued(item) {
  270. item.fid = '';
  271. item.err = false;
  272. item.succ = false;
  273. item.message = "";
  274. item.show = 0;
  275. item.color = "#409EFF"
  276. item.s3 = {
  277. endPoint: '',
  278. accessKeyId: '',
  279. secretAccessKey: '',
  280. bucket: ''
  281. };
  282. this.errorFiles.push(item);
  283. //for (let i = 0; i < this.errorFiles.length; i++)
  284. while (this.errorFiles.length != 0) {
  285. let errFile = this.errorFiles.pop();
  286. //1、获取bucket基本信息
  287. let isOk = await this.inputFile(errFile);
  288. //2、建立连接
  289. if (isOk == 0) {
  290. var s3 = new S3Client({
  291. endpoint: errFile.s3.endPoint,
  292. s3ForcePathStyle: true,
  293. signatureVersion: 'v4',
  294. region: 'us-east-1',
  295. forcePathStyle: true,
  296. credentials: {
  297. accessKeyId: errFile.s3.accessKeyId,
  298. secretAccessKey: errFile.s3.secretAccessKey,
  299. }
  300. });
  301. var isConnect = await this.createMultipartUpload(errFile.s3.bucket, errFile.fid, s3, errFile.files.type)
  302. if (isConnect != 1) {
  303. //连接成功
  304. /**
  305. * 3、传送文件
  306. * 3.1将文件分片
  307. * 3.2分片文件循环上传
  308. */
  309. const chunkCount = Math.ceil(errFile.files.size / chunkSize)//分片数
  310. var isUpload = 0;//用来判断分片是否上传成功
  311. var sharding = [];//成功分片信息
  312. for (let j = 0; j < chunkCount; j++) {
  313. let start = j * chunkSize;
  314. let end = Math.min(errFile.files.size, start + chunkSize)
  315. let _chunkFile = errFile.files.slice(start, end)
  316. isUpload = await this.uploadPart(_chunkFile, isConnect.UploadId, errFile.fid, errFile.s3.bucket, j + 1, s3);
  317. if (isUpload == 1 || errFile.show == 1) {
  318. isUpload = 1;
  319. //断开连接
  320. await this.abortMultipartUpload(errFile.s3.bucket, errFile.fid, isConnect.UploadId, s3)
  321. break;
  322. } else {
  323. //成功
  324. //将分片信息存起来
  325. errFile.percentage = Math.min(99, Math.ceil(errFile.percentage + 100 / chunkCount))
  326. sharding.push({ ETag: isUpload.ETag, PartNumber: j + 1 })
  327. }
  328. }
  329. if (isUpload == 1) {
  330. //取消连接
  331. //向用户输出文件上传失败,并将前端页面改变
  332. errFile.err = true;
  333. sharding = [];
  334. if (errFile.show == 1) {
  335. errFile.meassage = "暂停";
  336. } else {
  337. errFile.show = 2;
  338. errFile.meassage = "上传分片失败";
  339. }
  340. errFile.color = "#F56C6C";
  341. continue;
  342. } else {
  343. //4、合并分片
  344. let isMerge = await this.completeMultipartUpload(errFile.s3.bucket, errFile.fid, sharding, isConnect.UploadId, s3);
  345. if (isMerge == 1) {
  346. //失败,将消息返回给页面
  347. errFile.err = true;
  348. errFile.show = 2;
  349. errFile.color = "#F56C6C";
  350. errFile.meassage = "合并分片失败";
  351. continue;
  352. } else {
  353. //5、将合并成功信息传给后端
  354. let success = await this.upLoadIsOk(errFile.fid);
  355. if (success == 0) {
  356. errFile.succ = true;
  357. errFile.meassage = "上传成功";
  358. errFile.show = 3;
  359. errFile.color = "#67C23A";
  360. errFile.percentage = 100;
  361. await this.init();
  362. } else {
  363. errFile.err = true;
  364. errFile.color = "#F56C6C";
  365. errFile.show = 4;
  366. errFile.meassage = success;
  367. continue;
  368. }
  369. }
  370. }
  371. } else {
  372. errFile.err = true;
  373. errFile.show = 2;
  374. errFile.color = "#F56C6C";
  375. errFile.meassage = "与bucket建立连接失败";
  376. continue;
  377. }
  378. } else {
  379. errFile.err = true;
  380. errFile.color = "#F56C6C";
  381. errFile.meassage = isOk;
  382. errFile.show = 2;
  383. continue;
  384. }
  385. }
  386. },
  387. //暂停上传
  388. stop(item) {
  389. item.show = 1;
  390. },
  391. //文件上传
  392. async fileChange(event) {
  393. // 将上传文件传到文件列表
  394. let files = event.target.files;
  395. this.fileList = [];
  396. for (let i = 0; i < files.length; i++) {
  397. this.fileList.push({
  398. appear: true, color: "#409EFF",
  399. fid: '', percentage: 0, files: files[i], err: false, succ: false, message: "", show: 0, s3: {
  400. endPoint: '',
  401. accessKeyId: '',
  402. secretAccessKey: '',
  403. bucket: ''
  404. }
  405. })
  406. }
  407. this.dialogFormVisible = true;
  408. for (let i = 0; i < this.fileList.length; i++) {
  409. //1、获取bucket基本信息
  410. let isOk = await this.inputFile(this.fileList[i]);
  411. //2、建立连接
  412. if (isOk == 0) {
  413. var s3 = new S3Client({
  414. endpoint: this.fileList[i].s3.endPoint,
  415. s3ForcePathStyle: true,
  416. signatureVersion: 'v4',
  417. region: 'us-east-1',
  418. forcePathStyle: true,
  419. credentials: {
  420. accessKeyId: this.fileList[i].s3.accessKeyId,
  421. secretAccessKey: this.fileList[i].s3.secretAccessKey,
  422. }
  423. });
  424. var isConnect = await this.createMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, s3, this.fileList[i].files.type)
  425. if (isConnect != 1) {
  426. //连接成功
  427. /**
  428. * 3、传送文件
  429. * 3.1将文件分片
  430. * 3.2分片文件循环上传
  431. */
  432. const chunkCount = Math.ceil(this.fileList[i].files.size / chunkSize)//分片数
  433. var isUpload = 0;//用来判断分片是否上传成功
  434. var sharding = [];//成功分片信息
  435. for (let j = 0; j < chunkCount; j++) {
  436. let start = j * chunkSize;
  437. let end = Math.min(this.fileList[i].files.size, start + chunkSize)
  438. let _chunkFile = this.fileList[i].files.slice(start, end)
  439. isUpload = await this.uploadPart(_chunkFile, isConnect.UploadId, this.fileList[i].fid, this.fileList[i].s3.bucket, j + 1, s3);
  440. if (isUpload == 1 || this.fileList[i].show == 1) {
  441. isUpload = 1;
  442. //断开连接
  443. await this.abortMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, isConnect.UploadId, s3)
  444. break;
  445. } else {
  446. //成功
  447. //将分片信息存起来
  448. this.fileList[i].percentage = Math.min(99, Math.ceil(this.fileList[i].percentage + 100 / chunkCount))
  449. sharding.push({ ETag: isUpload.ETag, PartNumber: j + 1 })
  450. }
  451. }
  452. if (isUpload == 1) {
  453. //取消连接
  454. //向用户输出文件上传失败,并将前端页面改变
  455. this.fileList[i].err = true;
  456. sharding = [];
  457. if (this.fileList[i].show == 1) {
  458. this.fileList[i].meassage = "暂停";
  459. } else {
  460. this.fileList[i].show = 2;
  461. this.fileList[i].meassage = "上传分片失败";
  462. }
  463. this.fileList[i].color = "#F56C6C";
  464. continue;
  465. } else {
  466. //4、合并分片
  467. let isMerge = await this.completeMultipartUpload(this.fileList[i].s3.bucket, this.fileList[i].fid, sharding, isConnect.UploadId, s3);
  468. if (isMerge == 1) {
  469. //失败,将消息返回给页面
  470. this.fileList[i].err = true;
  471. this.fileList[i].show = 2;
  472. this.fileList[i].color = "#F56C6C";
  473. this.fileList[i].meassage = "合并分片失败";
  474. continue;
  475. } else {
  476. //5、将合并成功信息传给后端
  477. let success = await this.upLoadIsOk(this.fileList[i].fid);
  478. if (success == 0) {
  479. this.fileList[i].succ = true;
  480. this.fileList[i].meassage = "上传成功";
  481. this.fileList[i].percentage = 100;
  482. this.fileList[i].show = 3;
  483. this.fileList[i].color = "#67C23A";
  484. await this.init();
  485. } else {
  486. this.fileList[i].err = true;
  487. this.fileList[i].show = 4;
  488. this.fileList[i].color = "#F56C6C";
  489. this.fileList[i].meassage = success;
  490. continue;
  491. }
  492. }
  493. }
  494. } else {
  495. this.fileList[i].err = true;
  496. this.fileList[i].show = 2;
  497. this.fileList[i].color = "#F56C6C";
  498. this.fileList[i].meassage = "与bucket建立连接失败";
  499. continue;
  500. }
  501. } else {
  502. this.fileList[i].err = true;
  503. this.fileList[i].meassage = isOk;
  504. this.fileList[i].show = 2;
  505. this.fileList[i].color = "#F56C6C";
  506. continue;
  507. }
  508. }
  509. this.$refs["fileRef"].value = '';
  510. return
  511. },
  512. //通知后端,文件上传成功
  513. async upLoadIsOk(fid) {
  514. let success = 0;
  515. let param = {
  516. fid: fid
  517. }
  518. await changeStatus(param).then(res => {
  519. if (res.data.hasOwnProperty("returnCode")) {
  520. if (res.data.returnCode == 0) {
  521. return success = 0;
  522. } else {
  523. this.$message.error(fid + res.data.errMessage);
  524. return success = res.data.errMessage;
  525. }
  526. } else {
  527. this.$message.error(fid + "文件请求错误");
  528. return success = "文件请求错误";
  529. }
  530. }).catch(err => {
  531. this.$message.error(fid + "上传文件过程失败");
  532. return success = "上传文件过程失败";
  533. })
  534. return success
  535. },
  536. //取消连接
  537. async abortMultipartUpload(bucket, key, uploadId, s3) {
  538. const params = {
  539. Bucket: bucket,
  540. Key: key,
  541. UploadId: uploadId
  542. };
  543. const res = async () => {
  544. try {
  545. const data = await s3.send(new AbortMultipartUploadCommand(params));
  546. return data
  547. } catch (err) {
  548. console.log("取消连接失败: " + err.message);
  549. return 1
  550. }
  551. }
  552. return res()
  553. },
  554. //将分片合并
  555. async completeMultipartUpload(bucket, key, parts, uploadId, s3) {
  556. const params = {
  557. Bucket: bucket,
  558. Key: key,
  559. MultipartUpload: {
  560. Parts: parts
  561. },
  562. UploadId: uploadId
  563. };
  564. const res = async () => {
  565. try {
  566. const data = await s3.send(new CompleteMultipartUploadCommand(params));
  567. return data
  568. } catch (err) {
  569. console.log("合并分片失败: ", err.message);
  570. return 1
  571. }
  572. }
  573. return res()
  574. },
  575. //上传一个分片
  576. async uploadPart(f, uploadId, key, bucket, num, s3) {
  577. const params = {
  578. Bucket: bucket,
  579. Key: key,
  580. PartNumber: num,
  581. UploadId: uploadId,
  582. Body: f
  583. };
  584. const res = async () => {
  585. try {
  586. const data = await s3.send(new UploadPartCommand(params));
  587. return data;
  588. } catch (err) {
  589. // return alert("There was an error listing your albums: " + err.message);
  590. console.log('上传分片错误信息', err.message)
  591. return 1;
  592. }
  593. }
  594. return res();
  595. },
  596. //建立连接
  597. async createMultipartUpload(bucket, key, s3, type) {// string, string
  598. const params = {
  599. Bucket: bucket,
  600. Key: key,
  601. ContentType: type
  602. };
  603. const res = async () => {
  604. try {
  605. const data = await s3.send(new CreateMultipartUploadCommand(params));
  606. return data;
  607. } catch (err) {
  608. console.log('建立连接失败:', err.message)
  609. return 1;
  610. }
  611. }
  612. return res()
  613. },
  614. //上传文件基本信息,获取bucket基本信息
  615. async inputFile(file) {
  616. let m = await this.getEasyMd5(file.files)
  617. let param = {
  618. fileName: file.files.name,
  619. etag: m.m,
  620. size: file.files.size
  621. }
  622. let isOk = 0;
  623. await fileUpload(param).then(res => {
  624. if (res.data.hasOwnProperty("returnCode")) {
  625. if (res.data.returnCode == 0) {
  626. file.s3.endPoint = "https://" + res.data.endPoint;
  627. file.s3.bucket = res.data.bucket;
  628. file.s3.secretAccessKey = res.data.secretKey;
  629. file.s3.accessKeyId = res.data.accessKey;
  630. file.fid = res.data.fileId;
  631. return isOk = 0;
  632. } else {
  633. this.$message.error(res.data.errMessage);
  634. return isOk = res.data.errMessage;
  635. }
  636. } else {
  637. this.$message.error("请求错误");
  638. return isOk = "请求错误";
  639. }
  640. }).catch(err => {
  641. this.$message.error("上传文件过程失败");
  642. return isOk = "上传过程失败";
  643. })
  644. return isOk
  645. },
  646. //获取md5值
  647. async getEasyMd5(file) {
  648. var blobSlice =
  649. File.prototype.slice ||
  650. File.prototype.mozSlice ||
  651. File.prototype.webkitSlice;
  652. var chunkSize = 524288; //512KB
  653. if (file.size > chunkSize * 5) {
  654. let l = await new Promise((resolve, reject) => {
  655. var chunks = Math.floor(file.size / chunkSize);
  656. var step = Math.floor(chunks / 5);
  657. var currentChunk = 0;
  658. var spark = new SparkMD5.ArrayBuffer();
  659. var fileReader = new FileReader();
  660. var counter = 0;
  661. fileReader.onload = function (e) {
  662. if (counter < 5) {
  663. spark.append(e.target.result); // Append array buffer
  664. }
  665. currentChunk += step;
  666. var md5_progress = Math.ceil((currentChunk / chunks) * 100);
  667. if (counter < 5) {
  668. counter += 1;
  669. loadNext();
  670. } else {
  671. if (md5_progress != 100) {
  672. }
  673. var m = spark.end();
  674. let temp = {
  675. m: m
  676. }
  677. resolve(temp);
  678. }
  679. };
  680. fileReader.onerror = function () {
  681. };
  682. function loadNext() {
  683. var start = currentChunk * chunkSize;
  684. var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
  685. fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
  686. }
  687. loadNext();
  688. })
  689. return l;
  690. } else {
  691. let l = await new Promise((resolve, reject) => {
  692. const fileReader = new FileReader();
  693. var spark = new SparkMD5.ArrayBuffer();
  694. fileReader.readAsArrayBuffer(file);
  695. fileReader.onload = (e) => {
  696. spark.append(e.target.result);
  697. var m = spark.end();
  698. let temp = {
  699. m: m
  700. }
  701. resolve(temp);
  702. };
  703. })
  704. return l;
  705. }
  706. },
  707. //过滤转码后的文件
  708. filterChange(filterObj) {
  709. if (filterObj.filterTag.length == 0 || filterObj.filterTag.length == 2) {
  710. this.isScreen = 0;
  711. this.init();
  712. } else {
  713. if (filterObj.filterTag[0] == true) {
  714. this.isScreen = 1;
  715. this.init();
  716. } else {
  717. this.isScreen = 2;
  718. this.init();
  719. }
  720. }
  721. },
  722. //详细信息
  723. detail(row) {
  724. let param = {
  725. fid: row.fid
  726. }
  727. getFileDetails(param).then(res => {
  728. if (res.status == 401) {
  729. this.$message.error("身份验证过期,请重新登录");
  730. }
  731. if (res.status == 200 || res.status == 201) {
  732. if (res.data.returnCode == 0) {
  733. this.myDetails = res.data.file;
  734. // if (res.data.file.status == 1) {
  735. // this.myDetails['status'] = '上传成功';
  736. // } else if (res.data.file.status == 2) {
  737. // this.myDetails['status'] = '系统待上传';
  738. // }
  739. } else {
  740. return this.$message.error(res.data.errMessage);
  741. }
  742. }
  743. }).catch(err => {
  744. return this.$message.error("获取详情过程失败");
  745. })
  746. this.dialogTableVisible = true;
  747. },
  748. //查找
  749. search() {
  750. this.init();
  751. },
  752. //改变当前页数
  753. handleSizeChange(val) {
  754. this.pageSize = val;
  755. this.currentPage = 1;
  756. this.init();
  757. },
  758. //改变当前页
  759. handleCurrentChange(val) {
  760. this.currentPage = val;
  761. this.init();
  762. },
  763. //初始化
  764. async init() {
  765. let param = {
  766. pageNum: this.currentPage,
  767. pageSize: this.pageSize,
  768. order: 'create_time',
  769. asc: this.order,
  770. status: 1
  771. }
  772. if (this.fileName != null && this.fileName != '') {
  773. param['fileName'] = this.fileName;
  774. }
  775. if (this.isScreen == 1) {
  776. param['transcoding'] = true;
  777. } else if (this.isScreen == 2) {
  778. param['transcoding'] = false;
  779. }
  780. await getMediaFileList(param).then(res => {
  781. if (res.status == 401) {
  782. this.$message.error("身份验证过期,请重新登录");
  783. }
  784. if (res.status == 200 || res.status == 201) {
  785. if (res.data.returnCode == 0) {
  786. this.tableData = res.data.files;
  787. this.total = res.data.total;
  788. } else {
  789. return this.$message.error(res.data.errMessage);
  790. }
  791. }
  792. })
  793. },
  794. //改变排序
  795. change(column) {
  796. if (column.order == "descending") {
  797. this.order = false;
  798. this.init();
  799. } else {
  800. this.order = true;
  801. this.init();
  802. }
  803. },
  804. //格式化时间时区
  805. tableColumnFormatTime(row, column, cellValue, index) {
  806. // yyyy-MM-dd hh:mm:ss
  807. let date = new Date(cellValue);
  808. return date.Format("yyyy-MM-dd hh:mm:ss")
  809. }
  810. },
  811. created() {
  812. this.init();
  813. }
  814. }
  815. </script>
  816. <style lang="less" scoped>
  817. /deep/.el-divider--horizontal {
  818. margin: 0 0;
  819. }
  820. .on {
  821. display: inline-block;
  822. line-height: 1;
  823. white-space: nowrap;
  824. cursor: pointer;
  825. background: #FFF;
  826. border: 1px solid #DCDFE6;
  827. color: #606266;
  828. -webkit-appearance: none;
  829. text-align: center;
  830. box-sizing: border-box;
  831. outline: 0;
  832. margin: 0;
  833. transition: .1s;
  834. font-weight: 500;
  835. padding: 12px 20px;
  836. font-size: 14px;
  837. border-radius: 4px;
  838. color: #FFF;
  839. background-color: #409EFF;
  840. border-color: #409EFF;
  841. }
  842. .active {
  843. color: #409EFF;
  844. cursor: pointer;
  845. }
  846. .out {
  847. color: #606266;
  848. }
  849. .dia {
  850. /deep/.el-dialog__body {
  851. align-items: center;
  852. flex-direction: column;
  853. display: flex;
  854. }
  855. }
  856. /deep/.el-link.el-link--default {
  857. color: #6aaef1;
  858. }
  859. </style>

补充

有很多网友想要与后端相关的文档,所以补充一下

这个是封装的请求

  1. //获取媒体文件列表
  2. export function getMediaFileList(params) {
  3. return http.get('/mpms/files', params);
  4. }
  5. //获取媒体文件详情
  6. export function getFileDetails(params) {
  7. return http.get('/mpms/file', params);
  8. }
  9. //上传文件时获取bucket信息
  10. export function fileUpload(params) {
  11. return http.post('/mpms/file',params);
  12. }
  13. //上传文件并校验成功后,将文件状态改变
  14. export function changeStatus(params) {
  15. return http.put('/mpms/file', params);
  16. }
  17. //获取文件下载地址
  18. export function getDownloadUrl(params) {
  19. return http.get('/mpms/file/download', params);
  20. }

这是后端相关的接口文档

仅列出与文件上传相关的fileUpload、changeStatus

fileUpload

 changeStatus

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/66622
推荐阅读
相关标签
  

闽ICP备14008679号