| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- /*
- * @Description: 大文件上传、分片上传、断点续传、文件秒传
- * @Author: zhangy
- * @Date: 2022-05-16 13:10:13
- * @LastEditors: zhangy
- * @LastEditTime: 2022-05-19 10:14:33
- */
- const SparkMD5 = require('spark-md5')
- import handoutApi from '@/newApi/handout'
- import axios from 'axios'
- // 切片大小(单位:B)
- const CHUNK_SIZE = 50 * 1024 * 1024
- // const CHUNK_SIZE = 24 * 1024
- /**
- * @description: 分块计算文件的md5值
- * @param {*} file 文件
- * @param {*} chunkSize 分片大小
- * @returns {*}
- */
- function calculateFileMd5(file, chunkSize) {
- return new Promise((resolve, reject) => {
- const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
- const chunks = Math.ceil(file.size / chunkSize)
- 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()
- })
- }
- /**
- * @description: 分块计算文件的md5值
- * @param {*} file 文件
- * @returns {Promise}
- */
- function calculateFileMd5ByDefaultChunkSize(file) {
- return calculateFileMd5(file, CHUNK_SIZE)
- }
- /**
- * @description: 文件切片
- * @param {*} file
- * @param {*} size 切片大小
- * @returns [{file}]
- */
- function createFileChunk(file, size = CHUNK_SIZE) {
- const chunks = []
- let cur = 0
- while (cur < file.size) {
- chunks.push({ file: file.slice(cur, cur + size) })
- cur += size
- }
- return chunks
- }
- /**
- * @description: 获取文件的后缀名
- */
- function getFileType(fileName) {
- return fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase()
- }
- /**
- * @description: 根据文件的md5值判断文件是否已经上传过了
- * @param {*} md5 文件的md5
- * @param {*} 准备上传的文件
- * @returns {Promise}
- */
- function checkMd5(md5, file) {
- return new Promise(resolve => {
- getUploadStatus({ md5 })
- .then(res => {
- if (res.data.code === 20000) {
- // 文件已经存在了,秒传(后端直接返回已上传的文件)
- resolve({
- uploaded: true,
- url: res.data.msg,
- code: res.data.code
- })
- } else if (res.data.code === 40004) {
- // 文件不存在需要上传
- resolve({ uploaded: false, url: '', code: res.data.code })
- } else {
- resolve({ uploaded: false, url: '', code: 500 })
- }
- })
- .catch(() => {
- resolve({ uploaded: false, url: '', code: 500 })
- })
- })
- }
- /**
- * @description: 执行分片上传
- * @param {*} file 上传的文件
- * @param {*} i 第几分片,从0开始
- * @param {*} md5 文件的md5值
- * @param {*} vm 虚拟 dom 指向组件 this
- * @returns {Promise}
- */
- function PostFile(timeSign,file, i, md5, vm) {
- const name = file.name // 文件名
- const size = file.size // 总大小
- const shardCount = Math.ceil(size / CHUNK_SIZE) // 总片数
- if (i >= shardCount) {
- return
- }
- const start = i * CHUNK_SIZE
- const end = start + CHUNK_SIZE
- const packet = file.slice(start, end) // 将文件进行切片
- const param = { "timeSign": timeSign, "shardCount": shardCount, "name": name, "index": i + 1, "fileSign": md5, "fileMd5": md5 }
- /* 构建form表单进行提交 */
- const form = new FormData()
- form.append('file', packet) // slice方法用于切出文件的一部分
- form.append('param', JSON.stringify(param)) // slice方法用于切出文件的一部分
- return new Promise((resolve, reject) => {
- handoutApi.commondecompression(form)
- .then(res => {
- if (res.code === 200) {
- // 拿到已上传过的切片
- resolve({
- uploadedList: res.data ? res.data.map(item => {
- return item.slice(0, item.lastIndexOf('.'))
- }) : []
- })
- } else {
- resolve({ uploadedList: [], code: 500 })
- // reject()
- }
- })
- .catch(() => {
- // reject()
- resolve({ uploadedList: [], code: 500 })
- })
- })
- }
- /**
- * @description: 合并文件
- * @param {*} shardCount 分片数
- * @param {*} fileName 文件名
- * @param {*} md5 文件md值
- * @param {*} fileType 文件类型
- * @param {*} fileSize 文件大小
- * @returns {Promise}
- */
- function merge(timeSign, fileMd5, name) {
- return new Promise((resolve, reject) => {
- handoutApi.commonmergefile({ timeSign, fileMd5, name }).then(res => {
- if (res.code === 200) {
- resolve(true)
- } else {
- resolve(false)
- }
- }).catch(() => {
- resolve(false)
- })
- })
- }
- export default {
- data() {
- return {
- chunks: [],
- percent: 0,
- percentCount: 0,
- stopUpload: false // 在需要的时机或场合阻止上传
- }
- },
- methods: {
- /**
- * @description: 上传文件
- * @param {*} file 文件
- * @returns {Object} 包含成功的文件地址、名称等
- */
- async chunksUpload(file, timeSign) {
- this.chunks = []
- // step1 获取文件切片
- const initChunks = createFileChunk(file)
- console.log(initChunks, "initChunks")
- // step2 获取文件 md5 值
- const md5 = await calculateFileMd5ByDefaultChunkSize(file)
- // step3 获取文件的上传状态
- // const { uploaded, url, code } = await checkMd5(md5, file)
- // if (uploaded) {
- // // step4 如果上传成功
- // this.percent = 100
- // // step5 拿到结果
- // return url
- // }
- // if (!uploaded && code === 500) {
- // return this.errorInfo()
- // }
- // step4 如果文件未传成功,执行切片上传
- const { uploadedList } = await PostFile(timeSign, file, 0, md5, this)
- // todo 方法1:逐次发送请求
- // const requestList = [] // 请求集合
- // initChunks.forEach(async (chunk, index) => {
- // // 过滤掉已上传的切片
- // if (uploadedList.indexOf(md5 + '_' + (index + 1)) < 0) {
- // const fn = () => {
- // return PostFile(file, index, md5, this)
- // }
- // requestList.push(fn)
- // }
- // })
- // let reqNum = 0 // 记录发送的请求个数
- // const send = async () => {
- // if (reqNum >= requestList.length) {
- // // step5 如果所有切片已上传,执行合并
- // const res = await merge(initChunks.length, file.name, md5, getFileType(file.name), file.size)
- // if (res.data.code === 20000) {
- // return res.data.msg
- // } else {
- // this.errorInfo()
- // return {}
- // }
- // }
- // if (this.stopUpload) return {} // 阻止上传
- // const sliceRes = await requestList[reqNum]()
- // if (sliceRes.code && sliceRes.code === 500) {
- // return this.errorInfo()
- // }
- // // 计算当下所上传切片数
- // const count = initChunks.length - uploadedList.length
- // if (this.percentCount === 0) {
- // this.percentCount = 100 / count
- // }
- // this.percent += this.percentCount
- // reqNum++
- // return send()
- // }
- // const mergeResult = await send()
- // return mergeResult
- // todo 方法2:使用Promise.all 统一发送请求
- const requestList = initChunks.map(async (chunk, index) => {
- // 过滤掉已上传的切片
- if (uploadedList.indexOf(md5 + '_' + (index + 1)) < 0) {
- return PostFile(timeSign, file, index, md5, this)
- }
- })
- return Promise.all(requestList)
- .then(async () => {
- const res = await merge(timeSign, md5, file.name, md5)
- console.log(res, "-------res")
- return { status: res, md5 }
- })
- .catch(() => {
- this.$message.error('出错了,请稍后重试!')
- return { status: false, md5 }
- })
- },
- /**
- * @description: 错误提示
- */
- errorInfo() {
- this.$message.error('出错了,请稍后重试!')
- }
- }
- }
|