前端阿里云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,
}
}