<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: this.uploadIdInfo.objectName,
                    });
                    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>
