fpFile.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * @Description: 大文件上传、分片上传、断点续传、文件秒传
  3. * @Author: zhangy
  4. * @Date: 2022-05-16 13:10:13
  5. * @LastEditors: zhangy
  6. * @LastEditTime: 2022-05-19 10:14:33
  7. */
  8. const SparkMD5 = require('spark-md5')
  9. import handoutApi from '@/newApi/handout'
  10. import axios from 'axios'
  11. // 切片大小(单位:B)
  12. const CHUNK_SIZE = 50 * 1024 * 1024
  13. // const CHUNK_SIZE = 24 * 1024
  14. /**
  15. * @description: 分块计算文件的md5值
  16. * @param {*} file 文件
  17. * @param {*} chunkSize 分片大小
  18. * @returns {*}
  19. */
  20. function calculateFileMd5(file, chunkSize) {
  21. return new Promise((resolve, reject) => {
  22. const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
  23. const chunks = Math.ceil(file.size / chunkSize)
  24. let currentChunk = 0
  25. const spark = new SparkMD5.ArrayBuffer()
  26. const fileReader = new FileReader()
  27. fileReader.onload = function (e) {
  28. spark.append(e.target.result)
  29. currentChunk++
  30. if (currentChunk < chunks) {
  31. loadNext()
  32. } else {
  33. const md5 = spark.end()
  34. resolve(md5)
  35. }
  36. }
  37. fileReader.onerror = function (e) {
  38. reject(e)
  39. }
  40. function loadNext() {
  41. const start = currentChunk * chunkSize
  42. let end = start + chunkSize
  43. if (end > file.size) {
  44. end = file.size
  45. }
  46. fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
  47. }
  48. loadNext()
  49. })
  50. }
  51. /**
  52. * @description: 分块计算文件的md5值
  53. * @param {*} file 文件
  54. * @returns {Promise}
  55. */
  56. function calculateFileMd5ByDefaultChunkSize(file) {
  57. return calculateFileMd5(file, CHUNK_SIZE)
  58. }
  59. /**
  60. * @description: 文件切片
  61. * @param {*} file
  62. * @param {*} size 切片大小
  63. * @returns [{file}]
  64. */
  65. function createFileChunk(file, size = CHUNK_SIZE) {
  66. const chunks = []
  67. let cur = 0
  68. while (cur < file.size) {
  69. chunks.push({ file: file.slice(cur, cur + size) })
  70. cur += size
  71. }
  72. return chunks
  73. }
  74. /**
  75. * @description: 获取文件的后缀名
  76. */
  77. function getFileType(fileName) {
  78. return fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase()
  79. }
  80. /**
  81. * @description: 根据文件的md5值判断文件是否已经上传过了
  82. * @param {*} md5 文件的md5
  83. * @param {*} 准备上传的文件
  84. * @returns {Promise}
  85. */
  86. function checkMd5(md5, file) {
  87. return new Promise(resolve => {
  88. getUploadStatus({ md5 })
  89. .then(res => {
  90. if (res.data.code === 20000) {
  91. // 文件已经存在了,秒传(后端直接返回已上传的文件)
  92. resolve({
  93. uploaded: true,
  94. url: res.data.msg,
  95. code: res.data.code
  96. })
  97. } else if (res.data.code === 40004) {
  98. // 文件不存在需要上传
  99. resolve({ uploaded: false, url: '', code: res.data.code })
  100. } else {
  101. resolve({ uploaded: false, url: '', code: 500 })
  102. }
  103. })
  104. .catch(() => {
  105. resolve({ uploaded: false, url: '', code: 500 })
  106. })
  107. })
  108. }
  109. /**
  110. * @description: 执行分片上传
  111. * @param {*} file 上传的文件
  112. * @param {*} i 第几分片,从0开始
  113. * @param {*} md5 文件的md5值
  114. * @param {*} vm 虚拟 dom 指向组件 this
  115. * @returns {Promise}
  116. */
  117. function PostFile(timeSign,file, i, md5, vm) {
  118. const name = file.name // 文件名
  119. const size = file.size // 总大小
  120. const shardCount = Math.ceil(size / CHUNK_SIZE) // 总片数
  121. if (i >= shardCount) {
  122. return
  123. }
  124. const start = i * CHUNK_SIZE
  125. const end = start + CHUNK_SIZE
  126. const packet = file.slice(start, end) // 将文件进行切片
  127. const param = { "timeSign": timeSign, "shardCount": shardCount, "name": name, "index": i + 1, "fileSign": md5, "fileMd5": md5 }
  128. /* 构建form表单进行提交 */
  129. const form = new FormData()
  130. form.append('file', packet) // slice方法用于切出文件的一部分
  131. form.append('param', JSON.stringify(param)) // slice方法用于切出文件的一部分
  132. return new Promise((resolve, reject) => {
  133. handoutApi.commondecompression(form)
  134. .then(res => {
  135. if (res.code === 200) {
  136. // 拿到已上传过的切片
  137. resolve({
  138. uploadedList: res.data ? res.data.map(item => {
  139. return item.slice(0, item.lastIndexOf('.'))
  140. }) : []
  141. })
  142. } else {
  143. resolve({ uploadedList: [], code: 500 })
  144. // reject()
  145. }
  146. })
  147. .catch(() => {
  148. // reject()
  149. resolve({ uploadedList: [], code: 500 })
  150. })
  151. })
  152. }
  153. /**
  154. * @description: 合并文件
  155. * @param {*} shardCount 分片数
  156. * @param {*} fileName 文件名
  157. * @param {*} md5 文件md值
  158. * @param {*} fileType 文件类型
  159. * @param {*} fileSize 文件大小
  160. * @returns {Promise}
  161. */
  162. function merge(timeSign, fileMd5, name) {
  163. return new Promise((resolve, reject) => {
  164. handoutApi.commonmergefile({ timeSign, fileMd5, name }).then(res => {
  165. if (res.code === 200) {
  166. resolve(true)
  167. } else {
  168. resolve(false)
  169. }
  170. }).catch(() => {
  171. resolve(false)
  172. })
  173. })
  174. }
  175. export default {
  176. data() {
  177. return {
  178. chunks: [],
  179. percent: 0,
  180. percentCount: 0,
  181. stopUpload: false // 在需要的时机或场合阻止上传
  182. }
  183. },
  184. methods: {
  185. /**
  186. * @description: 上传文件
  187. * @param {*} file 文件
  188. * @returns {Object} 包含成功的文件地址、名称等
  189. */
  190. async chunksUpload(file, timeSign) {
  191. this.chunks = []
  192. // step1 获取文件切片
  193. const initChunks = createFileChunk(file)
  194. console.log(initChunks, "initChunks")
  195. // step2 获取文件 md5 值
  196. const md5 = await calculateFileMd5ByDefaultChunkSize(file)
  197. // step3 获取文件的上传状态
  198. // const { uploaded, url, code } = await checkMd5(md5, file)
  199. // if (uploaded) {
  200. // // step4 如果上传成功
  201. // this.percent = 100
  202. // // step5 拿到结果
  203. // return url
  204. // }
  205. // if (!uploaded && code === 500) {
  206. // return this.errorInfo()
  207. // }
  208. // step4 如果文件未传成功,执行切片上传
  209. const { uploadedList } = await PostFile(timeSign, file, 0, md5, this)
  210. // todo 方法1:逐次发送请求
  211. // const requestList = [] // 请求集合
  212. // initChunks.forEach(async (chunk, index) => {
  213. // // 过滤掉已上传的切片
  214. // if (uploadedList.indexOf(md5 + '_' + (index + 1)) < 0) {
  215. // const fn = () => {
  216. // return PostFile(file, index, md5, this)
  217. // }
  218. // requestList.push(fn)
  219. // }
  220. // })
  221. // let reqNum = 0 // 记录发送的请求个数
  222. // const send = async () => {
  223. // if (reqNum >= requestList.length) {
  224. // // step5 如果所有切片已上传,执行合并
  225. // const res = await merge(initChunks.length, file.name, md5, getFileType(file.name), file.size)
  226. // if (res.data.code === 20000) {
  227. // return res.data.msg
  228. // } else {
  229. // this.errorInfo()
  230. // return {}
  231. // }
  232. // }
  233. // if (this.stopUpload) return {} // 阻止上传
  234. // const sliceRes = await requestList[reqNum]()
  235. // if (sliceRes.code && sliceRes.code === 500) {
  236. // return this.errorInfo()
  237. // }
  238. // // 计算当下所上传切片数
  239. // const count = initChunks.length - uploadedList.length
  240. // if (this.percentCount === 0) {
  241. // this.percentCount = 100 / count
  242. // }
  243. // this.percent += this.percentCount
  244. // reqNum++
  245. // return send()
  246. // }
  247. // const mergeResult = await send()
  248. // return mergeResult
  249. // todo 方法2:使用Promise.all 统一发送请求
  250. const requestList = initChunks.map(async (chunk, index) => {
  251. // 过滤掉已上传的切片
  252. if (uploadedList.indexOf(md5 + '_' + (index + 1)) < 0) {
  253. return PostFile(timeSign, file, index, md5, this)
  254. }
  255. })
  256. return Promise.all(requestList)
  257. .then(async () => {
  258. const res = await merge(timeSign, md5, file.name, md5)
  259. console.log(res, "-------res")
  260. return { status: res, md5 }
  261. })
  262. .catch(() => {
  263. this.$message.error('出错了,请稍后重试!')
  264. return { status: false, md5 }
  265. })
  266. },
  267. /**
  268. * @description: 错误提示
  269. */
  270. errorInfo() {
  271. this.$message.error('出错了,请稍后重试!')
  272. }
  273. }
  274. }