【VUE】前端阿里云OSS断点续传,分片上传

什么是OSS:

数据以对象(Object)的形式存储在OSS的存储空间(Bucket )中。如果要使用OSS存储数据,您需要先创建Bucket,并指定Bucket的地域、访问权限、存储类型等属性。创建Bucket后,您可以将数据以Object的形式上传到Bucket,并指定Object的文件名(Key)作为其唯一标识。

分片上传:

在上传大文件(超过5 GB)到OSS的过程中,如果出现网络中断、程序异常退出等问题导致文件上传失败,您需要使用分片上传的方式上传大文件。分片上传通过将待上传的大文件分成多个较小的碎片(Part),充分利用网络带宽和服务器资源并发上传多个Part,加快上传完成时间,并在Part上传完成之后调用CompleteMultipartUpload接口将这些Part组合成一个完整的Object。

断点续传上传:

在上传大文件(超过5 GB)到OSS的过程中,如果出现网络中断、程序异常退出等问题导致文件上传失败,甚至重试多次仍无法完成上传,您需要使用断点续传上传的方式。断点续传上传将需要上传的大文件分成多个较小的分片并发上传,加速上传完成时间。如果上传过程中,某一分片上传失败,再次上传时会从Checkpoint文件记录的断点继续上传,无需重新上传所有分片。上传完成后,所有分片将合并成完整的文件。

 一.验证服务端是否已有该文件、该文件是否已上传部分块?

点击上传文件→选择文件后→读取文件信息→计算MD5值→发送给服务器→查看服务器返回信息

 计算MD5:

import SparkMD5 from 'spark-md5';this.calculateMD5(files.raw, this.sliceSize).then(async (md5) => {console.log(md5)}).catch((error) => {this.$message.error(error);});// md5 计算calculateMD5(file, chunkSize) {return new Promise((resolve, reject) => {const chunks = Math.ceil(file.size / chunkSize);let currentChunk = 0;const spark = new SparkMD5.ArrayBuffer();const fileReader = new FileReader();fileReader.onload = (e) => {spark.append(e.target.result); // 更新 MD5 值currentChunk++;if (currentChunk < chunks) {this.loadNextChunk(fileReader, currentChunk, chunkSize, file);} else {const md5 = spark.end(); // 计算最终 MD5 值resolve(md5);}};fileReader.onerror = function (e) {reject(e);};this.loadNextChunk(fileReader, currentChunk, chunkSize, file);});},// md5 计算loadNextChunk(fileReader, currentChunk, chunkSize, file) {const start = currentChunk * chunkSize;const end = Math.min(start + chunkSize, file.size);const chunk = file.slice(start, end);fileReader.readAsArrayBuffer(chunk);},

二 文件切块

			sliceSize: 1024 * 1024 * 2, // 分片上传-每片5MBfileBlockArr: [],_this.fileBlockArr = [];let blob = file.raw;const { size: fileSize, name: fileName } = blob;//计算文件切片总数,Math.ceil向上取整const totalSlice = Math.ceil(fileSize / _this.sliceSize);_this.chunktotle = totalSlice;for (let i = 0; i < totalSlice; i++) {let start = i * _this.sliceSize;let end = Math.min(fileSize, start + _this.sliceSize);let chunkBlob = blob.slice(start, end); // 直接使用slice方法分片Blob对象_this.fileBlockArr.push(chunkBlob);}

三 调用初始化接口

调用初始化接口,拿到部分参数后进行块上传

						const requestList = [];_this.fileBlockArr.forEach((item, index) => {if (index + 1 >= sliceNo) {const fn = () => {let data = new FormData();data.append('file', item);data.append('fileSlicesNum', totalSlice);data.append('sliceNo', index + 1);data.append('fileMD5', md5);data.append('resourceKey', _this.resourceKey);data.append('ossSlicesId', _this.ossSlicesId);data.append('fileSize', fileSize);return axios({url: process.env.VUE_APP_BASE_API + '/common/partUpload',method: 'post',data: data,headers: {Authorization: 'Bearer ' + getToken(),'Content-Type': 'multipart/form-data',},}).then((res) => {if (res.data.code === 200) {if (_this.percentCount === 0) {// 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值_this.percentCount = 100 / _this.fileBlockArr.length;}// _this.percent += _this.percentCount; // 改变进度_this.percent = res.data.progressPercent;_this.fileBlockArr.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传}}).catch((err) => {return Promise.reject(err);});};requestList.push(fn);}});let i = 0; // 记录发送的请求个数

四 上传与合并

						let i = 0; // 记录发送的请求个数const complete = () => {let completedData = {ossSlicesId: _this.ossSlicesId,resourceKey: _this.resourceKey,fileMD5: _this.md5,};completePart(completedData).then((response) => {if (response.code == 200) {_this.form.versionUrl = response.data;}}).catch((error) => {console.log('completePart-error:', error);});};const send = async () => {if (i >= requestList.length) {complete();return;}await requestList[i]();i++;send();};send(); // 发送请求

全览

data(){return {sliceSize: 1024 * 1024 * 2, // 分片上传-每片5MBchunktotle: 0,percentCount: 0,percent: 0,md5: '',fileBlockArr: [],resourceKey: undefined,ossSlicesId: undefined,fileName: undefined,}},methods:{uploadChange(files, fileList) {this.fileName = files.name;this.calculateMD5(files.raw, this.sliceSize).then(async (md5) => {this.md5 = md5;// 分片验证let checkData = {fileMD5: this.md5,};continueUpload(checkData).then((response) => {if (response.code === 200) {if (response.data !== null &&response.data.sliceNo < response.data.fileSlicesNum) {//已存在,但未上传完成 取已上传到的块信息this.resourceKey = response.data.resourceKey;this.ossSlicesId = response.data.ossSlicesId;let sliceNo = response.data.sliceNo;blockUpload(files, sliceNo + 1);} else if (response.data !== null &&response.data.sliceNo == response.data.fileSlicesNum) {// 上传完成待合并let completedData = {ossSlicesId: response.data.ossSlicesId,resourceKey: response.data.resourceKey,fileMD5: _this.md5,};mergePart(completedData);} else {// 未上传partInit({fileName: files.name,storageDirectory: 'internalApp',}).then((response) => {if (response.code === 200) {this.resourceKey = response.data.resourceKey;this.ossSlicesId = response.data.ossSlicesId;blockUpload(files, 0);}});}}}).catch((error) => {console.log('continueUpload-error:', error);});function mergePart(completedData) {completePart(completedData).then((response) => {if (response.code == 200) {let fileData = {name: _this.fileName,url: response.data,};_this.fileList.push(fileData);_this.form.versionUrl = response.data;}}).catch((error) => {console.log('completePart-error:', error);});}let _this = this;function blockUpload(file, sliceNo) {_this.fileBlockArr = [];let blob = file.raw;const { size: fileSize, name: fileName } = blob;//计算文件切片总数,Math.ceil向上取整const totalSlice = Math.ceil(fileSize / _this.sliceSize);_this.chunktotle = totalSlice;for (let i = 0; i < totalSlice; i++) {let start = i * _this.sliceSize;let end = Math.min(fileSize, start + _this.sliceSize);let chunkBlob = blob.slice(start, end); // 直接使用slice方法分片Blob对象_this.fileBlockArr.push(chunkBlob);}const requestList = [];_this.fileBlockArr.forEach((item, index) => {if (index + 1 >= sliceNo) {const fn = () => {let data = new FormData();data.append('file', item);data.append('fileSlicesNum', totalSlice);data.append('sliceNo', index + 1);data.append('fileMD5', md5);data.append('resourceKey', _this.resourceKey);data.append('ossSlicesId', _this.ossSlicesId);data.append('fileSize', fileSize);return axios({url: process.env.VUE_APP_BASE_API + '/common/partUpload',method: 'post',data: data,headers: {Authorization: 'Bearer ' + getToken(),'Content-Type': 'multipart/form-data',},}).then((res) => {if (res.data.code === 200) {if (_this.percentCount === 0) {// 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值_this.percentCount = 100 / _this.fileBlockArr.length;}// _this.percent += _this.percentCount; // 改变进度_this.percent = res.data.progressPercent;_this.fileBlockArr.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传}}).catch((err) => {return Promise.reject(err);});};requestList.push(fn);}});let i = 0; // 记录发送的请求个数const complete = () => {let completedData = {ossSlicesId: _this.ossSlicesId,resourceKey: _this.resourceKey,fileMD5: _this.md5,};mergePart(completedData);};const send = async () => {if (i >= requestList.length) {complete();return;}await requestList[i]();i++;send();};send(); // 发送请求}}).catch((error) => {this.$message.error(error);});},// md5 计算calculateMD5(file, chunkSize) {return new Promise((resolve, reject) => {const chunks = Math.ceil(file.size / chunkSize);let currentChunk = 0;const spark = new SparkMD5.ArrayBuffer();const fileReader = new FileReader();fileReader.onload = (e) => {spark.append(e.target.result); // 更新 MD5 值currentChunk++;if (currentChunk < chunks) {this.loadNextChunk(fileReader, currentChunk, chunkSize, file);} else {const md5 = spark.end(); // 计算最终 MD5 值resolve(md5);}};fileReader.onerror = function (e) {reject(e);};this.loadNextChunk(fileReader, currentChunk, chunkSize, file);});},// md5 计算loadNextChunk(fileReader, currentChunk, chunkSize, file) {const start = currentChunk * chunkSize;const end = Math.min(start + chunkSize, file.size);const chunk = file.slice(start, end);fileReader.readAsArrayBuffer(chunk);},
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React - 实现菜单栏滚动

简介 本文将会基于react实现滚动菜单栏功能。 技术实现 实现效果 点击菜单&#xff0c;内容区域会自动滚动到对应卡片。内容区域滑动&#xff0c;指定菜单栏会被选中。 ScrollMenu.js import {useRef, useState} from "react"; import ./ScrollMenu.css;export co…

线程和进程的区别和联系

一、什么是进程 进程(Process), 是一个具有独立功能的程序关于某个数据集合的一次运行活动&#xff0c;是系统进行 【资源分配和调度】 的一个独立单位。 进程是【程序】的【一次执行】(是计算机中程序的执行过程&#xff0c;而不是计算机中的程序)进程是系统进行【资源分配和…

单调栈,LeetCode 1793. 好子数组的最大分数

一、题目 1、题目描述 给你一个整数数组 nums &#xff08;下标从 0 开始&#xff09;和一个整数 k 。 一个子数组 (i, j) 的 分数 定义为 min(nums[i], nums[i1], ..., nums[j]) * (j - i 1) 。一个 好 子数组的两个端点下标需要满足 i < k < j 。 请你返回 好 子数组…

[LeetBook]【学习日记】排序算法——归并排序

主要思想 归并排序是一种分治算法&#xff0c;其排序过程包括分和治分是指将要排序的序列一分为二、二分为四&#xff0c;直到单个序列中只有一个数治是指在分完后&#xff0c;将每两个元素重新组合&#xff0c;四合为二、二合为一&#xff0c;最终完成排序 图片作者&#xf…

Gitlab部署及使用

1. 简介 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用 Git 作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。Gitlab是目前被广泛使用的基于 git 的开源代码管理平台&#xff0c;基于Ruby on Rails构建&#xff0c;主要针对软件开发过程中产生的代码…

taro之Picker,PickerView基础用法

1.Picker 直接上代码 import Taro,{Component} from "tarojs/taro"; import {View,Picker} from tarojs/components import { AtIcon } from taro-ui import { putKey } from /src/utils/storage-utilsclass AgriculturePolicy extends Component{constructor (prop…

MySQL的锁的类型

乐观锁与悲观锁&#xff1a; 乐观锁&#xff08;Optimistic Lock&#xff09;通常不直接锁定数据&#xff0c;而是在更新时检查数据是否已被其他事务修改。如果发现冲突&#xff0c;则放弃本次操作或重试。 悲观锁&#xff08;Pessimistic Lock&#xff09;则是在操作数据之前…

out.as_ptr() as *mut libc::c_char

out.as_ptr() as *mut libc::c_char,这段代码出现在 Rust 语言中&#xff0c;用于将 Rust 字符串类型 (String 或 Vec<u8> 等) 的内部缓冲区指针转换为 C 样式字符串&#xff08;以空字符 ‘\0’ 结尾&#xff09;的可变指针。 out&#xff1a;通常是一个具有内部缓冲区的…

Uni App中去掉访问路径中的#

要启用HTML5历史路由模式&#xff0c;你需要&#xff1a; 确保你的服务器支持HTML5历史API。这意味着服务器应该能够处理在没有#的情况下路由的请求。 在Uni App项目中配置路由模式为history。 在Uni App项目中&#xff0c;你可以在manifest.json文件中配置路由模式。找到&quo…

基于禁忌搜索算法的VRP问题求解matlab仿真,带GUI界面,可设置参数

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1车辆路径问题&#xff08;Vehicle Routing Problem, VRP&#xff09;概述 4.2 禁忌搜索算法&#xff08;Tabu Search, TS&#xff09;原理 5.完整程序 1.程序功能描述 基于禁忌搜索算法…

Learning to summarize from human feedback

Abstract 人工参考总结以及 ROUGE 指标只是我们真实关心的目标(总结质量)的粗略代表。通过优化人工偏好来显著提升总结质量使用大量高质量的人类比较来训练一个模型来预测人类偏好的总结使用这个模型作为奖励函数对总结策略进行强化学习微调我们模型的效果在 TL;DR 数据集上显…

Spring的炼气之路(炼气三层)

目录 一、容器的介绍 1.1 创建容器 1.1.1 类路径下的XML配置文件 1.1.2 文件系统下的XML配置文件 1.2 获取bean 1.2.1 使用baen名称获取 1.2.2 使用bean名称指定类型 1.2.3 使用bean类型获取 二、注解开发 1.1 注解开发bean 1.2 纯注解开发 三、注解开发之bean的…

漫谈微服务网关

一、什么是服务网关 服务网关 路由转发 过滤器 1、路由转发&#xff1a;接收一切外界请求&#xff0c;转发到后端的微服务上去&#xff1b; 2、过滤器&#xff1a;在服务网关中可以完成一系列的横切功能&#xff0c;例如权限校验、限流以及监控等&#xff0c;这些都可以通过…

【Linux】Linux开发工具-vim / 编译器-gcc/g++ / 调试器-gdb / git操作 / 项目自动化构建工具-make/Makefile

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.在Linux写自己的第一个程序 1.1 nano指令 1.2 nano指令的使用 1.2.1 介绍 1.2.2 演示 1.2.2.1 创建.c文件 1.2.2.2 nano cod…

Java后端八股------设计模式

Coffee可以设计成接口。 b

EPSON XV4001BC陀螺仪传感器汽车导航系统的应用

近年来为了提高汽车应用系统的可靠性,传感器融合系统被越来越多的应用到汽车领域,如汽车导航系统中的行人检测和预碰撞警告等,通过提供精准的导航信息,为驾驶员提供更安全,更稳定,更舒适的出行体验,例如在行人检测系统中,只使用低成本的红外传感器不能检测到行人的实际位置,而利…

python 八大排序算法

1、选择排序 选择排序是一种简单直观的排序算法&#xff0c;其工作原理是在未排序序列中找到最小&#xff08;或最大&#xff09;的元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后继续寻找剩余未排序序列中的最小&#xff08;或最大&#xff09;元素&#xff0c;放…

LangChain + Qwen(DashScope)

文章目录 引言DashScope API KEY关于 DashScope 和 ModelScope 代码可用模型及费用模型说明计费单价免费额度 引言 常见的 RAG 示例&#xff0c;一般使用 OpenAI&#xff0c;你也可以使用 Qwen 作为 LLM。 在 LangChain 中&#xff0c;调用 Tongyi 来实现。&#xff08;而不是…

本地gitlab-runner的创建与注册

引言 之前通过一些方式在本地创建runner&#xff0c;时而会出现一些未知的坑&#xff0c;所以写下本文记录runner可以无坑创建的方式。 以下注册runner到相应仓库的前提是已经在本地安装了gitlab-runner 具体安装方式见官网 本地gitlab-runner安装常用的指令 查看gitlab r…

5G网络架构及技术(一):入门级介绍

参考资料&#xff1a; [1] 5G网络架构&#xff0c;March 15, 2020 / By Adnan Ghayas [2] 5G应用场景&#xff0c;June 2, 2021 / By Adnan Ghayas [3] 独立和非独立5G网络&#xff0c;September 19, 2020 / By Adnan Ghayas 5G网络架构&#xff08;一&#xff09;&#xff1a;…