| @@ -1797,7 +1797,8 @@ | |||||
| "assert-plus": { | "assert-plus": { | ||||
| "version": "1.0.0", | "version": "1.0.0", | ||||
| "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", | ||||
| "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" | |||||
| "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "assign-symbols": { | "assign-symbols": { | ||||
| "version": "1.0.0", | "version": "1.0.0", | ||||
| @@ -2854,6 +2855,7 @@ | |||||
| "version": "1.0.8", | "version": "1.0.8", | ||||
| "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", | ||||
| "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", | ||||
| "optional": true, | |||||
| "requires": { | "requires": { | ||||
| "delayed-stream": "~1.0.0" | "delayed-stream": "~1.0.0" | ||||
| } | } | ||||
| @@ -3590,7 +3592,8 @@ | |||||
| "delayed-stream": { | "delayed-stream": { | ||||
| "version": "1.0.0", | "version": "1.0.0", | ||||
| "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", | ||||
| "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" | |||||
| "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "delegate": { | "delegate": { | ||||
| "version": "3.2.0", | "version": "3.2.0", | ||||
| @@ -3744,9 +3747,9 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "dropzone": { | "dropzone": { | ||||
| "version": "5.7.0", | |||||
| "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.7.0.tgz", | |||||
| "integrity": "sha512-kOltiZXH5cO/72I22JjE+w6BoT6uaVLfWdFMsi1PMKFkU6BZWpqRwjnsRm0o6ANGTBuZar5Piu7m/CbKqRPiYg==" | |||||
| "version": "5.7.2", | |||||
| "resolved": "https://registry.npm.taobao.org/dropzone/download/dropzone-5.7.2.tgz?cache=0&sync_timestamp=1596009792692&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdropzone%2Fdownload%2Fdropzone-5.7.2.tgz", | |||||
| "integrity": "sha1-kb7hVy3aUV1AkB2jBLx53d8wm0w=" | |||||
| }, | }, | ||||
| "duplexer2": { | "duplexer2": { | ||||
| "version": "0.0.2", | "version": "0.0.2", | ||||
| @@ -4806,7 +4809,8 @@ | |||||
| "extsprintf": { | "extsprintf": { | ||||
| "version": "1.3.0", | "version": "1.3.0", | ||||
| "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", | ||||
| "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" | |||||
| "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "fancy-log": { | "fancy-log": { | ||||
| "version": "1.3.3", | "version": "1.3.3", | ||||
| @@ -7621,7 +7625,8 @@ | |||||
| "jsbn": { | "jsbn": { | ||||
| "version": "0.1.1", | "version": "0.1.1", | ||||
| "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", | ||||
| "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" | |||||
| "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "jsesc": { | "jsesc": { | ||||
| "version": "2.5.2", | "version": "2.5.2", | ||||
| @@ -8677,12 +8682,14 @@ | |||||
| "mime-db": { | "mime-db": { | ||||
| "version": "1.44.0", | "version": "1.44.0", | ||||
| "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", | ||||
| "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" | |||||
| "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "mime-types": { | "mime-types": { | ||||
| "version": "2.1.27", | "version": "2.1.27", | ||||
| "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", | ||||
| "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", | ||||
| "optional": true, | |||||
| "requires": { | "requires": { | ||||
| "mime-db": "1.44.0" | "mime-db": "1.44.0" | ||||
| } | } | ||||
| @@ -13654,7 +13661,8 @@ | |||||
| "tweetnacl": { | "tweetnacl": { | ||||
| "version": "0.14.5", | "version": "0.14.5", | ||||
| "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", | ||||
| "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" | |||||
| "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", | |||||
| "optional": true | |||||
| }, | }, | ||||
| "type": { | "type": { | ||||
| "version": "1.2.0", | "version": "1.2.0", | ||||
| @@ -18,7 +18,7 @@ | |||||
| "css-loader": "3.5.3", | "css-loader": "3.5.3", | ||||
| "cssnano": "4.1.10", | "cssnano": "4.1.10", | ||||
| "domino": "2.1.5", | "domino": "2.1.5", | ||||
| "dropzone": "5.7.0", | |||||
| "dropzone": "5.7.2", | |||||
| "fast-glob": "3.2.2", | "fast-glob": "3.2.2", | ||||
| "file-loader": "6.0.0", | "file-loader": "6.0.0", | ||||
| "fomantic-ui": "2.8.4", | "fomantic-ui": "2.8.4", | ||||
| @@ -1,362 +0,0 @@ | |||||
| <template> | |||||
| <uploader | |||||
| ref="uploader" | |||||
| :options="options" | |||||
| :autoStart="false" | |||||
| @file-added="onFileAdded" | |||||
| fileStatusText="fileStatusText" | |||||
| class="uploader-app"> | |||||
| <uploader-unsupport></uploader-unsupport> | |||||
| <uploader-drop> | |||||
| <p>拖动文件</p> | |||||
| <uploader-btn>选择文件</uploader-btn> | |||||
| </uploader-drop> | |||||
| <uploader-list></uploader-list> | |||||
| <p>文件处理状态:{{status}}</p> | |||||
| <p>文件上传进度:{{progress}}%</p> | |||||
| </uploader> | |||||
| </template> | |||||
| <script> | |||||
| import SparkMD5 from 'spark-md5'; | |||||
| import axios from 'axios' | |||||
| import qs from 'qs' | |||||
| const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||||
| export default { | |||||
| data () { | |||||
| return { | |||||
| attrs: { | |||||
| accept: '*' | |||||
| }, | |||||
| fileStatusText: { | |||||
| success: '上传成功', | |||||
| error: '上传出错了', | |||||
| uploading: '上传中...', | |||||
| paused: '暂停', | |||||
| waiting: '等待中...', | |||||
| cmd5: '计算md5...' | |||||
| }, | |||||
| fileStatusText: (status, response) => { | |||||
| return this.fileStatusText[status]; | |||||
| }, | |||||
| progress: 0, | |||||
| status: '初始状态', | |||||
| } | |||||
| }, | |||||
| created() { | |||||
| //const uploaderInstance = this.$refs.uploader; | |||||
| }, | |||||
| mounted () { | |||||
| this.$nextTick(() => { | |||||
| window.uploader = this.$refs.uploader.uploader | |||||
| }) | |||||
| }, | |||||
| methods: { | |||||
| onFileAdded(file) { | |||||
| file.datasetId = document.getElementById("datasetId").getAttribute("datasetId"); | |||||
| this.progress=0; | |||||
| this.status='初始状态'; | |||||
| // 计算MD5 | |||||
| this.computeMD5(file); | |||||
| }, | |||||
| getSuccessChunks(file) { | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.get('/attachments/get_chunks', {params :{ | |||||
| md5: file.uniqueIdentifier, | |||||
| _csrf: csrf | |||||
| }}).then(function (response) { | |||||
| file.uploadID = response.data.uploadID; | |||||
| file.uuid = response.data.uuid; | |||||
| file.uploaded = response.data.uploaded; | |||||
| file.chunks = response.data.chunks; | |||||
| file.attachID = response.data.attachID; | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| }, | |||||
| newMultiUpload(file) { | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.get('/attachments/new_multipart', {params :{ | |||||
| totalChunkCounts: file.totalChunkCounts, | |||||
| md5: file.uniqueIdentifier, | |||||
| size: file.size, | |||||
| fileType: file.fileType, | |||||
| _csrf: csrf | |||||
| }}).then(function (response) { | |||||
| file.uploadID = response.data.uploadID; | |||||
| file.uuid = response.data.uuid; | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| }, | |||||
| multipartUpload(file) { | |||||
| let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, | |||||
| chunkSize = 1024*1024*64, | |||||
| chunks = Math.ceil(file.size / chunkSize), | |||||
| currentChunk = 0, | |||||
| fileReader = new FileReader(), | |||||
| time = new Date().getTime(); | |||||
| function loadNext() { | |||||
| let start = currentChunk * chunkSize; | |||||
| let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; | |||||
| fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end)); | |||||
| } | |||||
| function checkSuccessChunks() { | |||||
| var index = successChunks.indexOf((currentChunk+1).toString()) | |||||
| if (index == -1) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| function getUploadChunkUrl(currentChunk, partSize) { | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.get('/attachments/get_multipart_url', {params :{ | |||||
| uuid: file.uuid, | |||||
| uploadID: file.uploadID, | |||||
| size: partSize, | |||||
| chunkNumber: currentChunk+1, | |||||
| _csrf: csrf | |||||
| }}).then(function (response) { | |||||
| urls[currentChunk] = response.data.url | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| } | |||||
| function uploadMinio(url, e) { | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.put(url, e.target.result | |||||
| ).then(function (res) { | |||||
| etags[currentChunk] = res.headers.etag; | |||||
| resolve(res); | |||||
| }).catch(function (err) { | |||||
| console.log(err); | |||||
| reject(err); | |||||
| }); | |||||
| }); | |||||
| } | |||||
| function updateChunk(currentChunk) { | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.post('/attachments/update_chunk', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| chunkNumber: currentChunk+1, | |||||
| etag: etags[currentChunk], | |||||
| _csrf: csrf | |||||
| })).then(function (response) { | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| } | |||||
| async function uploadChunk(e) { | |||||
| if (!checkSuccessChunks()) { | |||||
| let start = currentChunk * chunkSize; | |||||
| let partSize = ((start + chunkSize) >= file.size) ? file.size -start : chunkSize; | |||||
| //获取分片上传url | |||||
| await getUploadChunkUrl(currentChunk, partSize); | |||||
| if (urls[currentChunk] != "") { | |||||
| //上传到minio | |||||
| await uploadMinio(urls[currentChunk], e); | |||||
| if (etags[currentChunk] != "") { | |||||
| //更新数据库:分片上传结果 | |||||
| await updateChunk(currentChunk); | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } | |||||
| }; | |||||
| function completeUpload(){ | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.post('/attachments/complete_multipart', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| uploadID: file.uploadID, | |||||
| file_name: file.name, | |||||
| size: file.size, | |||||
| dataset_id: file.datasetId, | |||||
| _csrf: csrf | |||||
| })).then(function (response) { | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| } | |||||
| var successChunks = new Array(); | |||||
| var successParts = new Array(); | |||||
| successParts = file.chunks.split(","); | |||||
| for (let i = 0; i < successParts.length; i++) { | |||||
| successChunks[i] = successParts[i].split("-")[0].split("\"")[1]; | |||||
| } | |||||
| var urls = new Array(); | |||||
| var etags = new Array(); | |||||
| console.log('上传分片...'); | |||||
| this.status='上传中'; | |||||
| { | |||||
| loadNext(); | |||||
| fileReader.onload = async (e) => { | |||||
| await uploadChunk(e); | |||||
| currentChunk++; | |||||
| if (currentChunk < chunks) { | |||||
| console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1}/${chunks}个分片上传`); | |||||
| this.progress = Math.ceil((currentChunk / chunks)*100); | |||||
| await loadNext(); | |||||
| } else { | |||||
| await completeUpload(); | |||||
| console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`); | |||||
| this.progress = 100; | |||||
| this.status='上传完成'; | |||||
| window.location.reload(); | |||||
| } | |||||
| }; | |||||
| } | |||||
| }, | |||||
| //计算MD5 | |||||
| computeMD5(file) { | |||||
| let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, | |||||
| chunkSize = 1024*1024*64, | |||||
| chunks = Math.ceil(file.size / chunkSize), | |||||
| currentChunk = 0, | |||||
| spark = new SparkMD5.ArrayBuffer(), | |||||
| fileReader = new FileReader(); | |||||
| let time = new Date().getTime(); | |||||
| console.log('计算MD5...') | |||||
| this.status='计算MD5'; | |||||
| file.totalChunkCounts = chunks; | |||||
| loadNext(); | |||||
| fileReader.onload = (e) => { | |||||
| spark.append(e.target.result); // Append array buffer | |||||
| currentChunk++; | |||||
| if (currentChunk < chunks) { | |||||
| console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1}/${chunks}分片解析`); | |||||
| loadNext(); | |||||
| } else { | |||||
| let md5 = spark.end(); | |||||
| console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`); | |||||
| spark.destroy(); //释放缓存 | |||||
| file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识 | |||||
| file.cmd5 = false; //取消计算md5状态 | |||||
| this.computeMD5Success(file); | |||||
| } | |||||
| }; | |||||
| fileReader.onerror = () => { | |||||
| console.warn('oops, something went wrong.'); | |||||
| file.cancel(); | |||||
| }; | |||||
| function loadNext() { | |||||
| let start = currentChunk * chunkSize; | |||||
| let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; | |||||
| fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end)); | |||||
| } | |||||
| }, | |||||
| async computeMD5Success(file) { | |||||
| await this.getSuccessChunks(file); | |||||
| if (file.uploadID == "" || file.uuid == "") { //未上传过 | |||||
| await this.newMultiUpload(file); | |||||
| if (file.uploadID != "" && file.uuid != "") { | |||||
| file.chunks = ""; | |||||
| this.multipartUpload(file); | |||||
| } else { | |||||
| //失败如何处理 | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| if (file.uploaded == "1") { //已上传成功 | |||||
| //秒传 | |||||
| if (file.attachID == "0") { //删除数据集记录,未删除文件 | |||||
| await addAttachment(file); | |||||
| } | |||||
| console.log("文件已上传完成"); | |||||
| this.progress = 100; | |||||
| this.status='上传完成'; | |||||
| window.location.reload(); | |||||
| } else { | |||||
| //断点续传 | |||||
| this.multipartUpload(file); | |||||
| } | |||||
| } | |||||
| function addAttachment(file){ | |||||
| return new Promise((resolve, reject) => { | |||||
| axios.post('/attachments/add', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| file_name: file.name, | |||||
| size: file.size, | |||||
| dataset_id: file.datasetId, | |||||
| _csrf: csrf | |||||
| })).then(function (response) { | |||||
| resolve(response); | |||||
| }).catch(function (error) { | |||||
| console.log(error); | |||||
| reject(error); | |||||
| }); | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style> | |||||
| .uploader-app { | |||||
| width: 850px; | |||||
| padding: 15px; | |||||
| margin: 40px auto 0; | |||||
| font-size: 12px; | |||||
| box-shadow: 0 0 10px rgba(0, 0, 0, .4); | |||||
| } | |||||
| .uploader-app .uploader-btn { | |||||
| margin-right: 40px; | |||||
| } | |||||
| .uploader-app .uploader-list { | |||||
| max-height: 440px; | |||||
| overflow: auto; | |||||
| overflow-x: hidden; | |||||
| overflow-y: auto; | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,331 @@ | |||||
| <template> | |||||
| <div class="dropzone-wrapper"> | |||||
| <div id="dropzone" class="dropzone"> | |||||
| </div> | |||||
| <p>文件处理状态:{{ status }}</p> | |||||
| <p>文件上传进度:{{ progress }}%</p> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import Dropzone from 'dropzone/dist/dropzone.js'; | |||||
| // import 'dropzone/dist/dropzone.css' | |||||
| import createDropzone from '../features/dropzone.js'; | |||||
| import SparkMD5 from 'spark-md5'; | |||||
| import axios from 'axios'; | |||||
| import qs from 'qs'; | |||||
| const { | |||||
| AppSubUrl, | |||||
| StaticUrlPrefix, | |||||
| csrf | |||||
| } = window.config; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| dropzoneUploader: null, | |||||
| maxFiles: 1, | |||||
| maxFilesize: 1 * 1024 * 1024 * 1024 * 1024, | |||||
| acceptedFiles: '*/*', | |||||
| progress: 0, | |||||
| status: '等待上传', | |||||
| } | |||||
| }, | |||||
| async mounted() { | |||||
| const dropzoneUploader = await createDropzone("div#dropzone", { | |||||
| url: '/todouploader', | |||||
| maxFiles: this.maxFiles, | |||||
| maxFilesize: this.maxFileSize, | |||||
| timeout: 0, | |||||
| autoQueue: false, | |||||
| }) | |||||
| dropzoneUploader.on("addedfile", (file) => { | |||||
| setTimeout(() => { | |||||
| file.accepted && this.onFileAdded(file); | |||||
| }, 200); | |||||
| }); | |||||
| dropzoneUploader.on("maxfilesexceeded", function(file) { | |||||
| if (this.files[0].status !== 'success') { | |||||
| alert('请等待第一个文件传输完成') | |||||
| this.removeFile(file) | |||||
| return | |||||
| } | |||||
| this.removeAllFiles(); | |||||
| this.addFile(file); | |||||
| }); | |||||
| this.dropzoneUploader = dropzoneUploader | |||||
| }, | |||||
| methods: { | |||||
| resetStatus() { | |||||
| this.progress = 0 | |||||
| this.status = '' | |||||
| }, | |||||
| updateProgress(file, progress) { | |||||
| file.previewTemplate.querySelector(".dz-upload").style.width = `${progress}%`; | |||||
| }, | |||||
| emitDropzoneSuccess(file) { | |||||
| file.status = Dropzone.SUCCESS; | |||||
| this.dropzoneUploader.emit("success", file); | |||||
| this.dropzoneUploader.emit("complete", file); | |||||
| }, | |||||
| onFileAdded(file) { | |||||
| file.datasetId = document.getElementById("datasetId").getAttribute("datasetId"); | |||||
| this.resetStatus() | |||||
| this.computeMD5(file); | |||||
| }, | |||||
| finishUpload(file) { | |||||
| this.emitDropzoneSuccess(file) | |||||
| setTimeout(() => { | |||||
| window.location.reload(); | |||||
| }, 1000); | |||||
| }, | |||||
| computeMD5(file) { | |||||
| this.resetStatus() | |||||
| let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, | |||||
| chunkSize = 1024 * 1024 * 64, | |||||
| chunks = Math.ceil(file.size / chunkSize), | |||||
| currentChunk = 0, | |||||
| spark = new SparkMD5.ArrayBuffer(), | |||||
| fileReader = new FileReader(); | |||||
| let time = new Date().getTime(); | |||||
| // console.log('计算MD5...') | |||||
| this.status = '计算MD5'; | |||||
| file.totalChunkCounts = chunks; | |||||
| loadNext(); | |||||
| fileReader.onload = (e) => { | |||||
| fileLoaded.call(this, e) | |||||
| }; | |||||
| fileReader.onerror = (err) => { | |||||
| console.warn('oops, something went wrong.', err); | |||||
| file.cancel(); | |||||
| }; | |||||
| function fileLoaded(e){ | |||||
| spark.append(e.target.result); // Append array buffer | |||||
| currentChunk++; | |||||
| if (currentChunk < chunks) { | |||||
| // console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1}/${chunks}分片解析`); | |||||
| this.status = `加载文件 ${(currentChunk/chunks*100).toFixed(2)}% (${currentChunk}/${chunks})`; | |||||
| this.updateProgress(file, (currentChunk/chunks*100).toFixed(2)) | |||||
| loadNext(); | |||||
| return | |||||
| } | |||||
| let md5 = spark.end(); | |||||
| console.log( | |||||
| `MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s` | |||||
| ); | |||||
| spark.destroy(); //释放缓存 | |||||
| file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识 | |||||
| file.cmd5 = false; //取消计算md5状态 | |||||
| this.computeMD5Success(file); | |||||
| } | |||||
| function loadNext() { | |||||
| let start = currentChunk * chunkSize; | |||||
| let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; | |||||
| fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); | |||||
| } | |||||
| }, | |||||
| async computeMD5Success(md5edFile) { | |||||
| const file = await this.getSuccessChunks(md5edFile); | |||||
| if (file.uploadID == "" || file.uuid == "") { //未上传过 | |||||
| await this.newMultiUpload(file); | |||||
| if (file.uploadID != "" && file.uuid != "") { | |||||
| file.chunks = ""; | |||||
| this.multipartUpload(file); | |||||
| } else { | |||||
| //失败如何处理 | |||||
| return; | |||||
| } | |||||
| return | |||||
| } | |||||
| if (file.uploaded == "1") { //已上传成功 | |||||
| //秒传 | |||||
| if (file.attachID == "0") { //删除数据集记录,未删除文件 | |||||
| await addAttachment(file); | |||||
| } | |||||
| console.log("文件已上传完成"); | |||||
| this.progress = 100; | |||||
| this.status = '上传完成'; | |||||
| this.finishUpload(file) | |||||
| } else { | |||||
| //断点续传 | |||||
| this.multipartUpload(file); | |||||
| } | |||||
| async function addAttachment(file) { | |||||
| return await axios.post('/attachments/add', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| file_name: file.name, | |||||
| size: file.size, | |||||
| dataset_id: file.datasetId, | |||||
| _csrf: csrf | |||||
| })) | |||||
| } | |||||
| }, | |||||
| async getSuccessChunks(file) { | |||||
| const params = { | |||||
| params: { | |||||
| md5: file.uniqueIdentifier, | |||||
| _csrf: csrf | |||||
| } | |||||
| } | |||||
| try { | |||||
| const response = await axios.get('/attachments/get_chunks', params) | |||||
| file.uploadID = response.data.uploadID; | |||||
| file.uuid = response.data.uuid; | |||||
| file.uploaded = response.data.uploaded; | |||||
| file.chunks = response.data.chunks; | |||||
| file.attachID = response.data.attachID; | |||||
| return file | |||||
| } catch(error) { | |||||
| console.log("getSuccessChunks catch: ", error); | |||||
| return null | |||||
| } | |||||
| }, | |||||
| async newMultiUpload(file) { | |||||
| const res = await axios.get('/attachments/new_multipart', { | |||||
| params: { | |||||
| totalChunkCounts: file.totalChunkCounts, | |||||
| md5: file.uniqueIdentifier, | |||||
| size: file.size, | |||||
| fileType: file.fileType, | |||||
| _csrf: csrf | |||||
| } | |||||
| }) | |||||
| file.uploadID = res.data.uploadID; | |||||
| file.uuid = res.data.uuid; | |||||
| }, | |||||
| multipartUpload(file) { | |||||
| let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, | |||||
| chunkSize = 1024 * 1024 * 64, | |||||
| chunks = Math.ceil(file.size / chunkSize), | |||||
| currentChunk = 0, | |||||
| fileReader = new FileReader(), | |||||
| time = new Date().getTime(); | |||||
| function loadNext() { | |||||
| let start = currentChunk * chunkSize; | |||||
| let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; | |||||
| fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); | |||||
| } | |||||
| function checkSuccessChunks() { | |||||
| var index = successChunks.indexOf((currentChunk + 1).toString()) | |||||
| if (index == -1) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| async function getUploadChunkUrl(currentChunk, partSize) { | |||||
| const res = await axios.get('/attachments/get_multipart_url', { | |||||
| params: { | |||||
| uuid: file.uuid, | |||||
| uploadID: file.uploadID, | |||||
| size: partSize, | |||||
| chunkNumber: currentChunk + 1, | |||||
| _csrf: csrf | |||||
| } | |||||
| }) | |||||
| console.log("getUploadChunkUrl: ", res) | |||||
| urls[currentChunk] = res.data.url | |||||
| } | |||||
| async function uploadMinio(url, e) { | |||||
| const res = await axios.put(url, e.target.result) | |||||
| etags[currentChunk] = res.headers.etag; | |||||
| } | |||||
| async function updateChunk(currentChunk) { | |||||
| await axios.post('/attachments/update_chunk', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| chunkNumber: currentChunk + 1, | |||||
| etag: etags[currentChunk], | |||||
| _csrf: csrf | |||||
| })) | |||||
| } | |||||
| async function uploadChunk(e) { | |||||
| if (!checkSuccessChunks()) { | |||||
| let start = currentChunk * chunkSize; | |||||
| let partSize = ((start + chunkSize) >= file.size) ? file.size - start : chunkSize; | |||||
| //获取分片上传url | |||||
| await getUploadChunkUrl(currentChunk, partSize); | |||||
| if (urls[currentChunk] != "") { | |||||
| //上传到minio | |||||
| await uploadMinio(urls[currentChunk], e); | |||||
| if (etags[currentChunk] != "") { | |||||
| //更新数据库:分片上传结果 | |||||
| await updateChunk(currentChunk); | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } | |||||
| }; | |||||
| async function completeUpload() { | |||||
| return await axios.post('/attachments/complete_multipart', qs.stringify({ | |||||
| uuid: file.uuid, | |||||
| uploadID: file.uploadID, | |||||
| file_name: file.name, | |||||
| size: file.size, | |||||
| dataset_id: file.datasetId, | |||||
| _csrf: csrf | |||||
| })) | |||||
| } | |||||
| var successChunks = new Array(); | |||||
| var successParts = new Array(); | |||||
| successParts = file.chunks.split(","); | |||||
| for (let i = 0; i < successParts.length; i++) { | |||||
| successChunks[i] = successParts[i].split("-")[0].split("\"")[1]; | |||||
| } | |||||
| var urls = new Array(); | |||||
| var etags = new Array(); | |||||
| console.log('上传分片...'); | |||||
| this.status = '上传中' | |||||
| loadNext(); | |||||
| fileReader.onload = async (e) => { | |||||
| await uploadChunk(e); | |||||
| currentChunk++; | |||||
| if (currentChunk < chunks) { | |||||
| console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1}/${chunks}个分片上传`); | |||||
| this.progress = Math.ceil((currentChunk / chunks) * 100); | |||||
| this.updateProgress(file, (currentChunk/chunks*100).toFixed(2)) | |||||
| await loadNext(); | |||||
| } else { | |||||
| await completeUpload(); | |||||
| console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`); | |||||
| this.progress = 100; | |||||
| this.status = '上传完成'; | |||||
| this.finishUpload(file) | |||||
| } | |||||
| }; | |||||
| }, | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style> | |||||
| .dropzone-wrapper { | |||||
| margin: 2em auto ; | |||||
| } | |||||
| </style> | |||||
| @@ -8,6 +8,7 @@ import './polyfills.js'; | |||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||
| import 'jquery.are-you-sure'; | import 'jquery.are-you-sure'; | ||||
| import './vendor/semanticdropdown.js'; | import './vendor/semanticdropdown.js'; | ||||
| import VueSimpleUploader from 'vue-simple-uploader'; | |||||
| import {svg} from './utils.js'; | import {svg} from './utils.js'; | ||||
| import initContextPopups from './features/contextpopup.js'; | import initContextPopups from './features/contextpopup.js'; | ||||
| @@ -21,11 +22,9 @@ import highlight from './features/highlight.js'; | |||||
| import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; | import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; | ||||
| import {initNotificationsTable, initNotificationCount} from './features/notification.js'; | import {initNotificationsTable, initNotificationCount} from './features/notification.js'; | ||||
| import {createCodeEditor} from './features/codeeditor.js'; | import {createCodeEditor} from './features/codeeditor.js'; | ||||
| import App from './App.vue' | |||||
| import uploader from 'vue-simple-uploader' | |||||
| Vue.use(uploader); | |||||
| import MinioUploader from './components/MinioUploader.vue'; | |||||
| Vue.use(VueSimpleUploader); | |||||
| const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | ||||
| @@ -3175,8 +3174,8 @@ function initVueUploader() { | |||||
| /* eslint-disable no-new */ | /* eslint-disable no-new */ | ||||
| new Vue({ | new Vue({ | ||||
| el: '#uploader', | el: '#uploader', | ||||
| components: { App }, | |||||
| template: '<App/>' | |||||
| components: {MinioUploader}, | |||||
| template: '<MinioUploader />' | |||||
| }); | }); | ||||
| } | } | ||||