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

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