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