赞
踩
一般上传文件时会因为ngnix对上传文件大小进行了限制、文件过大接口请求超时导致上传失败,此时可以考虑分片上传,分片就是说将文件拆分来进行上传,将各个文件的切片传递给后台,然后后台再进行合并(合并操作可由前端触发,也可由后端触发,本文由后端触发,前端触发可参考分片上传前端触发合并示例),切片就是文件切分字节(类似于字符串的截取)。
上传文件时需要一个文件的唯一标识,在上传文件过程中需要将文件的唯一标识(文件的唯一标识__该文件的MD5加密文件的字符串)传递给后端,后端会通过该标识返回给我们该上传文件目前的上传状态:
1、文件已存在,文件已经上传,此时我们无需再进行任何操作。
2、部分上传或者尚未上传,把需要上传的分片进行上传
实现目标:
实现方式
文件的MD5唯一标识,可通过spark-md5来获取,具体步骤:
npm i spark-md5 --save
import SparkMD5 from 'spark-md5' /** * 获取文件MD5唯一标识码 * @param file * @returns {Promise<unknown>} */ export function getFileMd5(file, chunkCount) { return new Promise((resolve, reject) => { const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice const chunks = chunkCount let currentChunk = 0 const spark = new SparkMD5.ArrayBuffer() const fileReader = new FileReader() fileReader.onload = function(e) { spark.append(e.target.result) currentChunk++ if (currentChunk < chunks) { loadNext() } else { const md5 = spark.end() resolve(md5) } } fileReader.onerror = function(e) { reject(e) } function loadNext() { const start = currentChunk * chunkSize let end = start + chunkSize if (end > file.size) { end = file.size } fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)) } loadNext() }) } // 切片大小,自定义 export const chunkSize = 5 * 1024 * 1024
<template> <div class="uoploadBtn"> <el-button type="primary" :size="size" plain @click.native="clickBtn">{{ title }}</el-button> <input v-if="fileUpload" ref="fileUpload" type="file" style="width: 0px;height: 0px" @change="fileChange"> </div> </template> <script> import { chunkSize, getFileMd5 } from './chunkFile.js' import request from './request'; export default { name: 'ChunksUpload', props: { actionUrl: { //文件上传服务地址 type: String, default: '' }, chunksUrl: { //检测文件上传服务地址 type: String, default: '' }, size: { type: String, default: 'mini' }, title: { type: String, default: '上传文件' }, params: { type: Object, default: function() { return {}; } } }, data() { return { fileUpload: false }; }, watch: {}, methods: { clickBtn() { const that = this; this.fileUpload = true; this.$nextTick(() => { that.$refs.fileUpload.click(); }); }, /** * @description:新增 * @param {*} loading * @return {*} */ insertReq(loading, params, formDataFile) { return request({ url: this.actionUrl, method: 'post', params: params, data: formDataFile, timeout: 600000 }); }, /** * @description: 上传文件发生变化时触发 * @param {*} * @return {*} */ fileChange() { const file = this.$refs.fileUpload.files[0]; if (!(file?.name.endsWith('.zip'))) { this.$message.error('请选择扩展名为.zip的文件包'); return; } this.CheckChunckInfo(file, this.insertReq); }, async CheckChunckInfo(file, ReqMethod) { const checkloading = this.$loading({ lock: true, text: '上传文件检测中...', background: 'rgba(0, 0, 0, 0.7)' }); const fileSize = file.size; const chunkCount = Math.ceil(fileSize / chunkSize); console.log('文件大小:', (File.size / 1024 / 1024) + 'Mb', '分片数:', chunkCount); const fileMd5 = await getFileMd5(file, chunkCount); const params = { 'identifier': fileMd5, // 文件的md5 'filename': file.name// 文件名 }; try { const response = await request({ url: this.chunksUrl, method: 'get', params }); checkloading.close(); this.fileUpload = false; if (response.code !== 0) { this.$message.warning(response?.msg); return; } const res = response.data; if (!res.exist) { // 没有完全上传就继续走分片上传 const loading = this.$loading({ lock: true, text: '正在上传,请等待...', background: 'rgba(0, 0, 0, 0.7)' }); this.beforeUpload(ReqMethod, loading, res, chunkCount, fileMd5, file); } else { this.$message.warning(file.name + ' 该压缩文件已上传,请重新选择!'); return; } } catch (e) { this.fileUpload = false; checkloading.close(); } }, async beforeUpload(ReqMethod, loading, res, chunkCount, fileMd5, file) { const self = this; // 获取后端返回的已上传分片数字的数组 const uploaded = res.chunks; const totalChunk = Array.from(Array(chunkCount).keys(), n => n + 1); const notUpload = totalChunk.filter(v => !uploaded.includes(v)); const cycleCompute = []; // 此处将未上传的切片按照每5个为一组进行分组,为后面每次批量上传的5个切片做准备,该数量可根据自己情况修改 if (notUpload.length) { notUpload.reduce(function(pre, item, index, notUpload) { var begin = index * 5; var end = begin + 5; var res = notUpload.slice(begin, end); if (res.length != 0) { cycleCompute[index] = res; } }, []); } else { loading.close(); this.$message.error(file.name + '该压缩文件已上传,请重新选择!'); return; } // 循环调用上传 if (cycleCompute.length) { for (let c = 0; c < cycleCompute.length; c++) { const reqArr = []; const cycleItem = cycleCompute[c]; for (let n = 0; n < cycleItem.length; n++) { // 定义分片开始上传的序号 const i = cycleItem[n] - 1; const start = i * chunkSize; const end = Math.min(file.size, start + chunkSize); const _chunkFile = file.slice(start, end); const formDataFile = new FormData(); const params = { identifier: fileMd5, total_chunks: chunkCount, chunk_numer: cycleItem[n], filename: file.name, ...self.params //自定义上传的参数 }; formDataFile.append('file', _chunkFile); reqArr.push(ReqMethod(loading, params, formDataFile)); } try { const responselist = await Promise.all(reqArr); const errprRes = responselist.filter(item => { return !item || item.code != 0; }); if (errprRes.length) { loading.close(); errprRes.forEach(resItem => { this.$message.error(resItem.msg); }); this.$emit('afterSave', false); return; } } catch (e) { loading.close(); return; } } } loading.close(); this.$message.success('上传成功'); this.$emit('afterSave', true); } } }; </script> <style scoped> .uoploadBtn{ display: inline-block; margin: 0px 12px } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。