oss 直传
分片上传
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import COS from 'cos-js-sdk-v5'
|
||||
|
||||
import {
|
||||
confirmOssUpload,
|
||||
getOssCredentials,
|
||||
type OssCredentialsResponse
|
||||
} from '@/api/file'
|
||||
|
||||
export type DirectUploadType = 'video'
|
||||
|
||||
export interface DirectUploadProgress {
|
||||
/** 0-100 */
|
||||
percent: number
|
||||
loaded: number
|
||||
total: number
|
||||
speed: number
|
||||
}
|
||||
|
||||
export interface DirectUploadResult {
|
||||
id: number
|
||||
cid: number
|
||||
type: number
|
||||
name: string
|
||||
uri: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface DirectUploadOptions {
|
||||
file: File
|
||||
type: DirectUploadType
|
||||
cid?: number
|
||||
onProgress?: (progress: DirectUploadProgress) => void
|
||||
/** 返回 false 时由调用方自行降级到老链路 */
|
||||
onFallback?: (provider: string) => boolean | void
|
||||
}
|
||||
|
||||
const SLICE_SIZE = 5 * 1024 * 1024 // 5MB
|
||||
const ASYNC_LIMIT = 3
|
||||
|
||||
function buildKey(prefix: string, file: File): string {
|
||||
const ext = (file.name.split('.').pop() || 'mp4').toLowerCase()
|
||||
const ts = Date.now()
|
||||
const rand = Math.random().toString(36).slice(2, 10)
|
||||
return `${prefix}${ts}-${rand}.${ext}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 直传到腾讯云 COS(含 STS 凭证申请、分片上传、回执)
|
||||
* 不支持降级 / fallback=true 时抛错,由调用方决定走老链路。
|
||||
*/
|
||||
export async function uploadVideoDirectToCos(options: DirectUploadOptions): Promise<DirectUploadResult> {
|
||||
const credentials: OssCredentialsResponse = await getOssCredentials({ type: options.type })
|
||||
|
||||
if (credentials.fallback) {
|
||||
const handled = options.onFallback?.(credentials.provider) ?? false
|
||||
if (!handled) {
|
||||
throw new DirectUploadFallbackError(credentials.provider)
|
||||
}
|
||||
// 调用方自行处理,本函数早退
|
||||
throw new DirectUploadFallbackError(credentials.provider)
|
||||
}
|
||||
|
||||
if (!credentials.credentials || !credentials.bucket || !credentials.region || !credentials.key_prefix) {
|
||||
throw new Error('STS 凭证返回缺失字段')
|
||||
}
|
||||
|
||||
if (credentials.max_size && options.file.size > credentials.max_size) {
|
||||
const mb = Math.round(credentials.max_size / 1024 / 1024)
|
||||
throw new Error(`视频体积超出上限(${mb}MB)`)
|
||||
}
|
||||
|
||||
const cred = credentials.credentials
|
||||
const expiredTime = credentials.expired_time ?? 0
|
||||
const startTime = credentials.start_time ?? Math.floor(Date.now() / 1000)
|
||||
|
||||
const cos = new COS({
|
||||
getAuthorization(_options, callback) {
|
||||
callback({
|
||||
TmpSecretId: cred.tmpSecretId,
|
||||
TmpSecretKey: cred.tmpSecretKey,
|
||||
SecurityToken: cred.sessionToken,
|
||||
StartTime: startTime,
|
||||
ExpiredTime: expiredTime
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const key = buildKey(credentials.key_prefix, options.file)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
cos.uploadFile(
|
||||
{
|
||||
Bucket: credentials.bucket as string,
|
||||
Region: credentials.region as string,
|
||||
Key: key,
|
||||
Body: options.file,
|
||||
SliceSize: SLICE_SIZE,
|
||||
AsyncLimit: ASYNC_LIMIT,
|
||||
onProgress(info) {
|
||||
if (!options.onProgress) return
|
||||
const total = info.total || options.file.size
|
||||
options.onProgress({
|
||||
percent: Math.min(99, Math.round((info.percent || 0) * 100)),
|
||||
loaded: info.loaded || 0,
|
||||
total,
|
||||
speed: info.speed || 0
|
||||
})
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const confirmed = await confirmOssUpload({
|
||||
type: options.type,
|
||||
key,
|
||||
name: options.file.name,
|
||||
size: options.file.size,
|
||||
content_type: options.file.type || '',
|
||||
cid: options.cid ?? 0
|
||||
})
|
||||
|
||||
options.onProgress?.({ percent: 100, loaded: options.file.size, total: options.file.size, speed: 0 })
|
||||
|
||||
return confirmed
|
||||
}
|
||||
|
||||
/** 当后端 fallback=true 时抛出,调用方据此降级到老链路 */
|
||||
export class DirectUploadFallbackError extends Error {
|
||||
readonly provider: string
|
||||
constructor(provider: string) {
|
||||
super(`COS 直传不可用,当前驱动: ${provider}`)
|
||||
this.name = 'DirectUploadFallbackError'
|
||||
this.provider = provider
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user