当前位置:   article > 正文

vue 大文件分片上传 - 断点续传、并发上传、秒传_vue后台管理大文件上传的场景

vue后台管理大文件上传的场景

       对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。

 本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章:

springboot 大文件上传、分片上传、断点续传、秒传https://blog.csdn.net/qq_43040552/article/details/122510154

上传分步:

本人分析上传总共分为:

  1. MD5读取文件,获取文件的MD5编码
  2. 请求服务端判断文件是否上传,如上传完成就直接返回文件地址
  3. 如未上传,判断是否是断点续传
  4. 判断是并发上传还是顺序上传
  5. 开始分片文件上传,分片上传完成后写入已上传列表中
  6. 判断是否上传完成

直接上代码

文件上传:

  1. import md5 from 'js-md5' //引入MD5加密
  2. import UpApi from '@/api/common.js'
  3. import { concurrentExecution } from '@/utils/jnxh'
  4. /**
  5. * 文件分片上传
  6. * @params file {File} 文件
  7. * @params pieceSize {Number} 分片大小 默认3MB
  8. * @params concurrent {Number} 并发数量 默认2
  9. * @params process {Function} 进度回调函数
  10. * @params success {Function} 成功回调函数
  11. * @params error {Function} 失败回调函数
  12. */
  13. export const uploadByPieces = ({
  14. file,
  15. pieceSize = 3,
  16. concurrent = 3,
  17. success,
  18. process,
  19. error
  20. }) => {
  21. // 如果文件传入为空直接 return 返回
  22. if (!file || file.length < 1) {
  23. return error('文件不能为空')
  24. }
  25. let fileMD5 = '' // 总文件列表
  26. const chunkSize = pieceSize * 1024 * 1024 // 1MB一片
  27. const chunkCount = Math.ceil(file.size / chunkSize) // 总片数
  28. const chunkList = [] // 分片列表
  29. let uploaded = [] // 已经上传的
  30. let fileType = '' // 文件类型
  31. // 获取md5
  32. /***
  33. * 获取md5
  34. **/
  35. const readFileMD5 = () => {
  36. // 读取视频文件的md5
  37. fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
  38. console.log('获取文件的MD5值')
  39. let fileRederInstance = new FileReader()
  40. console.log('file', file)
  41. fileRederInstance.readAsBinaryString(file)
  42. fileRederInstance.addEventListener('load', e => {
  43. let fileBolb = e.target.result
  44. fileMD5 = md5(fileBolb)
  45. var index = file.name.lastIndexOf('.')
  46. var tp = file.name.substring(index + 1, file.name.length)
  47. let form = new FormData()
  48. form.append('filename', file.name)
  49. form.append('identifier', fileMD5)
  50. form.append('objectType', fileType)
  51. form.append('chunkNumber', 1)
  52. UpApi.uploadChunk(form).then(res => {
  53. if (res.skipUpload) {
  54. console.log('文件已被上传')
  55. success && success(res)
  56. } else {
  57. // 判断是否是断点续传
  58. if (res.uploaded && res.uploaded.length != 0) {
  59. uploaded = [].concat(res.uploaded)
  60. }
  61. console.log('已上传的分片:' + uploaded)
  62. // 判断是并发上传或顺序上传
  63. if (concurrent == 1 || chunkCount == 1) {
  64. console.log('顺序上传')
  65. sequentialUplode(0)
  66. } else {
  67. console.log('并发上传')
  68. concurrentUpload()
  69. }
  70. }
  71. }).catch((e) => {
  72. console.log('文件合并错误')
  73. console.log(e)
  74. })
  75. })
  76. }
  77. /***
  78. * 获取每一个分片的详情
  79. **/
  80. const getChunkInfo = (file, currentChunk, chunkSize) => {
  81. let start = currentChunk * chunkSize
  82. let end = Math.min(file.size, start + chunkSize)
  83. let chunk = file.slice(start, end)
  84. return {
  85. start,
  86. end,
  87. chunk
  88. }
  89. }
  90. /***
  91. * 针对每个文件进行chunk处理
  92. **/
  93. const readChunkMD5 = () => {
  94. // 针对单个文件进行chunk上传
  95. for (var i = 0; i < chunkCount; i++) {
  96. const {
  97. chunk
  98. } = getChunkInfo(file, i, chunkSize)
  99. // 判断已经上传的分片中是否包含当前分片
  100. if (uploaded.indexOf(i + '') == -1) {
  101. uploadChunk({
  102. chunk,
  103. currentChunk: i,
  104. chunkCount
  105. })
  106. }
  107. }
  108. }
  109. /***
  110. * 原始上传
  111. **/
  112. const uploadChunk = (chunkInfo) => {
  113. var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
  114. console.log(sd, '进度')
  115. process(sd)
  116. console.log(chunkInfo, '分片大小')
  117. let inde = chunkInfo.currentChunk + 1
  118. if (uploaded.indexOf(inde + '') > -1) {
  119. const {
  120. chunk
  121. } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
  122. uploadChunk({
  123. chunk,
  124. currentChunk: inde,
  125. chunkCount
  126. })
  127. } else {
  128. var index = file.name.lastIndexOf('.')
  129. var tp = file.name.substring(index + 1, file.name.length)
  130. // 构建上传文件的formData
  131. let fetchForm = new FormData()
  132. fetchForm.append('identifier', fileMD5)
  133. fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
  134. fetchForm.append('chunkSize', chunkSize)
  135. fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
  136. const chunkfile = new File([chunkInfo.chunk], file.name)
  137. fetchForm.append('file', chunkfile)
  138. // fetchForm.append('file', chunkInfo.chunk)
  139. fetchForm.append('filename', file.name)
  140. fetchForm.append('relativePath', file.name)
  141. fetchForm.append('totalChunks', chunkInfo.chunkCount)
  142. fetchForm.append('totalSize', file.size)
  143. fetchForm.append('objectType', tp)
  144. // 执行分片上传
  145. let config = {
  146. headers: {
  147. 'Content-Type': 'application/json',
  148. 'Accept': '*/*'
  149. }
  150. }
  151. UpApi.uploadChunk(fetchForm, config).then(res => {
  152. if (res.code == 200) {
  153. console.log('分片上传成功')
  154. uploaded.push(chunkInfo.currentChunk + 1)
  155. // 判断是否全部上传完
  156. if (uploaded.length == chunkInfo.chunkCount) {
  157. console.log('全部完成')
  158. success(res)
  159. process(100)
  160. } else {
  161. const {
  162. chunk
  163. } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize)
  164. uploadChunk({
  165. chunk,
  166. currentChunk: chunkInfo.currentChunk + 1,
  167. chunkCount
  168. })
  169. }
  170. } else {
  171. console.log(res.msg)
  172. }
  173. }).catch((e) => {
  174. error && error(e)
  175. })
  176. // if (chunkInfo.currentChunk < chunkInfo.chunkCount) {
  177. // setTimeout(() => {
  178. //
  179. // }, 1000)
  180. // }
  181. }
  182. }
  183. /***
  184. * 顺序上传
  185. **/
  186. const sequentialUplode = (currentChunk) => {
  187. const {
  188. chunk
  189. } = getChunkInfo(file, currentChunk, chunkSize)
  190. let chunkInfo = {
  191. chunk,
  192. currentChunk,
  193. chunkCount
  194. }
  195. var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
  196. process(sd)
  197. console.log('当前上传分片:' + currentChunk)
  198. let inde = chunkInfo.currentChunk + 1
  199. if (uploaded.indexOf(inde + '') > -1) {
  200. console.log('分片【' + currentChunk + '】已上传')
  201. sequentialUplode(currentChunk + 1)
  202. } else {
  203. let uploadData = createUploadData(chunkInfo)
  204. let config = {
  205. headers: {
  206. 'Content-Type': 'application/json',
  207. 'Accept': '*/*'
  208. }
  209. }
  210. // 执行分片上传
  211. UpApi.uploadChunk(uploadData, config).then(res => {
  212. if (res.code == 200) {
  213. console.log('分片【' + currentChunk + '】上传成功')
  214. uploaded.push(chunkInfo.currentChunk + 1)
  215. // 判断是否全部上传完
  216. if (uploaded.length == chunkInfo.chunkCount) {
  217. console.log('全部完成')
  218. success(res)
  219. process(100)
  220. } else {
  221. sequentialUplode(currentChunk + 1)
  222. }
  223. } else {
  224. console.log(res.msg)
  225. }
  226. }).catch((e) => {
  227. error && error(e)
  228. })
  229. }
  230. }
  231. /***
  232. * 并发上传
  233. **/
  234. const concurrentUpload = () => {
  235. for (var i = 0; i < chunkCount; i++) {
  236. chunkList.push(Number(i))
  237. }
  238. console.log('需要上传的分片列表:' + chunkList)
  239. concurrentExecution(chunkList, concurrent, (curItem) => {
  240. return new Promise((resolve, reject) => {
  241. const {
  242. chunk
  243. } = getChunkInfo(file, curItem, chunkSize)
  244. let chunkInfo = {
  245. chunk,
  246. currentChunk: curItem,
  247. chunkCount
  248. }
  249. var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100)
  250. process(sd)
  251. console.log('当前上传分片:' + curItem)
  252. let inde = chunkInfo.currentChunk + 1
  253. if (uploaded.indexOf(inde + '') == -1) {
  254. // 构建上传文件的formData
  255. let uploadData = createUploadData(chunkInfo)
  256. // 请求头
  257. let config = {
  258. headers: {
  259. 'Content-Type': 'application/json',
  260. 'Accept': '*/*'
  261. }
  262. }
  263. UpApi.uploadChunk(uploadData, config).then(res => {
  264. if (res.code == 200) {
  265. uploaded.push(chunkInfo.currentChunk + 1)
  266. console.log('已经上传完成的分片:' + uploaded)
  267. // 判断是否全部上传完
  268. if (uploaded.length == chunkInfo.chunkCount) {
  269. success(res)
  270. process(100)
  271. }
  272. resolve()
  273. } else {
  274. reject(res)
  275. console.log(res.msg)
  276. }
  277. }).catch((e) => {
  278. reject(res)
  279. error && error(e)
  280. })
  281. } else {
  282. console.log('分片【' + chunkInfo.currentChunk + '】已上传')
  283. resolve()
  284. }
  285. })
  286. }).then(res => {
  287. console.log('finish', res)
  288. })
  289. }
  290. /***
  291. * 创建文件上传参数
  292. **/
  293. const createUploadData = (chunkInfo) => {
  294. let fetchForm = new FormData()
  295. fetchForm.append('identifier', fileMD5)
  296. fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1)
  297. fetchForm.append('chunkSize', chunkSize)
  298. fetchForm.append('currentChunkSize', chunkInfo.chunk.size)
  299. const chunkfile = new File([chunkInfo.chunk], file.name)
  300. fetchForm.append('file', chunkfile)
  301. // fetchForm.append('file', chunkInfo.chunk)
  302. fetchForm.append('filename', file.name)
  303. fetchForm.append('relativePath', file.name)
  304. fetchForm.append('totalChunks', chunkInfo.chunkCount)
  305. fetchForm.append('totalSize', file.size)
  306. fetchForm.append('objectType', fileType)
  307. return fetchForm
  308. }
  309. readFileMD5() // 开始执行代码
  310. }

并发控制:

  1. /**
  2. * 并发执行
  3. * @params list {Array} - 要迭代的数组
  4. * @params limit {Number} - 并发数量控制数,最好小于3
  5. * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代
  6. * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
  7. */
  8. export function concurrentExecution(list, limit, asyncHandle) {
  9. // 递归执行
  10. let recursion = (arr) => {
  11. // 执行方法 arr.shift() 取出并移除第一个数据
  12. return asyncHandle(arr.shift()).then(() => {
  13. // 数组还未迭代完,递归继续进行迭代
  14. if (arr.length !== 0) {
  15. return recursion(arr)
  16. } else {
  17. return 'finish'
  18. }
  19. })
  20. }
  21. // 创建新的并发数组
  22. let listCopy = [].concat(list)
  23. // 正在进行的所有并发异步操作
  24. let asyncList = []
  25. limit = limit > listCopy.length ? listCopy.length : limit
  26. console.log(limit)
  27. while (limit--) {
  28. asyncList.push(recursion(listCopy))
  29. }
  30. // 所有并发异步操作都完成后,本次并发控制迭代完成
  31. return Promise.all(asyncList)
  32. }

Gitee:文件分片上传https://gitee.com/wangxmsn/domes/tree/develop/file-sharding-upload

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

闽ICP备14008679号