You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

MinioUploader.vue 11 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <div class="dropzone-wrapper">
  3. <div id="dropzone" class="dropzone">
  4. </div>
  5. <p>文件处理状态:{{ status }}</p>
  6. <p>文件上传进度:{{ progress }}%</p>
  7. </div>
  8. </template>
  9. <script>
  10. // import Dropzone from 'dropzone/dist/dropzone.js';
  11. // import 'dropzone/dist/dropzone.css'
  12. import createDropzone from '../features/dropzone.js';
  13. import SparkMD5 from 'spark-md5';
  14. import axios from 'axios';
  15. import qs from 'qs';
  16. const {
  17. AppSubUrl,
  18. StaticUrlPrefix,
  19. csrf
  20. } = window.config;
  21. export default {
  22. data() {
  23. return {
  24. dropzoneUploader: null,
  25. maxFiles: 1,
  26. maxFilesize: 1 * 1024 * 1024 * 1024 * 1024,
  27. acceptedFiles: '*/*',
  28. progress: 0,
  29. status: '等待上传',
  30. }
  31. },
  32. async mounted() {
  33. const $dropzoneParams = $('div#minioUploader-params')
  34. const $dropzone = $('div#dropzone')
  35. const dropzoneUploader = await createDropzone($dropzone[0], {
  36. url: '/todouploader',
  37. maxFiles: this.maxFiles,
  38. maxFilesize: this.maxFileSize,
  39. timeout: 0,
  40. autoQueue: false,
  41. dictDefaultMessage: $dropzoneParams.data('default-message'),
  42. dictInvalidFileType: $dropzoneParams.data('invalid-input-type'),
  43. dictFileTooBig: $dropzoneParams.data('file-too-big'),
  44. dictRemoveFile: $dropzoneParams.data('remove-file'),
  45. })
  46. dropzoneUploader.on("addedfile", (file) => {
  47. setTimeout(() => {
  48. file.accepted && this.onFileAdded(file);
  49. }, 200);
  50. });
  51. dropzoneUploader.on("maxfilesexceeded", function(file) {
  52. if (this.files[0].status !== 'success') {
  53. alert('请等待第一个文件传输完成')
  54. this.removeFile(file)
  55. return
  56. }
  57. this.removeAllFiles();
  58. this.addFile(file);
  59. });
  60. this.dropzoneUploader = dropzoneUploader
  61. },
  62. methods: {
  63. resetStatus() {
  64. this.progress = 0
  65. this.status = ''
  66. },
  67. updateProgress(file, progress) {
  68. file.previewTemplate.querySelector(".dz-upload").style.width = `${progress}%`;
  69. },
  70. emitDropzoneSuccess(file) {
  71. file.status = "success";
  72. this.dropzoneUploader.emit("success", file);
  73. this.dropzoneUploader.emit("complete", file);
  74. },
  75. onFileAdded(file) {
  76. file.datasetId = document.getElementById("datasetId").getAttribute("datasetId");
  77. this.resetStatus()
  78. this.computeMD5(file);
  79. },
  80. finishUpload(file) {
  81. this.emitDropzoneSuccess(file)
  82. setTimeout(() => {
  83. window.location.reload();
  84. }, 1000);
  85. },
  86. computeMD5(file) {
  87. this.resetStatus()
  88. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  89. chunkSize = 1024 * 1024 * 64,
  90. chunks = Math.ceil(file.size / chunkSize),
  91. currentChunk = 0,
  92. spark = new SparkMD5.ArrayBuffer(),
  93. fileReader = new FileReader();
  94. let time = new Date().getTime();
  95. // console.log('计算MD5...')
  96. this.status = '计算MD5';
  97. file.totalChunkCounts = chunks;
  98. loadNext();
  99. fileReader.onload = (e) => {
  100. fileLoaded.call(this, e)
  101. };
  102. fileReader.onerror = (err) => {
  103. console.warn('oops, something went wrong.', err);
  104. file.cancel();
  105. };
  106. function fileLoaded(e){
  107. spark.append(e.target.result); // Append array buffer
  108. currentChunk++;
  109. if (currentChunk < chunks) {
  110. // console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1}/${chunks}分片解析`);
  111. this.status = `加载文件 ${(currentChunk/chunks*100).toFixed(2)}% (${currentChunk}/${chunks})`;
  112. this.updateProgress(file, (currentChunk/chunks*100).toFixed(2))
  113. loadNext();
  114. return
  115. }
  116. let md5 = spark.end();
  117. console.log(
  118. `MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`
  119. );
  120. spark.destroy(); //释放缓存
  121. file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
  122. file.cmd5 = false; //取消计算md5状态
  123. this.computeMD5Success(file);
  124. }
  125. function loadNext() {
  126. let start = currentChunk * chunkSize;
  127. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  128. fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
  129. }
  130. },
  131. async computeMD5Success(md5edFile) {
  132. const file = await this.getSuccessChunks(md5edFile);
  133. if (file.uploadID == "" || file.uuid == "") { //未上传过
  134. await this.newMultiUpload(file);
  135. if (file.uploadID != "" && file.uuid != "") {
  136. file.chunks = "";
  137. this.multipartUpload(file);
  138. } else {
  139. //失败如何处理
  140. return;
  141. }
  142. return
  143. }
  144. if (file.uploaded == "1") { //已上传成功
  145. //秒传
  146. if (file.attachID == "0") { //删除数据集记录,未删除文件
  147. await addAttachment(file);
  148. }
  149. console.log("文件已上传完成");
  150. this.progress = 100;
  151. this.status = '上传完成';
  152. this.finishUpload(file)
  153. } else {
  154. //断点续传
  155. this.multipartUpload(file);
  156. }
  157. async function addAttachment(file) {
  158. return await axios.post('/attachments/add', qs.stringify({
  159. uuid: file.uuid,
  160. file_name: file.name,
  161. size: file.size,
  162. dataset_id: file.datasetId,
  163. _csrf: csrf
  164. }))
  165. }
  166. },
  167. async getSuccessChunks(file) {
  168. const params = {
  169. params: {
  170. md5: file.uniqueIdentifier,
  171. _csrf: csrf
  172. }
  173. }
  174. try {
  175. const response = await axios.get('/attachments/get_chunks', params)
  176. file.uploadID = response.data.uploadID;
  177. file.uuid = response.data.uuid;
  178. file.uploaded = response.data.uploaded;
  179. file.chunks = response.data.chunks;
  180. file.attachID = response.data.attachID;
  181. return file
  182. } catch(error) {
  183. console.log("getSuccessChunks catch: ", error);
  184. return null
  185. }
  186. },
  187. async newMultiUpload(file) {
  188. const res = await axios.get('/attachments/new_multipart', {
  189. params: {
  190. totalChunkCounts: file.totalChunkCounts,
  191. md5: file.uniqueIdentifier,
  192. size: file.size,
  193. fileType: file.fileType,
  194. _csrf: csrf
  195. }
  196. })
  197. file.uploadID = res.data.uploadID;
  198. file.uuid = res.data.uuid;
  199. },
  200. multipartUpload(file) {
  201. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  202. chunkSize = 1024 * 1024 * 64,
  203. chunks = Math.ceil(file.size / chunkSize),
  204. currentChunk = 0,
  205. fileReader = new FileReader(),
  206. time = new Date().getTime();
  207. function loadNext() {
  208. let start = currentChunk * chunkSize;
  209. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  210. fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
  211. }
  212. function checkSuccessChunks() {
  213. var index = successChunks.indexOf((currentChunk + 1).toString())
  214. if (index == -1) {
  215. return false;
  216. }
  217. return true;
  218. }
  219. async function getUploadChunkUrl(currentChunk, partSize) {
  220. const res = await axios.get('/attachments/get_multipart_url', {
  221. params: {
  222. uuid: file.uuid,
  223. uploadID: file.uploadID,
  224. size: partSize,
  225. chunkNumber: currentChunk + 1,
  226. _csrf: csrf
  227. }
  228. })
  229. console.log("getUploadChunkUrl: ", res)
  230. urls[currentChunk] = res.data.url
  231. }
  232. async function uploadMinio(url, e) {
  233. const res = await axios.put(url, e.target.result)
  234. etags[currentChunk] = res.headers.etag;
  235. }
  236. async function updateChunk(currentChunk) {
  237. await axios.post('/attachments/update_chunk', qs.stringify({
  238. uuid: file.uuid,
  239. chunkNumber: currentChunk + 1,
  240. etag: etags[currentChunk],
  241. _csrf: csrf
  242. }))
  243. }
  244. async function uploadChunk(e) {
  245. if (!checkSuccessChunks()) {
  246. let start = currentChunk * chunkSize;
  247. let partSize = ((start + chunkSize) >= file.size) ? file.size - start : chunkSize;
  248. //获取分片上传url
  249. await getUploadChunkUrl(currentChunk, partSize);
  250. if (urls[currentChunk] != "") {
  251. //上传到minio
  252. await uploadMinio(urls[currentChunk], e);
  253. if (etags[currentChunk] != "") {
  254. //更新数据库:分片上传结果
  255. await updateChunk(currentChunk);
  256. } else {
  257. return;
  258. }
  259. } else {
  260. return;
  261. }
  262. }
  263. };
  264. async function completeUpload() {
  265. return await axios.post('/attachments/complete_multipart', qs.stringify({
  266. uuid: file.uuid,
  267. uploadID: file.uploadID,
  268. file_name: file.name,
  269. size: file.size,
  270. dataset_id: file.datasetId,
  271. _csrf: csrf
  272. }))
  273. }
  274. var successChunks = new Array();
  275. var successParts = new Array();
  276. successParts = file.chunks.split(",");
  277. for (let i = 0; i < successParts.length; i++) {
  278. successChunks[i] = successParts[i].split("-")[0].split("\"")[1];
  279. }
  280. var urls = new Array();
  281. var etags = new Array();
  282. console.log('上传分片...');
  283. this.status = '上传中'
  284. loadNext();
  285. fileReader.onload = async (e) => {
  286. await uploadChunk(e);
  287. currentChunk++;
  288. if (currentChunk < chunks) {
  289. console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1}/${chunks}个分片上传`);
  290. this.progress = Math.ceil((currentChunk / chunks) * 100);
  291. this.updateProgress(file, (currentChunk/chunks*100).toFixed(2))
  292. await loadNext();
  293. } else {
  294. await completeUpload();
  295. console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`);
  296. this.progress = 100;
  297. this.status = '上传完成';
  298. this.finishUpload(file)
  299. }
  300. };
  301. },
  302. }
  303. }
  304. </script>
  305. <style>
  306. .dropzone-wrapper {
  307. margin: 2em auto ;
  308. }
  309. .ui .dropzone {
  310. border: 2px dashed #0087f5;
  311. box-shadow: none !important;
  312. padding: 0;
  313. min-height: 5rem;
  314. border-radius: 4px;
  315. }
  316. </style>