minio断点续传和分片上传

<template>
  <div class="home">
    <template>
    <el-card style="width: 80%; margin: 80px auto" header="文件分片上传">
        <el-upload
            class="upload-demo"
            drag
            action="/"
            multiple
            :http-request="handleHttpRequest"
            :on-remove="handleRemoveFile">
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
            <div class="el-upload__text">
                请拖拽文件到此处或 <em>点击此处上传</em>
            </div>
        </el-upload>
    </el-card>

</template>
  </div>
</template>
<script>
import md5 from "./../lib/md5";
import { taskInfo, initTask, preSignUrl, merge } from './../lib/api';
import Queue from 'promise-queue-plus';
import axios from 'axios'
export default {
  name: 'HomeView',
  data(){
    return {
       fileUploadChunkQueue:{}
    }
  },
  methods:{
    /**
 * 获取一个上传任务,没有则初始化一个
 */
    async getTaskInfo(file){
      let task;
    const identifier = await md5(file)
    const { code, data, msg } = await taskInfo(identifier)
    if (code === 200000) {
        task = data
        if (!task) {
            const initTaskData = {
                identifier,
                fileName: file.name,
                totalSize: file.size,
                chunkSize: 5 * 1024 * 1024
            }
            const { code, data, msg } = await initTask(initTaskData)
            if (code === 200000) {
                task = data
            } else {
                ElNotification.error({
                    title: '文件上传错误',
                    message: msg
                })
            }
        }
    } else {
        ElNotification.error({
            title: '文件上传错误',
            message: msg
        })
    }
    return task
    },

/**
 * 上传逻辑处理,如果文件已经上传完成(完成分块合并操作),则不会进入到此方法中
 */
 handleUpload(file, taskRecord, options){
  let lastUploadedSize = 0; // 上次断点续传时上传的总大小
    let uploadedSize = 0 // 已上传的大小
    const totalSize = file.size || 0 // 文件总大小
    let startMs = new Date().getTime(); // 开始上传的时间
    const { exitPartList, chunkSize, chunkNum, fileIdentifier } = taskRecord
    // 获取从开始上传到现在的平均速度(byte/s)
    const getSpeed = () => {
        // 已上传的总大小 - 上次上传的总大小(断点续传)= 本次上传的总大小(byte)
        const intervalSize = uploadedSize - lastUploadedSize
        const nowMs = new Date().getTime()
        // 时间间隔(s)
        const intervalTime = (nowMs - startMs) / 1000
        return intervalSize / intervalTime
    }
    const uploadNext = async (partNumber) => {
        const start = new Number(chunkSize) * (partNumber - 1)
        const end = start + new Number(chunkSize)
        const blob = file.slice(start, end)
        const { code, data, msg } = await preSignUrl({ identifier: fileIdentifier, partNumber: partNumber} )
        if (code === 200000 && data) {
            await axios.request({
                url: data,
                method: 'PUT',
                data: blob,
                headers: {'Content-Type': 'application/octet-stream'}
            })
            return Promise.resolve({ partNumber: partNumber, uploadedSize: blob.size })
        }
        return Promise.reject(`分片${partNumber}, 获取上传地址失败`)
    }

    /**
     * 更新上传进度
     * @param increment 为已上传的进度增加的字节量
     */
    const updateProcess = (increment) => {
        increment = new Number(increment)
        const { onProgress } = options
        let factor = 1000; // 每次增加1000 byte
        let from = 0;
        // 通过循环一点一点的增加进度
        while (from <= increment) {
            from += factor
            uploadedSize += factor
            const percent = Math.round(uploadedSize / totalSize * 100).toFixed(2);
            onProgress({percent: percent})
        }

        const speed = getSpeed();
        const remainingTime = speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : '未知'
        console.log('剩余大小:', (totalSize - uploadedSize) / 1024 / 1024, 'mb');
        console.log('当前速度:', (speed / 1024 / 1024).toFixed(2), 'mbps');
        console.log('预计完成:', remainingTime);
    }


    return new Promise(resolve => {
        const failArr = [];
        const queue = Queue(5, {
            "retry": 3,               //Number of retries
            "retryIsJump": false,     //retry now?
            "workReject": function(reason,queue){
                failArr.push(reason)
            },
            "queueEnd": function(queue){
                resolve(failArr);
            }
        })
        this.fileUploadChunkQueue[file.uid] = queue
        for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {
            const exitPart = (exitPartList || []).find(exitPart => exitPart.partNumber == partNumber)
            if (exitPart) {
                // 分片已上传完成,累计到上传完成的总额中,同时记录一下上次断点上传的大小,用于计算上传速度
                lastUploadedSize += new Number(exitPart.size)
                updateProcess(exitPart.size)
            } else {
                queue.push(() => uploadNext(partNumber).then(res => {
                    // 单片文件上传完成再更新上传进度
                    updateProcess(res.uploadedSize)
                }))
            }
        }
        if (queue.getLength() == 0) {
            // 所有分片都上传完,但未合并,直接return出去,进行合并操作
            resolve(failArr);
            return;
        }
        queue.start()
    })
 },

/**
 * el-upload 自定义上传方法入口
 */

 async handleHttpRequest(options){
  const file = options.file
    const task = await this.getTaskInfo(file)
    if (task) {
        const { finished, path, taskRecord } = task
        const { fileIdentifier: identifier } = taskRecord
        if (finished) {
            return path
        } else {
            const errorList = await this.handleUpload(file, taskRecord, options)
            if (errorList.length > 0) {
                ElNotification.error({
                    title: '文件上传错误',
                    message: '部分分片上次失败,请尝试重新上传文件'
                })
                return;
            }
            const { code, data, msg } = await merge(identifier)
            if (code === 200000) {
                return path;
            } else {
                ElNotification.error({
                    title: '文件上传错误',
                    message: msg
                })
            }
        }
    } else {
        ElNotification.error({
            title: '文件上传错误',
            message: '获取上传任务失败'
        })
    }
 },

/**
 * 移除文件列表中的文件
 * 如果文件存在上传队列任务对象,则停止该队列的任务
 */

 handleRemoveFile(uploadFile, uploadFiles){
  const queueObject = this.fileUploadChunkQueue[uploadFile.uid]
    if (queueObject) {
        queueObject.stop()
        this.fileUploadChunkQueue[uploadFile.uid] = undefined
    }
 }


    
  }
}
</script>

md5.js

import SparkMD5 from 'spark-md5'
const DEFAULT_SIZE = 20 * 1024 * 1024
const md5 = (file, chunkSize = DEFAULT_SIZE) => {
    return new Promise((resolve, reject) => {
        const startMs = new Date().getTime();
        let blobSlice =
            File.prototype.slice ||
            File.prototype.mozSlice ||
            File.prototype.webkitSlice;
        let chunks = Math.ceil(file.size / chunkSize);
        let currentChunk = 0;
        let spark = new SparkMD5.ArrayBuffer(); //追加数组缓冲区。
        let fileReader = new FileReader(); //读取文件
        fileReader.onload = function (e) {
            spark.append(e.target.result);
            currentChunk++;
            if (currentChunk < chunks) {
                loadNext();
            } else {
                const md5 = spark.end(); //完成md5的计算,返回十六进制结果。
                console.log('文件md5计算结束,总耗时:', (new Date().getTime() - startMs) / 1000, 's')
                resolve(md5);
            }
        };
        fileReader.onerror = function (e) {
            reject(e);
        };

        function loadNext() {
            console.log('当前part number:', currentChunk, '总块数:', chunks);
            let start = currentChunk * chunkSize;
            let end = start + chunkSize;
            (end > file.size) && (end = file.size);
            fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
        }
        loadNext();
    });
}

export default md5

api.js

import axios from 'axios'
import axiosExtra from 'axios-extra'
const baseUrl = 'http://localhost:3080'

const http = axios.create({
    baseURL: baseUrl
})

const httpExtra = axiosExtra.create({
    maxConcurrent: 5, //并发为1
    queueOptions: {
        retry: 3, //请求失败时,最多会重试3次
        retryIsJump: false //是否立即重试, 否则将在请求队列尾部插入重试请求
    }
})

http.interceptors.response.use(response => {
    return response.data
})

/**
 * 根据文件的md5获取未上传完的任务
 * @param identifier 文件md5
 * @returns {Promise<AxiosResponse<any>>}
 */
const taskInfo = (identifier) => {
    return http.get(`/v1/minio/tasks/${identifier}`)
}

/**
 * 初始化一个分片上传任务
 * @param identifier 文件md5
 * @param fileName 文件名称
 * @param totalSize 文件大小
 * @param chunkSize 分块大小
 * @returns {Promise<AxiosResponse<any>>}
 */
const initTask = ({ identifier, fileName, totalSize, chunkSize }) => {
    return http.post('/v1/minio/tasks', {identifier, fileName, totalSize, chunkSize})
}

/**
 * 获取预签名分片上传地址
 * @param identifier 文件md5
 * @param partNumber 分片编号
 * @returns {Promise<AxiosResponse<any>>}
 */
const preSignUrl = ({ identifier, partNumber }) => {
    return http.get(`/v1/minio/tasks/${identifier}/${partNumber}`)
}

/**
 * 合并分片
 * @param identifier
 * @returns {Promise<AxiosResponse<any>>}
 */
const merge = (identifier) => {
    return http.post(`/v1/minio/tasks/merge/${identifier}`)
}

export {
    taskInfo,
    initTask,
    preSignUrl,
    merge,
    httpExtra
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/254441.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

risc-v system instruction

ECALL ecall 指令以前叫做 scall&#xff0c;用于执行环境的变更&#xff0c;它会根据当前所处模式触发不同的执行环境切换异常, 用来执行需要更高权限才能执行的功能;简单来说&#xff0c;ecall 指令将权限提升到内核模式并将程序跳转到指定的地址。操作系统内核和应用程序其实…

AD采集卡设计方案:630-基于PCIe的高速模拟AD采集卡

基于PCIe的高速模拟AD采集卡 一、产品概述 基于PCIe的一款分布式高速数据采集系统&#xff0c;实现多路AD的数据采集&#xff0c;并通过PCIe传输到存储计算服务器&#xff0c;实现信号的分析、存储。 北京太速科技&#xff0c;产品固化FPGA逻辑&#xff0c;适配2路…

ShardingSphere-JDBC 和 ShardingSphere-Proxy,你选择哪一个

参考文章 总结&#xff1a; 只使用Java&#xff0c;ShardingSphere-JDBC更好有异构语言的话&#xff0c;ShardingSphere-Proxy 更好混用也挺香

Flink系列之:监控Checkpoint

Flink系列之&#xff1a;监控Checkpoint 一、概览二、概览&#xff08;Overview&#xff09;选项卡三、历史记录&#xff08;History&#xff09;选项卡四、历史记录数量配置五、摘要信息&#xff08;Summary&#xff09;选项卡六、配置信息&#xff08;Configuration&#xff…

100GPTS计划-AI动漫AnimeArtisan

地址 https://poe.com/AnimeArtisan https://chat.openai.com/g/g-LM6ObVhfF-anime-artisan 测试 风景类: 阳光、蓝天、白云、大海、海滩、森林、瀑布、山峰、雪山 日常类: 睡觉、跑步、学习、工作、做家务、看书、听音乐、运动、购物、煮饭 人物类: 女孩、男孩、老人、儿童…

『 Linux 』重新理解挂起状态

文章目录 &#x1f984; 前言新建状态 &#x1f40b;挂起状态 &#x1f40b;唤入唤出 &#x1f40b;进程与操作系统间的联系 &#x1f40b; &#x1f984; 前言 『 Linux 』使用fork函数创建进程与进程状态的查看中提到了对挂起状态的一个理解&#xff1b; ​ 挂起状态相比于其…

爬虫练习-获取imooc课程目录

代码&#xff1a; from bs4 import BeautifulSoup import requests headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0, }id371 #课程id htmlrequests.get(https://coding.imooc.com/class/chapter/id.html#Anchor,head…

kubernetes 学习笔记

1. Kubernetes 介绍 1.1 应用部署方式的演变 在部署应用程序的方式上&#xff0c;主要经理了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上。虚拟化部署&#xff1a;可以在一台物理机上运行多个虚拟机&#xff0c;每个虚…

高可用接入层技术演化及集群概述

集群概述 集群的介绍及优势 集群&#xff1a;将多台服务器通过硬件或软件的方式组合起来&#xff0c;完成特定的任务&#xff0c;而这些服务器对外表现为一个整体。集群的优势 高可靠性&#xff1a;利用集群管理软件&#xff0c;当主服务器故障时&#xff0c;备份服务器能够自…

Cesium 加载 Geoserver WMS 图层以及条件查询和切换图层样式

Cesium 加载 Geoserver WMS 图层以及条件查询和切换图层样式 图层样式核心代码完整代码&#xff1a;在线示例 Cesium 加载 Geoserver WMS 图层&#xff0c;在实际项目中常常会遇到&#xff0c;需要对图层进行过滤&#xff0c;这里介绍一下过滤方法。 Cesium Geoserver 图层条件…

AirPodsPro3爆料汇总,2025年发布?

不止是iPhone&#xff0c;苹果的AirPods Pro系列耳机也是非常受用户青睐的一款产品&#xff0c;相信不少果粉都非常期待它的升级换代。 第一代AirPods Pro于2019年10月发布&#xff0c;第二代AirPods Pro于2022年9月发布&#xff0c;按照这个时间线来看的话&#xff0c;第三代A…

《Linux C编程实战》笔记:进程操作之创建进程

进程是一个动态的实体&#xff0c;是程序的一次执行过程。进程是操作系统资源分配的基本单位。 以下是一些概念&#xff0c;我就直接抄书了 进程是操作系统的知识&#xff0c;简单理解的话&#xff0c;你写的代码运行起来算一个进程&#xff1f; 创建进程 每个进程由进程ID号…

在vue项目中,数据已经在页面渲染,但在后续操作时获取不到数据

如下图 产生这个问题的原因 异步问题 如何解决 方法一&#xff1a;可以将其存放在一个setTimeout里面&#xff08;利用一个极小的延迟来获取数据&#xff09;&#xff0c;如下图 效果 方法二&#xff1a;将操作放入axios里面&#xff0c;如下图

昂首资本发现原油价差这样用,难怪银行这么富

难怪银行这么富&#xff0c;原来是发现一个稳定产生利益的投资策略。虽然这个利润可能看起来比较少。但是昂首资本需要提醒各位投资者的是&#xff1a;首先&#xff0c;这个策略几乎没有风险。第二&#xff0c;这是一个可以复制的投资策略。 下面昂首资本就通过原油的价差进行实…

开发信怎么写回复率高?写外贸邮件的技巧?

如何打造高回复率的开发信&#xff1f;有效的开发信模板推荐&#xff1f; 如何写一封能够引起客户兴趣并提高回复率的开发信变得至关重要。开发信是建立联系、促进销售和扩大业务的关键工具之一。蜂邮EDM将探讨一些关键策略&#xff0c;帮助你提高开发信的回复率&#xff0c;确…

爬虫图片验证码处理

图片验证码处理 目前&#xff0c;很多网站为了防止爬虫爬取&#xff0c;登录时需要用户输入验证码。下面我们学习如何在爬虫程序中识别验证码。 其中包含验证码。 页面中的验证码图片对应一个<img>元素&#xff0c;即一张图片&#xff0c;浏览器加载完登录页面后&#…

Spring 6(二)【IOC原理】

前言 IOC 是Spring的两大核心概念之一&#xff0c;它是一种思想&#xff0c;需要极其熟练的掌握。 今日摘录&#xff1a; 低能无聊的人太多。说他们勤勉&#xff0c;不过是因困为不会合理分配时间&#xff1b;说他们积极&#xff0c;不过是逃避其他困难工作而已。即便说工作只…

DNN二分类模型

import os import datetime#打印时间 def printbar():nowtime datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S)print("\n"""*8 "%s"%nowtime)#mac系统上pytorch和matplotlib在jupyter中同时跑需要更改环境变量 os.environ["KMP_DUP…

【网络安全】-Linux操作系统—操作系统发展历史与Linux

文章目录 操作系统发展历史初期的操作系统分时操作系统个人计算机操作系统 Linux的诞生UNIX与GNU项目Linux内核的创建 Linux的特点开放源代码多样性社区支持 Linux的应用服务器和超级计算机嵌入式系统桌面系统 总结 操作系统发展历史 操作系统&#xff08;Operating System&am…

详细教程 - 从零开发 Vue 鸿蒙harmonyOS应用 第五节 (基于uni-app封装鸿蒙接口请求库)

随着鸿蒙系统的兴起,越来越多的app会采用鸿蒙开发。而鸿蒙开发必不可少的就是调用各种接口服务。为了简化接口的调用流程,我们通常会做一层封装。今天就来讲解一下,如何用uni-app封装鸿蒙的接口请求库。 一、新建项目 首先我们要新建一个鸿蒙项目啦&#xff01;当然选择第一个…
最新文章