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 10 kB

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