瀏覽代碼

算法导入改为切片上传大文件

zhangliang2 3 年之前
父節點
當前提交
4038bef6b7

+ 7 - 1
src/api/common.js

@@ -8,6 +8,9 @@ const uploadWj = basePart + '/simulation/resource/server/file/uploadWj'; // 上
 const download = basePart + '/simulation/resource/common/minio/download'; // 下载方法
 const uploadProcessBar = basePart + '/simulation/resource/common/minio/uploadProcessBar'; // 算法上传
 
+const createMultipartUpload = basePart + '/simulation/resource/common/minio/createMultipartUpload'; // 分片上传-请求上传地址
+const completeMultipartUpload = basePart + '/simulation/resource/common/minio/completeMultipartUpload'; // 分片上传-文件合并地址
+
 
 export default {
     single,
@@ -16,7 +19,10 @@ export default {
     
     uploadWj,
     download,
-    uploadProcessBar
+    uploadProcessBar,
+
+    createMultipartUpload,
+    completeMultipartUpload
 }
 
 //10.15.12.74:7001/simulation/oauth/client/sign/single?code=1001&ticket=1001

+ 4 - 4
src/axios/filter.js

@@ -88,15 +88,15 @@ const instance = axios.create({
     baseURL: '',
 });
 
-instance.defaults.headers.common['Authorization'] = ""; //请求token信息配置
-instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; //请求type设置
+// instance.defaults.headers.common['Authorization'] = ""; //请求token信息配置
+// instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; //请求type设置
 instance.defaults.timeout = 1000 * 60 * 60; //在超时前,所有请求都会等待
-instance.defaults.withCredentials = true;
+// instance.defaults.withCredentials = true;
 
 instance.interceptors.request.use(function (config) {
     // 在发送请求之前处理
     // 判断token在前台session中是否存在
-    config.headers.common['Authorization'] = localStorage.getItem('Authorization');
+    // config.headers.common['Authorization'] = localStorage.getItem('Authorization');
     showFullScreenLoading();
     return config;
 }, function (error) {

+ 16 - 6
src/components/upload/upload.vue

@@ -105,15 +105,15 @@ export default {
                 await formData.append("file", file.file);
 
                 let axios = this.$axios;
-                // 处理大文件上传
-                if (this.needInstance) {
-                    axios = this.$instance;
-                }
+                // 处理大文件上传 instance已被设置不携带token
+                // if (this.needInstance) {
+                //     axios = this.$instance;
+                // }
 
                 // 上传前进行通知
                 this.$emit("willUpload");
 
-                let url = this.uploadUrl;
+                /* let url = this.uploadUrl;
                 let defaultParam = {};
                 if (!url) {
                     url = this.$api.common.uploadWj;
@@ -124,7 +124,17 @@ export default {
                             md5: res,
                         };
                     });
-                }
+                } */
+
+                let url = this.$api.common.uploadWj;
+                let defaultParam = {};
+                // 获取MD5值
+                await this.$md5(file.file).then((res) => {
+                    defaultParam = {
+                        // file: file.file,
+                        md5: res,
+                    };
+                });
 
                 await axios({
                     method: "post",

+ 448 - 0
src/components/upload/uploadBigFile.vue

@@ -0,0 +1,448 @@
+<template>
+    <div>
+        <el-upload
+            ref="upload"
+            action=""
+            :on-remove="handleRemove"
+            :on-error="error"
+            :http-request="toUpload"
+            :auto-upload="true"
+            :file-list="fileList"
+            :limit="limit"
+            :on-exceed="handleExceed"
+            multiple
+        >
+            <el-button size="small" type="primary">点击上传</el-button>
+        </el-upload>
+    </div>
+</template>
+
+<script>
+import SparkMD5 from "spark-md5";
+
+export default {
+    name: "uploadBigFile",
+    props: {
+        // 限制上传的个数
+        limit: {
+            type: Number,
+            default: 100,
+        },
+        // 一组文件传相同的值,保证其为一组
+        objectPath: {
+            type: String,
+            default: "1",
+        },
+        type: {
+            type: String,
+            default: "1",
+        },
+    },
+    data() {
+        return {
+            fileList: [],
+            attachmentList: [],
+            defaultParam: {},
+            chunkSize: 5 * 1024 * 1024, // 每次切片的大小
+            uploadIdInfo: {},
+            simultaneousUploads: 3, // 上传并发数
+        };
+    },
+    methods: {
+        handleExceed(files, fileList) {
+            this.$message.warning(
+                `当前限制选择 ${this.limit} 个文件,本次选择了 ${
+                    files.length
+                } 个文件,共选择了 ${files.length + fileList.length} 个文件`
+            );
+        },
+        async handleRemove(file, fileList) {
+            let md5 = "";
+            if (file.raw) {
+                await this.$md5(file.raw).then((res) => {
+                    md5 = res;
+                });
+            } else {
+                md5 = file.md5;
+            }
+
+            let removeIndex = "";
+            this.attachmentList.forEach((item, index) => {
+                if (md5 === item.md5) {
+                    removeIndex = index;
+                }
+            });
+
+            this.attachmentList.splice(removeIndex, 1);
+            this.$emit("attachmentChange", this.attachmentList);
+        },
+
+        async toUpload(file) {
+            let currentFile = file.file;
+
+            // 上传前进行通知
+            this.$emit("willUpload");
+
+            // 1. 计算MD5
+            await this.getFileMd5(currentFile, async (md5, totalChunks) => {
+                // 2. 检查是否已上传
+                // const checkResult = await this.checkFileUploadedByMd5(md5);
+                // 已上传
+                // if (checkResult.data.status === 1) {
+                //     this.$message.success(
+                //         `上传成功,文件地址:${checkResult.data.url}`
+                //     );
+                //     console.log("文件访问地址:" + checkResult.data.url);
+                //     // currentFile.status = FileStatus.success;
+                //     // currentFile.uploadProgress = 100;
+                //     return;
+                // } else if (checkResult.data.status === 2) {
+                //     // "上传中" 状态
+                //     // 获取已上传分片列表
+                //     let chunkUploadedList = checkResult.data.chunkUploadedList;
+                //     currentFile.chunkUploadedList = chunkUploadedList;
+                // } else {
+                //     // 未上传
+                //     console.log("未上传");
+                // }
+
+                // console.log("文件MD5:" + md5);
+                // 3. 正在创建分片
+                let chunkSize = this.chunkSize;
+
+                let fileChunks = this.createFileChunk(currentFile, chunkSize);
+
+                let param = {
+                    // fileName: currentFile.name,
+                    // fileSize: currentFile.size,
+                    // chunkSize: chunkSize,
+                    // fileMd5: md5,
+                    // contentType: "application/octet-stream",
+                    objectName: `${this.objectPath}/${currentFile.name}`,
+                    chunkSize: totalChunks,
+                    type: "algorithmFile",
+                };
+                // 4. 获取上传url
+                let uploadIdInfoResult = await this.getFileUploadUrls(param);
+                if (uploadIdInfoResult) {
+                    this.uploadIdInfo = uploadIdInfoResult;
+                } else {
+                    return;
+                }
+
+                let uploadUrls = this.uploadIdInfo.uploadUrls;
+                if (fileChunks.length !== uploadUrls.length) {
+                    this.$message.error("文件分片上传地址获取错误!");
+                    this.$emit("didUpload");
+                    return;
+                }
+                this.$set(currentFile, "chunkList", []);
+
+                fileChunks.map((chunkItem, index) => {
+                    currentFile.chunkList.push({
+                        chunkNumber: index + 1,
+                        chunk: chunkItem,
+                        uploadUrl: uploadUrls[index],
+                        uploadTimesLeft: 3, // 剩余可上传次数
+                    });
+                });
+                let tempFileChunks = [];
+                currentFile.chunkList.forEach((item) => {
+                    tempFileChunks.push(item);
+                });
+
+                // 处理分片列表,删除已上传的分片
+                // tempFileChunks = this.processUploadChunkList(tempFileChunks,currentFile);
+
+                // 5. 上传
+                let isUploaded = await this.uploadChunkBase(tempFileChunks);
+                // console.log("上传完成", isUploaded);
+                if (!isUploaded) {
+                    this.$message.error("上传失败");
+                    this.$emit("didUpload");
+                    return;
+                }
+
+                // 6. 合并文件
+                const mergeResult = await this.mergeFile({
+                    uploadId: this.uploadIdInfo.uploadId,
+                    fileName: currentFile.name,
+                    md5: md5,
+                });
+
+                if (mergeResult) {
+                    this.success();
+                    this.attachmentList.push({
+                        fileName: currentFile.name,
+                    });
+                    this.$emit("attachmentChange", this.attachmentList);
+                }
+                this.$emit("didUpload");
+                // if (!mergeResult.success) {
+                //     // currentFile.status = FileStatus.error;
+                //     this.$message.error(mergeResult.error);
+                // } else {
+                //     // currentFile.status = FileStatus.success;
+                //     console.log("文件访问地址:" + mergeResult.data.url);
+                //     this.$message.success(
+                //         `上传成功,文件地址:${mergeResult.data.url}`
+                //     );
+                // }
+            });
+        },
+
+        /* checkFileUploadedByMd5(md5) {
+            let url = `http://127.0.0.1:8027/upload/check?md5=${md5}`
+            return new Promise((resolve, reject) => {
+            axios.get(url).then((response) => {
+                resolve(response.data)
+            }).catch(error => {
+                reject(error)
+            })
+            })
+        }, */
+        getFileUploadUrls(fileParam) {
+            return new Promise((resolve, reject) => {
+                this.$axios({
+                    method: "post",
+                    url: this.$api.common.createMultipartUpload,
+                    data: fileParam,
+                })
+                    .then((res) => {
+                        if (res.code == 200 && res.info) {
+                            resolve(res.info);
+                        } else {
+                            this.$emit("didUpload");
+                            this.$message.error(
+                                res.message || "文件分片上传地址获取错误"
+                            );
+                            resolve(false);
+                        }
+                    })
+                    .catch((error) => {
+                        this.$emit("didUpload");
+                        this.$message.error(
+                            error || "文件分片上传地址获取错误!"
+                        );
+                        resolve(false);
+                    });
+            });
+        },
+
+        /**
+         * 分片读取文件 MD5
+         */
+        getFileMd5(file, callback) {
+            let chunkSize = this.chunkSize;
+            const blobSlice =
+                File.prototype.slice ||
+                File.prototype.mozSlice ||
+                File.prototype.webkitSlice;
+            const fileReader = new FileReader();
+            // 计算分片数
+            const totalChunks = Math.ceil(file.size / chunkSize);
+            // console.log("总分片数:" + totalChunks);
+            let currentChunk = 0;
+            const spark = new SparkMD5.ArrayBuffer();
+
+            loadNext();
+
+            fileReader.onload = function (e) {
+                try {
+                    spark.append(e.target.result);
+                } catch (error) {
+                    console.log("获取Md5错误:" + currentChunk);
+                    // 上传完成后进行通知
+                    this.$emit("didUpload");
+                    this.$message.error("文件解析错误");
+                }
+
+                if (currentChunk < totalChunks) {
+                    currentChunk++;
+                    loadNext();
+                } else {
+                    callback(spark.end(), totalChunks);
+                }
+            };
+
+            fileReader.onerror = function () {
+                console.warn("读取Md5失败,文件读取错误");
+                // 上传完成后进行通知
+                this.$emit("didUpload");
+                this.$message.error("文件读取错误");
+            };
+
+            function loadNext() {
+                const start = currentChunk * chunkSize;
+                const end =
+                    start + chunkSize >= file.size
+                        ? file.size
+                        : start + chunkSize;
+                // 注意这里的 fileRaw
+                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
+            }
+        },
+        /**
+         * 文件分片
+         */
+        createFileChunk(file, size = chunkSize) {
+            const fileChunkList = [];
+            let count = 0;
+            while (count < file.size) {
+                fileChunkList.push({
+                    file: file.slice(count, count + size),
+                });
+                count += size;
+            }
+            return fileChunkList;
+        },
+        /**
+         * 处理即将上传的分片列表,判断是否有已上传的分片,有则从列表中删除
+         */
+        /* processUploadChunkList(chunkList,file) {
+        // const currentFile = this.uploadFileList[currentFileIndex]
+        const currentFile = file
+        let chunkUploadedList = currentFile.chunkUploadedList
+        if (chunkUploadedList === undefined || chunkUploadedList === null || chunkUploadedList.length === 0) {
+          return chunkList
+        }
+        // 
+        for (let i = chunkList.length - 1; i >= 0; i--) {
+          const chunkItem = chunkList[i]
+          for (let j = 0; j < chunkUploadedList.length; j++) {
+            if (chunkItem.chunkNumber === chunkUploadedList[j]) {
+              chunkList.splice(i, 1)
+              break
+            }
+          }
+        }
+        return chunkList
+      }, */
+
+        uploadChunkBase(chunkList) {
+            let successCount = 0;
+            let totalChunks = chunkList.length;
+
+            return new Promise((resolve, reject) => {
+                const handler = () => {
+                    if (chunkList.length) {
+                        const chunkItem = chunkList.shift();
+                        // console.log(chunkItem);
+
+                        // 直接上传二进制,不需要构造 FormData,否则上传后文件损坏
+                        this.$instance
+                            .put(chunkItem.uploadUrl, chunkItem.chunk.file, {
+                                // 上传进度处理
+                                // onUploadProgress: this.checkChunkUploadProgress(chunkItem),
+                                headers: {
+                                    "Content-Type": "application/octet-stream",
+                                    //   'Content-Type': 'binary/octet-stream'
+                                    //   'Content-Type': 'multipart/octet-stream'
+                                },
+                            })
+                            .then((response) => {
+                                successCount++;
+                                this.$emit(
+                                    "percentageChange",
+                                    Math.floor(
+                                        (successCount / totalChunks) * 100
+                                    )
+                                );
+
+                                handler();
+                            })
+                            .catch((error) => {
+                                // 更新状态
+                                // 重新添加到队列中
+                                chunkItem.uploadTimesLeft--;
+
+                                if (chunkItem.uploadTimesLeft < 0) {
+                                    resolve(false);
+                                    return;
+                                }
+
+                                chunkList.push(chunkItem);
+                                handler();
+                            });
+                    }
+
+                    if (successCount >= totalChunks) {
+                        resolve(true);
+                    }
+                };
+                // 并发
+                for (let i = 0; i < this.simultaneousUploads; i++) {
+                    handler();
+                }
+            });
+        },
+        /**
+         * 合并文件
+         */
+        mergeFile(file) {
+            return new Promise((resolve, reject) => {
+                this.$axios({
+                    method: "post",
+                    url: this.$api.common.completeMultipartUpload,
+                    data: {
+                        objectName: this.uploadIdInfo.objectName,
+                        uploadId: this.uploadIdInfo.uploadId,
+                    },
+                })
+                    .then((res) => {
+                        if (res.code == 200) {
+                            resolve(true);
+                        } else {
+                            resolve(false);
+                            this.$message.error(res.message || "合并文件失败");
+                        }
+                    })
+                    .catch((error) => {
+                        this.$message.error(error || "合并文件失败!");
+                        resolve(false);
+                    });
+            });
+        },
+
+        success(response, file, fileList) {
+            this.$message.success("上传成功 ");
+        },
+        error(response, file, fileList) {
+            console.log("error 上传失败");
+            this.$message.warning("上传失败");
+        },
+        download(downPath, downName) {
+            this.$axios({
+                method: "post",
+                url: this.$api.common.download,
+                responseType: "blob",
+                data: {
+                    objectName: downPath,
+                },
+            }).then((res) => {
+                const blob = new Blob([res]); //构造一个blob对象来处理数据
+                const fileName = downName;
+                //对于<a>标签,只有 Firefox 和 Chrome(内核) 支持 download 属性
+                //IE10以上支持blob但是依然不支持download
+                if ("download" in document.createElement("a")) {
+                    //支持a标签download的浏览器
+                    const link = document.createElement("a"); //创建a标签
+                    link.download = fileName; //a标签添加属性
+                    link.style.display = "none";
+                    link.href = URL.createObjectURL(blob);
+                    document.body.appendChild(link);
+                    link.click(); //执行下载
+                    URL.revokeObjectURL(link.href); //释放url
+                    document.body.removeChild(link); //释放标签
+                } else {
+                    //其他浏览器
+                    navigator.msSaveBlob(blob, fileName);
+                }
+            });
+        },
+    },
+};
+</script>
+
+<style scoped>
+</style>

+ 17 - 11
src/views/algorithmsLibrary/exportAlgorithms.vue

@@ -42,11 +42,10 @@
                             :limit="1"
                             type="algorithmFile"
                             :objectPath="objectPath"
-                            :needInstance="true"
-                            :uploadUrl="uploadUrl"
                             @attachmentChange="attachmentChange"
                             @willUpload="willUpload"
                             @didUpload="didUpload"
+                            @percentageChange="percentageChange"
                         ></upload>
                     </el-form-item>
                 </el-form>
@@ -85,7 +84,7 @@
 </template>
 
 <script>
-import upload from "@/components/upload/upload";
+import upload from "@/components/upload/uploadBigFile";
 import { mapState } from "vuex";
 
 export default {
@@ -114,7 +113,7 @@ export default {
             },
             objectPath: "", // 用于上传文件的传参
             isUploading: false,
-            uploadUrl: this.$api.common.uploadProcessBar,
+            // uploadUrl: this.$api.common.uploadProcessBar,
             timer: null, // 用来接收进度条的定时器
             percentage: 0, // 进度条数据
             customColor: "", // 进度条颜色
@@ -128,7 +127,7 @@ export default {
 
     methods: {
         attachmentChange(obj) {
-            console.log(obj);
+            // console.log(obj);
             if (obj.length > 0) {
                 this.form.minioPath = obj[0].fileName;
                 this.$refs.form.clearValidate("minioPath");
@@ -210,21 +209,28 @@ export default {
                 }
             });
         },
+        // 上传开始
         willUpload() {
             this.isUploading = true;
 
-            this.timer = setInterval(() => {
-                this.onProgress();
-            }, 4500);
+            // this.timer = setInterval(() => {
+            //     this.onProgress();
+            // }, 4500);
         },
+        // 上传完成
         didUpload(state) {
             this.isUploading = false;
-            clearInterval(this.timer);
-            this.timer = null;
+            // clearInterval(this.timer);
+            // this.timer = null;
             this.percentage = 0;
-            this.step = 0;
+            // this.step = 0;
             this.objectPath = this.$getObjectPathByRandom();
         },
+        // 进度变化
+        percentageChange(val) {
+            if (val > 100) val = 100;
+            this.percentage = val;
+        },
     },
 
     mounted() {

+ 1 - 0
vue.config.js

@@ -91,6 +91,7 @@ module.exports = {
                 // target: 'http://10.15.12.73:7003',  // 王志强
                 target: 'http://10.15.12.70', // windowstest
                 // target: 'http://10.15.12.87:8001',  // 赵艳
+                // target: 'http://10.15.12.72:8001',  // 王晓峰
                 // target: 'http://192.168.30.75',  // gq
                 // target: 'http://47.94.105.148',  // aliyun
                 // target: 'http://10.15.12.74:8001',