基于OSS前端直传的分片上传以及断点续传

一、大文件分片上传

原型

大文件如果直接上传的话由于nginx的限制会导致响应500报错,或者响应时间过长导致响应超时

image.png

并且大文件上传有如下缺点

  1. 上传时间长: 对于大文件,直接上传可能需要较长时间,特别是在网络速度较慢或不稳定的情况下。这可能会导致用户等待时间过长,影响用户体验。
  2. 内存占用: 直接上传大文件可能会占用大量内存,特别是在客户端进行文件读取和上传的情况下。这可能会导致客户端程序消耗大量内存资源,甚至在上传大型文件时出现内存溢出的情况。
  3. 网络传输中断: 在网络不稳定的环境下,上传大文件可能会遇到网络传输中断的问题。这会导致上传过程中断,需要重新开始上传,从而增加了额外的时间和网络流量。
  4. 服务器端资源消耗: 直接上传大文件可能会对服务器端资源造成较大负担,特别是在高并发情况下。大文件上传会占用服务器的网络带宽和处理能力,可能会影响服务器的稳定性和性能。

解决方案

根据这种情况我们往往采用分片上传来进行解决
分片上传:将一个大文件在前端进行分片,并将每个小分片进行编码异步上传至后端服务器中。后端收到这些分片后解码并按顺序进行重组

优点:

  1. 提高上传速度。在网络带宽允许的情况下,可以同时上传多个文件片段,从而加快了整个文件的上传速度
  2. 方便前端做上传进度条处理,提高用户体验
  3. 断点续传,分片上传过程中若出现了网络问题或其他问题导致上传中断,只需要上传中断的部分而不需要上传整个文件,提高了上传的可靠性

由于公司采用的是阿里云的oss对象存储,oss中已经我们提供了分片上传这样一项服务,因此我们只需要在前端简单的配置便可以实现分片上传并返回进度条,前端甚至不需要手动分片

import OSS from "ali-oss";
interface OSSConfig {
  accessKeyId: string;
  accessKeySecret: string;
  region: string;
  bucket: string;
}

export class OssService {
  private client: any;
  constructor(ossConfig: OSSConfig) {
    this.client = new OSS({
      accessKeyId: ossConfig.accessKeyId,
      accessKeySecret: ossConfig.accessKeySecret,
      region: ossConfig.region,
      bucket: ossConfig.bucket,
    });
  }

  // 开始分片上传。
  async multipartUpload(
    ossPath: string,
    file: File,
    progressCallback: (progress: number) => void
  ) {
    return new Promise(async (resolve, reject) => {
      try {
        const result = await this.client.multipartUpload(ossPath, file, {
          // 回调返回进度条
          progress: async (p: number) => {
            progressCallback(p);
          },
        });
        resolve(result);
      } catch (e: any) {
        if (e.code === "ConnectionTimeoutError") {
          reject("TimeoutError");
        }
        reject(e);
      }
    });
  }
}
import { OssService } from "@/util/OSS/index";


// OSS 配置
const ossConfig = {
  accessKeyId: "accessKeyId",
  accessKeySecret: "accessKeySecret",
  endpoint: "endpoint",
  bucket: "bucket",
  region: "region",
};

const oss = new OssService(ossConfig);


// 以naiveui UI框架 的自定义上传 举例
async function customRequest({
  file,
  data,
  onFinish,
  onError,
}: UploadCustomRequestOptions) {
  try {
    // 工具类引用
    const res = await oss.multipartUpload(
      `/file/${file.file?.name}`,
      file.file as File,
      (e) => {
        // 返回进度条,做进度条处理
        console.log(e);
      }
    );
    onFinish();
    message.success("上传成功");
  } catch (e) {
    onError();
    message.error("上传失败");
  }
}

大致效果

动画.gif
动画2.gif

缺点

目前的OSS配置项是保存在前端,因此安全性不高。即使通过后端加密后返回最终也需要在前端进行解密,对于懂点逆向的人来说,需要破解难度不高
因此,需要将OSS的配置项存储到后端,前端通过接口请求后端返回OSS的一个临时授权凭证(STS),根据这个临时授权凭证便可以进行前端直传到OSS服务器

实现方法

  1. 通过官网进行配置

使用STS临时访问凭证访问OSS_对象存储(OSS)-阿里云帮助中心

  1. 编写生成sts临时凭证接口
import * as OSS from 'ali-oss';
import * as STS from '@alicloud/sts-sdk';
import ossConfig, { STSConfig } from 'src/config/oss';
/**
   * 获取签名
   */
  async getSignature() {
    const stsEndpoint = STSConfig.stsEndpoint;
    const accessKeyId = STSConfig.accessKeyId;
    const accessKeySecret = STSConfig.accessKeySecret;
    const roleArn = STSConfig.roleArn;

    // 生成签名,策略等信息
    const sts = new STS({
      endpoint: stsEndpoint,
      accessKeyId: accessKeyId,
      accessKeySecret: accessKeySecret,
    });
    // 响应给客户端的签名和策略等信息
    return {
      ...(await sts.assumeRole(roleArn, 'RamOssTest')),
    };
  }
  1. 前端拿到临时凭证后再进行直传
import OSS from "ali-oss";
import { oss_Config } from "@/config";
import { getSignature } from "@/api/common/upload";

export class OssService {
  private client: any;
  // 定义中断点。
  protected abortCheckpoint: any;

  constructor() {
    this.initOss();
  }
  // 新增凭证请求
  async initOss() {
    const { Credentials } = (await getSignature()).data;
    this.client = new OSS({
      accessKeyId: Credentials.AccessKeyId,
      accessKeySecret: Credentials.AccessKeySecret,
      stsToken: Credentials.SecurityToken,
      region: oss_Config.region,
      bucket: oss_Config.bucket,
    });
  }

  // 开始分片上传。
  async multipartUpload(
    ossPath: string,
    file: File,
    progressCallback: (progress: number) => void
  ) {
    return new Promise(async (resolve, reject) => {
      try {
        const result = await this.client.multipartUpload(ossPath, file, {
          checkpoint: this.abortCheckpoint,
          progress: async (p: number, cpt: any) => {
            // 为中断点赋值。
            this.abortCheckpoint = cpt;
            progressCallback(p);
          },
        });
        resolve(result);
      } catch (e: any) {
        // 捕获超时异常。
        if (e.code === "ConnectionTimeoutError") {
          reject("TimeoutError");
        }
        reject(e);
      }
    });
  }

  // 暂停上传。
  pause() {
    this.client.cancel();
  }
}

// 自定义文件上传 & 续传
async function customRequest({
  file,
  onFinish,
  onError,
}: UploadCustomRequestOptions) {
  try {
    // 文件重命名
    const fileExtension = getFileExtension(file.file?.name as string);
    // const newFileName = (await calculateFileHash(file.file as File)) + fileExtension;
    const newFileName = uuid() + fileExtension;

    // 设置上传进度
    processing.value = true;

    const res: any = await oss.multipartUpload(
      `/file/${newFileName}`,
      file.file as File,
      (e) => {
        percentage.value = Math.floor(e * 100);
      }
    );

    processing.value = false;
    file.url = `http://${oss_Config.bucket}.${oss_Config.endpoint}${res.name}`;
    fileList.value.push(file);

    onFinish();
    emit("update:value", fileList.value);
    message.success("上传成功");
  } catch (e) {
    onError();
    processing.value = false;
    message.error("上传失败");
  }
}

原理

image.png


二、断点续传

断点续传是一种网络传输技术,用于在文件传输过程中实现中断后的续传操作。其核心思想是在文件传输过程中保存传输的断点信息,以便在传输中断或失败后能够在之后的某个时间点恢复传输,而不需要重新开始传输整个文件

结合阿里云OSS我们也很容易可以实现断点续传

只需要在OSS工具类中新加两个地方即可实现!

  async multipartUpload(
    ossPath: string,
    file: File,
    progressCallback: (progress: number) => void
  ) {
    return new Promise(async (resolve, reject) => {
      try {
        const result = await this.client.multipartUpload(ossPath, file, {
          checkpoint: this.abortCheckpoint, // 新加
          progress: async (p: number, cpt: any) => {
            // 为中断点赋值。
            this.abortCheckpoint = cpt; // 新加
            progressCallback(p);
          },
        });
        resolve(result);
      } catch (e: any) {
        // 捕获超时异常。
        if (e.code === "ConnectionTimeoutError") {
          reject("TimeoutError");
        }
        reject(e);
      }
    });
  }

效果

动画3.gif

原理

image.png

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值