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

前言

阿里云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

猜你喜欢

转载自blog.csdn.net/CYL_2021/article/details/132003653