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

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