分片上传,普通上传,停止上传,恢复,用了一下分片上传和停止上传,其他还没试过
useAliOssHook
import env from '@/config'
import api from '@/services/aliOss'
import { generateFileName } from '@/utils'
import { message } from '@/utils/AntdGlobal'
import OSS from 'ali-oss'
import dayjs from 'dayjs'
import { nanoid } from 'nanoid'
import { useState } from 'react'
// 阿里OSS
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
}
const ossClientMap: any = {} // oss客户端实例
const fileNameMap: any = {} // 文件名
const checkpointMap: any = {} // 所有分片上传文件的检查点
const optionMap: any = {}
const partSize = 1 * 1024 * 1024 // 每个分片大小1MB
const parallel = 3 // 同时上传的分片数
export const useAliOssHook = (newPath: string = '') => {
const [uuid] = useState(nanoid())
const [percent, setPercent] = useState(0)
const [loading, setLoading] = useState(false)
const { bucket, region } = env.aliOss
// 获取StsToken
const getCredential = async () => {
const { accessKey, accessKeySecret, securityToken, expiration } = await api.getStsToken()
credentials = {
accessKeyId: accessKey,
accessKeySecret: accessKeySecret,
stsToken: securityToken,
expiration: expiration
}
}
// 创建OssClient
const initOSSClient = async () => {
ossClientMap[uuid] = new OSS({
...credentials,
bucket,
region
})
}
// 普通上传
const commonUpload = async (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()
}
try {
const data = await ossClientMap[uuid].put(newPath + folderPath + fileNameMap[uuid], file)
const { url } = data
// optionMap[uuid].onSuccess(
// { fileId: res?.fileId, url }, optionMap[uuid].file,
// )
setLoading(false)
return url
} catch (error) {
const msg = (error as Error).message
message.error(`上传失败,${msg}`)
}
}
// 断点续传
const resumeMultipartUpload = async () => {
Object.values(checkpointMap[uuid]).forEach(async (checkpoint: any) => {
const { uploadId, file, name } = checkpoint
try {
await ossClientMap[uuid].multipartUpload(uploadId, file, {
parallel,
partSize,
progress: onMultipartUploadProgress,
checkpoint
})
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 (error) {
const msg = (error as Error).message
message.error(`上传失败,${msg}`)
}
})
}
// 分片上传进度改变回调
const onMultipartUploadProgress = async (progress: number, checkpoint: any) => {
const percentNum = Number.parseInt(`${progress * 100}`, 10)
setPercent(percentNum)
if (optionMap[uuid] && optionMap[uuid].onProgress) {
optionMap[uuid].onProgress({ percent: percentNum }, checkpoint.file)
}
if (progress === 1 && checkpoint === null) {
// 结束回调
return
}
checkpointMap[uuid] = {
[checkpoint.uploadId]: checkpoint
}
// 判断STS Token是否将要过期,过期则重新获取
const { expiration } = credentials
// const timegap = 2
// if (expiration && moment(expiration).subtract(timegap, 'minute').isBefore(moment())) {
if (expiration && dayjs() > dayjs(expiration)) {
if (ossClientMap[uuid]) {
ossClientMap[uuid].cancel()
}
await getCredential()
await resumeMultipartUpload()
}
}
// 分片上传
const multipartUpload = async (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()
}
const data = await ossClientMap[uuid].multipartUpload(newPath + folderPath + fileNameMap[uuid], file, {
parallel,
partSize,
progress: onMultipartUploadProgress
})
const { name } = data
// 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 name
}
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) {
const msg = (error as Error).message
message.error(`上传失败,${msg}`)
}
}
// 暂停上传
const stop = () => {
if (ossClientMap[uuid]) ossClientMap[uuid].cancel()
}
// 续传
const resume = () => {
if (ossClientMap[uuid]) resumeMultipartUpload()
}
return { stop, resume, upload, percent, loading }
}
utils
// 生成不重复文件名
export function generateFileName(file: any) {
const suffix = file.name.split('.').pop()
const fileName = nanoid()
return `${fileName}.${suffix}`
}
调用
const { upload, percent, stop } = useAliOssHook('服务器上存放上传文件的路径/')
const params = {
file
}
const name = await upload(params)