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.

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