客户端实现阿里云OSS文件上传(分片上传,断点续传)

news2024/11/13 14:49:09

前言

阿里云OSS(Object Storage Service)是一种稳定、安全、高扩展性的云存储服务,它允许您以低成本、高可靠、高可用的方式存储和访问任意类型的数据。在实际应用中,文件上传是一个常见的功能需求。为了提高上传效率和文件完整性,我们可以使用分片上传和断点续传技术。

分片上传,断点上传使用场景:
通常在文件大于100 MB的情况下,建议采用分片上传的方法,通过断点续传和重试,提高上传成功率。如果在文件小于100 MB的情况下使用分片上传,且partSize设置不合理的情况下,可能会出现无法完整显示上传进度的情况。对于小于100 MB的文件,建议使用简单上传的方式。

简单上传参考:https://help.aliyun.com/zh/oss/developer-reference/simple-upload-8?spm=a2c4g.11186623.0.0.3f742a44LqLGSc

接下来介绍在vue项目中如何使用分片上传和断点续传。

准备

1.安装阿里云OSS SDK

npm install ali-oss --save

2.创建OSS

import OSS from "ali-oss";
const client = ref(
  new OSS({
    // yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
        region: 'yourRegion',
        // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
        accessKeyId: 'yourAccessKeyId',
        accessKeySecret: 'yourAccessKeySecret',
        // 填写Bucket名称。
        bucket: 'examplebucket'
  })
);

分片上传

分片上传的步骤:

  1. 初始化分片上传:调用OSS API初始化一个分片上传会话,获取一个Upload ID。Upload ID用于标识这次分片上传操作。
  2. 分片切割:将待上传的大文件切割成固定大小的分片。
  3. 逐个上传分片:按顺序将每个分片逐个上传到阿里云OSS,每个分片上传成功后会返回一个ETag,用于标识该分片。
  4. 完成分片上传:在所有分片都上传完成后,调用OSS API完成分片上传操作。在此步骤中,需要将每个分片的ETag和分片号按顺序传递给OSS,OSS将根据这些信息进行分片合并,形成完整的大文件。

代码实现:

const uploadProgress = ref(0); //上传进度
const paused = ref(false); //是否暂停
const name = ref(""); //文件名
const uploadId = ref(); //上传id
const chunkArr: any = ref([]);//分片上传的结果数组
let chunks: any = [];//文件分片结果
const uploadFile1 = async (file: any) => {
  chunkArr.value = [];
  uploadProgress.value = 0;
  //获取文件分片的结果数组
  chunks =sliceFile(file);
  const { name: fileName, type: mimeType } = file;
  name.value = fileName;
  //初始化分片上传,获取Upload ID
  const result = await client.value.initMultipartUpload(fileName);
  uploadId.value = result.uploadId;
  //分片上传
  uploadChunk(chunks[0], 1);
};

// 分片
const chunkSize = ref(1 * 1024 * 1024); //分片大小
const size = ref(0);//文件大小
const sliceFile = (file: any) => {
  const fileSize = file.size;
  size.value = file.size;
  const chunks = Math.ceil(fileSize / chunkSize.value); // 计算分片总数
  const allChunks = [];
  for (let i = 0; i < chunks; i++) {
    const start = i * chunkSize.value;
    const end = Math.min(start + chunkSize.value, fileSize);
    const chunk = file.slice(start, end);
    allChunks.push(chunk);
  }
  return allChunks;
};

//上传
async function uploadChunk(chunk: any, partNumber: number) {
  //判断是否暂停上传
  if (paused.value) {
    paused.value = false;
    return;
  }
  //上传分片
  const part = await client.value.uploadPart(
    name.value,
    uploadId.value,
    partNumber,
    chunk,
    0,
    chunk.size
  );
  chunkArr.value.push({ number: partNumber, etag: part.etag });
  // 获取进度
  uploadProgress.value = Number(
    ((partNumber / chunks.length) * 100).toFixed(2)
  );
  if (partNumber < chunks.length) {
    //分片上传成功
    partNumber++;
    uploadChunk(chunks[partNumber - 1], partNumber);
  } else {
    // 分片全部上传完毕
    await client.value.completeMultipartUpload(
      name.value,
      uploadId.value,
      chunkArr.value
    );
  }
}

官方文档分片上传

断点续传

//暂停上传
const stop = () => {
  paused.value = true;
};

//继续上传(断点续传)
const continued = () => {
  client.value.listParts(name.value, uploadId.value).then((result: any) => {
    //获取已经上传分片的信息
    var parts = result.parts || [];
    var nextPartNumber = parts.length + 1;
    //继续上传未上传的分片
    uploadChunk(chunks[nextPartNumber - 1], nextPartNumber);
  });
};

完整案例

<template>
  <el-upload
    v-model:file-list="fileList"
    class="upload-demo"
    :before-upload="beforeUpload"
  >
    <div class="upload">
      <el-button type="primary">Click to upload</el-button>
      <el-progress
        :percentage="uploadProgress"
        type="line"
        style="margin-left: 10px; width: 350px"
      >
      </el-progress>
    </div>
  </el-upload>
  <el-button type="primary" size="small" @click="stop">暂停</el-button>
  <el-button type="primary" size="small" @click="continued">继续</el-button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage, ElMessageBox, progressProps } from "element-plus";
import OSS from "ali-oss";
import type { UploadProps, UploadUserFile } from "element-plus";
const client = ref(
  new OSS({
    // yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
        region: 'yourRegion',
        // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
        accessKeyId: 'yourAccessKeyId',
        accessKeySecret: 'yourAccessKeySecret',
        // 填写Bucket名称。
        bucket: 'examplebucket'
  })
);
const beforeUpload = async (file: File) => {
  try {
    await uploadFile(file);
  } catch (error) {
    console.error("Error uploading file:", error);
  }
  return false;
};
//分片上传
const fileList = ref<UploadUserFile[]>([]);
const uploadProgress = ref(0); //上传进度
const paused = ref(false); //是否暂停
const name = ref(""); //文件名
const uploadId = ref(); //上传id
const chunkArr: any = ref([]);//分片上传的结果数组
let chunks: any = [];//文件分片结果
const uploadFile = async (file: any) => {
  chunkArr.value = [];
  uploadProgress.value = 0;
  //获取文件分片的结果数组
  chunks =sliceFile(file);
  const { name: fileName, type: mimeType } = file;
  name.value = fileName;
  //初始化分片上传,获取Upload ID
  const result = await client.value.initMultipartUpload(fileName);
  uploadId.value = result.uploadId;
  //分片上传
  uploadChunk(chunks[0], 1);
};

// 分片
const chunkSize = ref(5 * 1024 * 1024); //分片大小
const size = ref(0);//文件大小
const sliceFile = (file: any) => {
  const fileSize = file.size;
  size.value = file.size;
  const chunks = Math.ceil(fileSize / chunkSize.value); // 计算分片总数
  const allChunks = [];
  for (let i = 0; i < chunks; i++) {
    const start = i * chunkSize.value;
    const end = Math.min(start + chunkSize.value, fileSize);
    const chunk = file.slice(start, end);
    allChunks.push(chunk);
  }
  return allChunks;
};

//上传
async function uploadChunk(chunk: any, partNumber: number) {
  //判断是否暂停上传
  if (paused.value) {
    paused.value = false;
    return;
  }
  //上传分片
  const part = await client.value.uploadPart(
    name.value,
    uploadId.value,
    partNumber,
    chunk,
    0,
    chunk.size
  );
  chunkArr.value.push({ number: partNumber, etag: part.etag });
  // 获取进度
  uploadProgress.value = Number(
    ((partNumber / chunks.length) * 100).toFixed(2)
  );
  if (partNumber < chunks.length) {
    //分片上传成功
    partNumber++;
    uploadChunk(chunks[partNumber - 1], partNumber);
  } else {
    // 分片全部上传完毕
    await client.value.completeMultipartUpload(
      name.value,
      uploadId.value,
      chunkArr.value
    );
  }
}
//暂停上传
const stop = () => {
  paused.value = true;
};

//继续上传(断点续传)
const continued = () => {
  client.value.listParts(name.value, uploadId.value).then((result: any) => {
    //获取已经上传分片的信息
    var parts = result.parts || [];
    var nextPartNumber = parts.length + 1;
    //继续上传未上传的分片
    uploadChunk(chunks[nextPartNumber - 1], nextPartNumber);
  });
};

</script>

<style lang="scss" scoped>
.upload {
  display: flex;
}
</style>

在这里插入图片描述

遇到问题及解决方案:

问题一:跨域问题
https://blog.csdn.net/StruggleRookie/article/details/119417281

问题二:使用OSS分片上传功能上传文件时报“Please set the etag of expose-headers in OSS”错误
https://help.aliyun.com/zh/oss/the-please-set-the-etag-of-expose-headers-in-oss-error-message-is-returned-when-you-use-multipart-upload-to-upload-files

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

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

相关文章

Leetcode-每日一题【剑指 Offer 56 - I. 数组中数字出现的次数】

题目 一个整型数组 nums 里除两个数字之外&#xff0c;其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)&#xff0c;空间复杂度是O(1)。 示例 1&#xff1a; 输入&#xff1a;nums [4,1,4,6]输出&#xff1a;[1,6] 或 [6,1] 示例 2&#x…

IOS开发:去除TabView的底部留白

我最近在做IOS开发的时候&#xff0c;使用SwiftUI中的TabView做左右滚动的页面切换&#xff0c;遇到了页面底部有大量留白无法去除的问题&#xff1a; 我查了很多资料都没有看到网上有人记录这个问题的解决方案&#xff0c;后来查阅apple developer的文档&#xff0c;我发现.ed…

模电基础知识学习笔记

文章目录&#xff1a; 一&#xff1a;基本元器件介绍 1.二极管 1.1 普通二极管特性测试 1.2 稳压二极管测试 1.3 整流二极管 1.4 开关二极管 2.电容 3.三极管(电流控制) 3.1 介绍 3.2 类型&#xff08;PNP、NPN&#xff09; 3.3 三种工作状态:放大状态、截止状态…

RBAC权限详解

1.传统的权限设计 首先&#xff0c;我们先了解下什么是传统的权限设计 从上面的图中&#xff0c;我们发现&#xff0c;传统的权限设计是对每个人进行单独的权限设置&#xff0c;但这种方式已经不适合目前企业的高效管控权限的发展需求&#xff0c;因为每个人都要单独去设置权限…

大盗阿福(记忆化搜索板子)

提供核心代码&#xff1a;&#xff08;经典的记忆化搜索套路&#xff09; int dfs(int pos){if(f[pos]!-1) return f[pos];//记忆化if(pos>n) return 0;//边界&#xff0c;越界int sum0;//模板int f10,f20;f1dfs(pos1);f2dfs(pos2)w[pos];summax(f1,f2);//模板f[pos]sum;//模…

表达式树

请设计一个算法&#xff0c;将给定的表达式树(二叉树)转换为等价的中缀表达式(通过括号反映操作符的计算次序)并输出。 例如&#xff0c;当下列两棵表达式树作为算法的输入时&#xff1a; 输出的等价中缀表达式分别为 (ab)*(c*(-d)) 和 (a*b)(-(c-d))。 注意&#xff1a; 树…

ChatGPT炒股:自动批量提取股票公告中的表格并合并数据

在很多个股票公告中&#xff0c;都有同样格式的“日常性关联交易”的表格&#xff0c;如何合并到一张Excel表格中呢&#xff1f; 首先&#xff0c;在ChatGPT中输入提示词&#xff1a; 写一段Python代码&#xff1a; F盘文件夹“新三板 2023年日常性关联交易20230704”中很多…

零代码编程:PDF文件名和Excel数据进行比对找不同

F盘“北交所招股说明书”文件夹下有150个文件&#xff1b; F盘”北证A股20230703.xlsx”表格中证券名称有200多个&#xff1b; 现在想找出文件夹下的哪些证券名称不在表格里面。 在ChatGPT中输入提示词&#xff1a; 写一段Python程序&#xff1a; F盘“北交所招股说明书”文…

qt源码--事件系统之QAbstractEventDispatcher

1、QAbstractEventDispatcher内容较少&#xff0c;其主要是定义了一些注册接口&#xff0c;如定时器事件、socket事件、注册本地事件、自定义事件等等。其源码如下&#xff1a; 其主要定义了大量的纯虚函数&#xff0c;具体的实现会根据不同的系统平台&#xff0c;实现对应的方…

AD21原理图的高级应用(五)自定义原理图模板及调用

&#xff08;五&#xff09;自定义原理图模板及调用 1.创建原理图模板2.调用原理图模板 1.创建原理图模板 利用 Altium Designer 软件在原理图中创建自己的模板,可以在图纸的右下角绘制一个表格用于显示图纸的一些参数,例如文件名、作者、修改时间、审核者、公司信息、图纸总数…

建造者设计模式 + 高阶函数 => DSL

该设计模式适用于创建复杂对象&#xff0c;该复杂对象通常是由各个部分的子对象用一定的算法或者步骤构成&#xff0c;针对每个子对象内部算法和步骤通常是稳定的&#xff0c;但是该复杂对象的确实由于不同的需求而选择使用不同的子对象进行组装。对于构建该复杂的对象&#xf…

Vue2 第六节 key的作用与原理

&#xff08;1&#xff09;虚拟DOM &#xff08;2&#xff09;v-for中的key的作用 一.虚拟DOM 1.虚拟DOM就是内存中的数据 2.原生的JS没有虚拟DOM: 如果新的数据和原来的数据有重复数据&#xff0c;不会在原来的基础上新加数据&#xff0c;而是重新生成一份 3. Vue会有虚拟…

结构方程模型的绘制

模型的绘制是我们最终呈现出的一个结果 所以说这个课程主要关注的有两点 第一点就是模型图的绘制 第二点就是结果的解释 关于中间计算过程和背后的理论的一个结果 在本文章的所有的讲解过程中 只注重模型图的绘制方法 如何高效的绘制出所需要的一个模型图 同时能够调整…

【小吉带你学Git】Git分支

&#x1f38a;专栏【Git】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f916;概述&#x1f354;什么是分支&#x1f354;使用分支的好处&am…

百万QPS系统如何设计?

一、关系链业务简介 从主站业务角度来看&#xff0c;关系链指的是用户A与用户B的关注关系。以关注属性细分&#xff0c;以关注&#xff08;订阅&#xff09;为主&#xff0c;还涉及拉黑、悄悄关注、互相关注、特别关注等多种属性或状态。目前主站关系链量级较大&#xff0c;且还…

九、HAL_IWDG独立看门狗的使用

1、开发环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 2、IWDG简介 (1)IWDG即独立看门狗。 (2)看门狗本质上是一个定时器&#xff0c;设置一个时间&#xff0c;时间到即让程序复位。所以需要在在时间未到之前重置定时器&#xff0c;也就是喂…

线性表详细讲解

2.1 线性表的定义和特点2.2 案例引入2.3 线程表的类型定义2.4 线性表的顺序表示和实现2.4.1 线性表的顺序存储表示2.4.2 线性表的结构类型定义2.4.3 顺序表基本操作的实现2.4.4 顺序表总结 2.5 线性表的链式表示和实现2.5.1 线性表的链式存储表示2.5.2 单链表的实现&#xff08…

ARM裸机-3

1、嵌入式和单片机的区别 1.1、芯片平台 主流的单片机平台&#xff1a;51、PIC、STM32、AVR、MSP430等 主流的嵌入式平台&#xff1a;ARM、PPC、MIPS 1.2、资源、价格、应用领域 单片机片上资源有限、价格低、应用领域多为小家电、终端设备等。 嵌入式系统片上资源丰富、价格…

数据库连接及使用Statement对象完成CRUD

一、数据库连接&#xff1a; 二、使用Statement对象完成CRUD&#xff1a; 1、插入&#xff1a; 2、删除 3、修改 4、查询 三、ORM对象关系映射

数据结构:顺序表详解

数据结构&#xff1a;顺序表详解 一、 线性表二、 顺序表概念及结构1. 静态顺序表&#xff1a;使用定长数组存储元素。2. 动态顺序表&#xff1a;使用动态开辟的数组存储。三、接口实现1. 创建2. 初始化3. 扩容4. 打印5. 销毁6. 尾插7. 尾删8. 头插9. 头删10. 插入任意位置数据…