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