Your Name 3a444864ac feat: 腾讯云 COS 迁移至火山引擎 TOS 对象存储
签名算法从 COS HMAC-SHA1 改为 TOS V4 HMAC-SHA256,
更新前后端上传凭证字段、配置项、备份脚本和文档。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 11:02:15 +08:00

115 lines
3.3 KiB
TypeScript

'use client'
import { useState, useCallback } from 'react'
import { api } from '@/lib/api'
import { USE_MOCK } from '@/contexts/AuthContext'
interface UploadResult {
url: string
file_key: string
file_name: string
file_size: number
}
interface UseOSSUploadReturn {
upload: (file: File) => Promise<UploadResult>
isUploading: boolean
progress: number
error: string | null
reset: () => void
}
export function useOSSUpload(fileType: string = 'general'): UseOSSUploadReturn {
const [isUploading, setIsUploading] = useState(false)
const [progress, setProgress] = useState(0)
const [error, setError] = useState<string | null>(null)
const reset = useCallback(() => {
setIsUploading(false)
setProgress(0)
setError(null)
}, [])
const upload = useCallback(async (file: File): Promise<UploadResult> => {
setIsUploading(true)
setProgress(0)
setError(null)
try {
if (USE_MOCK) {
// Mock 模式:模拟 2 秒上传
for (let i = 0; i <= 100; i += 20) {
await new Promise(r => setTimeout(r, 400))
setProgress(i)
}
const result: UploadResult = {
url: `https://mock-oss.example.com/${fileType}/${Date.now()}_${file.name}`,
file_key: `${fileType}/${Date.now()}_${file.name}`,
file_name: file.name,
file_size: file.size,
}
setProgress(100)
setIsUploading(false)
return result
}
// 1. 获取上传凭证
setProgress(10)
const policy = await api.getUploadPolicy(fileType)
// 2. 构建 TOS 直传 FormData
const fileKey = `${policy.dir}${Date.now()}_${file.name}`
const formData = new FormData()
formData.append('key', fileKey)
formData.append('x-tos-algorithm', policy.x_tos_algorithm)
formData.append('x-tos-credential', policy.x_tos_credential)
formData.append('x-tos-date', policy.x_tos_date)
formData.append('x-tos-signature', policy.x_tos_signature)
formData.append('policy', policy.policy)
formData.append('success_action_status', '200')
formData.append('file', file)
// 3. 上传到 TOS
setProgress(30)
const xhr = new XMLHttpRequest()
await new Promise<void>((resolve, reject) => {
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
setProgress(30 + Math.round((e.loaded / e.total) * 50))
}
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve()
} else {
reject(new Error(`上传失败: ${xhr.status}`))
}
}
xhr.onerror = () => reject(new Error('网络错误'))
xhr.open('POST', policy.host)
xhr.send(formData)
})
// 4. 回调通知后端
setProgress(90)
const result = await api.fileUploaded(fileKey, file.name, file.size, fileType)
setProgress(100)
setIsUploading(false)
return {
url: result.url,
file_key: result.file_key,
file_name: result.file_name,
file_size: result.file_size,
}
} catch (err) {
const message = err instanceof Error ? err.message : '上传失败'
setError(message)
setIsUploading(false)
throw err
}
}, [fileType])
return { upload, isUploading, progress, error, reset }
}