前端阿里云OSS分片上传

前端阿里云OSS分片上传

npm i ali-oss

原生(Input) VS AntDesign(upload)

原生Input

import { useState } from 'react'
import OSS from 'ali-oss'
import moment from 'moment'
import { useRequest } from 'ahooks'
import { getSecurityToken, insertRecord } from '@/service/upload'
import { generateFileName, guid } from '@/utils'
import { filePreview } from '@/service/user'
// 认证信息 // STS凭证
let credentials: {
  accessKeyId: string;
  accessKeySecret: string;
  stsToken: string;
  expiration: string;
}
const bucket = 'xxx****xxx' // bucket名称
const region = 'xxx****xxx' // oss服务区域名称
const partSize = 1 * 1024 * 1024 // 每个分片大小为1M
const parallel = 3 // 同时上传分片个数
const ossClientMap: any = {} // oss客户端实例
const checkpointMap: any = {} // 所有分片上传文件的检查点
const fileNameMap: any = {} // 文件名
function useOSS() {
  const [percent, setPercent] = useState(0) // 进度
  const [uuid] = useState(guid()) // 产生一个全球唯一标识符
  const [curUrl, setcurUrl] = useState<string>('')
  // 获取STS Token
  function getCredential() {
    return getSecurityToken().then((res) => {
      credentials = {
        accessKeyId: res.accessKeyId,
        accessKeySecret: res.accessKeySecret,
        stsToken: res.token,
        expiration: res.expiration,
      }
      return res
    })
  }

  // 创建OSS Client
  function initOSSClient() {
    ossClientMap[uuid] = new OSS({
      ...credentials,
      bucket,
      region,
    })
  }
  // 文件直传
  const { run, data } = useRequest(insertRecord, {
    manual: true,
  })

  // 普通文件直传
  async function commonUpload(file: File) {
    if (!ossClientMap[uuid]) {
      await initOSSClient()
    }
    return ossClientMap[uuid].put(fileNameMap[uuid], file).then(async (result: any) => {
      run(file.name, file.size, result.name).then((res) => {
        filePreview(res.fileId).then((e) => {
          setcurUrl(e?.url)
        })
      })
    }).catch((err: Error) => {
      console.error(err)
      throw err
    })
  }

  // 断点续传
  async function resumeMultipartUpload() {
    Object.values(checkpointMap[uuid]).forEach((checkpoint: any) => {
      const { uploadId, file, name } = checkpoint
      ossClientMap[uuid].multipartUpload(uploadId, file, {
        parallel,
        partSize, // 每个part 1MB
        // eslint-disable-next-line no-use-before-define
        progress: onMultipartUploadProgress,
        checkpoint,
      }).then(() => {
        console.log('before delete checkpoints === ', checkpointMap[uuid])
        delete checkpointMap[uuid][checkpoint.uploadId]
        console.log('after delete checkpoints === ', checkpointMap[uuid])
        const url = `http://${bucket}.${region}.aliyuncs.com/${name}`
        console.log(`Resume multipart upload ${file.name} succeeded, url === `, url)
        return url
      }).catch((err: Error) => {
        console.log(err)
      })
    })
  }

  // 分片上传产生的回调函数
  /**
   *
   * @param progress 进度
   * @param checkpoint 检擦点
   */
  async function onMultipartUploadProgress(progress: any, checkpoint: any) {
    // 进度更新
    const percentNum = Number.parseInt(`${progress * 100}`, 10)
    setPercent(percentNum)
    console.log(percent)

    checkpointMap[uuid] = {
      [checkpoint.uploadId]: checkpoint,
    }
    // 判断STS Token是否将要过期,过期则重新获取
    const { expiration } = credentials
    const timegap = 14
    // eslint-disable-next-line no-undef
    if (expiration && moment(expiration).subtract(timegap, 'minute').isBefore(moment())) {
      console.log(`STS token will expire in ${timegap} minutes,uploading will pause and resume after getting new STS token`)
      if (ossClientMap[uuid]) {
        ossClientMap[uuid].cancel()
      }
      await getCredential()
      await resumeMultipartUpload()
    }
  }
  // 分片上传
  async function multipartUpload(file: File) {
    if (!ossClientMap[uuid]) {
      await initOSSClient()
    }
    return ossClientMap[uuid].multipartUpload(fileNameMap[uuid], file, {
      parallel,
      partSize,
      progress: onMultipartUploadProgress,
    }).then((result: any) => {
      const url = `http://${bucket}.${region}.aliyuncs.com/${fileNameMap[uuid]}`
      run(file.name, file.size, result.name).then((res) => {
        filePreview(res.fileId).then((e) => {
          setcurUrl(e?.url)
        })
      })
      return url
    }).catch((err: any) => {
      console.error(err)
      throw err
    })
  }

  const upload: any = async (file: File) => {
    try {
      setcurUrl('')
      setPercent(0)
      checkpointMap[uuid] = {}
      // 获取STS Token
      await getCredential()
      // 生成文件名
      fileNameMap[uuid] = generateFileName(file)
      // 如果文件大学小于分片大小,使用普通上传
      if (file.size < partSize) {
        await commonUpload(file)
      } else {
        await multipartUpload(file)
      }
    } catch (error) {
      if (error?.status !== 0) {
        throw error
      }
      return null
    }
  }
  // 暂停上传
  function stop() {
    if (ossClientMap[uuid]) ossClientMap[uuid].cancel()
  }

  // 续传
  function resume() {
    if (ossClientMap[uuid]) resumeMultipartUpload()
  }
  return {
    upload,
    percent,
    stop,
    resume,
    curUrl,
    fileId: data?.fileId,
  }
}
export default useOSS

ant Design ( upload ) 组件

/* eslint-disable no-console */
/* eslint-disable no-use-before-define */
import { useState } from 'react'
import OSS from 'ali-oss'
import moment from 'moment'
import { useRequest } from 'ahooks'
import { getSecurityToken, insertRecord } from '@/service/upload'
import { generateFileName, guid } from '@/utils'

type CustomRequestOptions = {
  onProgress?: (event: {
    percent: number;
  }, file: File) => void;
  onError?: (error: Error) => void;
  onSuccess?: (response: object, file: File) => void;
  data?: object;
  filename?: string;
  file: File;
  withCredentials?: boolean;
  action?: string;
  headers?: object;
}

let credentials: {
  accessKeyId: string;
  accessKeySecret: string;
  stsToken: string;
  expiration: string;
} // STS凭证
const bucket = 'xxx****xxx' // bucket名称
const region = 'xxx****xxx' // oss服务区域名称
const partSize = 1 * 1024 * 1024 // 每个分片大小1MB
const parallel = 3 // 同时上传的分片数
const ossClientMap: any = {} // oss客户端实例
const checkpointMap: any = {} // 所有分片上传文件的检查点
const optionMap: any = {}
const fileNameMap: any = {}
export function useOss(newPath: string = '') {
  const [percent, setPercent] = useState(0)
  const [loading, setLoading] = useState(false)
  const [uuid] = useState(guid())
  // 获取STS Token
  function getCredential() {
    return getSecurityToken().then((res) => {
      credentials = {
        accessKeyId: res.accessKeyId,
        accessKeySecret: res.accessKeySecret,
        stsToken: res.token,
        expiration: res.expiration,
      }
      return res
    })
  }

  // 创建OSS Client
  function initOSSClient() {
    ossClientMap[uuid] = new OSS({
      ...credentials,
      bucket,
      region,
    })
  }

  // 文件直传
  const { run } = useRequest(insertRecord, {
    manual: true,
  })

  // 普通上传
  async function commonUpload(file: any, type?: boolean) {
    let folderPath = ''
    if (type) {
      const folder = file?.webkitRelativePath.split('/')
      folderPath = `${folder.splice(0, folder.length - 1).join('/')}/`
    }
    if (!ossClientMap[uuid]) {
      await initOSSClient()
    }
    return ossClientMap[uuid].put(newPath + folderPath + fileNameMap[uuid], file).then((resFile: any) => {
      const { url } = resFile
      // if (newPath) {
      //   url = `http://${bucket}.${region}.aliyuncs.com/${newPath}${folderPath}${fileNameMap[uuid]}`
      // } else {
      //   url = `http://${bucket}.${region}.aliyuncs.com/${fileNameMap[uuid]}`
      // }
      run(
        file.name,
        file.size,
        resFile.name,
        newPath + folderPath,
      ).then((res) => {
        optionMap[uuid].onSuccess(
          { fileId: res?.fileId, url }, optionMap[uuid].file,
        )
        setLoading(false)
      })
      return url
    })
      .catch((err: Error) => {
        console.error(err)
        throw err
      })
  }

  // 断点续传
  async function resumeMultipartUpload() {
    Object.values(checkpointMap[uuid]).forEach((checkpoint: any) => {
      const { uploadId, file, name } = checkpoint
      ossClientMap[uuid].multipartUpload(uploadId, file, {
        parallel,
        partSize,
        progress: onMultipartUploadProgress,
        checkpoint,
      }).then(() => {
        delete checkpointMap[uuid][checkpoint.uploadId]
        const url = `http://${bucket}.${region}.aliyuncs.com/${name}`
        optionMap[uuid].onSuccess({ fileId: null, url }, optionMap[uuid].file)
        return url
      }).catch((err: Error) => {
        console.error(err)
        throw err
      })
    })
  }

  // 分片上传进度改变回调
  async function onMultipartUploadProgress(
    progress: number,
    checkpoint: any,
  ) {
    const percentNum = Number.parseInt(`${progress * 100}`, 10)
    setPercent(percentNum)
    if (optionMap[uuid]) {
      optionMap[uuid].onProgress({ percent: percentNum }, checkpoint.file)
    }
    checkpointMap[uuid] = {
      [checkpoint.uploadId]: checkpoint,
    }
    // 判断STS Token是否将要过期,过期则重新获取
    const { expiration } = credentials
    const timegap = 2
    if (expiration && moment(expiration).subtract(timegap, 'minute').isBefore(moment())) {
      console.log(`STS token will expire in ${timegap} minutes,uploading will pause and resume after getting new STS token`)
      if (ossClientMap[uuid]) {
        ossClientMap[uuid].cancel()
      }
      await getCredential()
      await resumeMultipartUpload()
    }
  }

  // 分片上传
  async function multipartUpload(file: any, type?: boolean) {
    let folderPath = ''
    if (type) {
      const folder = file?.webkitRelativePath.split('/')
      folderPath = `${folder.splice(0, folder.length - 1).join('/')}/`
    }
    if (!ossClientMap[uuid]) {
      await initOSSClient()
    }
    return ossClientMap[uuid].multipartUpload(newPath + folderPath + fileNameMap[uuid], file, {
      parallel,
      partSize,
      progress: onMultipartUploadProgress,
    }).then((resFile: any) => {
      const { url } = resFile
      // if (newPath) {
      //   url = `http://${bucket}.${region}.aliyuncs.com/${newPath}${folderPath}${fileNameMap[uuid]}`
      // } else {
      //   url = `http://${bucket}.${region}.aliyuncs.com/${fileNameMap[uuid]}`
      // }

      run(
        file.name,
        file.size,
        resFile.name,
        newPath + folderPath,
      ).then((res) => {
        optionMap[uuid].onSuccess(
          { fileId: res?.fileId, url }, optionMap[uuid].file,
        )
        delete ossClientMap[uuid]
        delete optionMap[uuid]
        delete fileNameMap[uuid]
        delete checkpointMap[uuid]
        setLoading(false)
      })
      return url
    }).catch((err: Error) => {
      console.error(`Multipart upload ${file.name} failed === `, err)
      throw err
    })
  }

  const upload: any = async (initOptions: CustomRequestOptions, type?: boolean) => {
    try {
      setLoading(true)
      checkpointMap[uuid] = {}
      // 获取STS Token
      await getCredential()
      optionMap[uuid] = initOptions
      fileNameMap[uuid] = generateFileName(optionMap[uuid].file)
      if (optionMap[uuid].file.size < partSize) {
        return commonUpload(optionMap[uuid].file, type)
      }
      return multipartUpload(optionMap[uuid].file, type)
    } catch (error) {
      if (error?.status !== 0) {
        initOptions.onError?.(error)
        throw error
      }
      return null
    }
  }

  // 暂停上传
  function stop() {
    if (ossClientMap[uuid]) ossClientMap[uuid].cancel()
  }

  // 续传
  function resume() {
    if (ossClientMap[uuid]) resumeMultipartUpload()
  }

  return {
    stop,
    resume,
    upload,
    percent,
    loading,
  }
}

Vue3 和 Vite 结合阿里云 OSS(Object Storage Service,对象存储服务)进行分片上传并监控进度,可以创建一个高效的文件上传组件,通常通过前端JavaScript配合后端API来实现。以下是简要步骤: 1. **安装依赖**: - 使用 npm 或 yarn 安装 `axios`(用于发送HTTP请求)和 `vue-upload-file` 或类似的上传插件。 ```bash npm install axios vue-upload-file @vue/cli-plugin-vite @vue/cli-service-dev ``` 2. **配置Vite**: 在`.env`或`vite.config.js`中添加阿里云OSS的相关配置,如访问密钥、凭证等。 3. **创建组件**: 创建一个自定义的上传组件,使用`vue-upload-file`库处理文件分片上传。例如,你可以创建一个`UploadComponent.vue`: ```html <template> <div> <input type="file" ref="fileInput" /> <button @click="upload">开始上传</button> <p>上传进度: {{ progress }}%</p> </div> </template> <script> import { useFileUpload } from 'vue-upload-file' export default { setup() { const { uploadFile, progress } = useFileUpload({ url: '/api/upload', // 后端接收分片上传的URL onBeforeProgress: (file) => { // 开始分片前的操作,比如设置最大分片数 }, onStart: () => { // 分片上传开始时的回调 }, onProgress: (progress) => { // 监控每个分片上传的进度 }, onFinish: (response) => { // 上传完成后处理响应 }, onError: (error) => { // 错误处理 } }) const handleUpload = async () => { await uploadFile() } return { upload, progress, handleUpload } } } </script> ``` 4. **后端接口**: 设计后端接口来接收分片数据,并在每次接收到部分上传的数据时更新数据库状态,同时返回上传的唯一标识以便后续合并。 5. **进度同步**: 根据后端返回的上传状态,可以在前端持续更新进度展示。 6. **错误处理**: 要处理网络中断、服务器错误等情况,保证用户得到明确的反馈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值