签名算法从 COS HMAC-SHA1 改为 TOS V4 HMAC-SHA256, 更新前后端上传凭证字段、配置项、备份脚本和文档。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
3.3 KiB
TypeScript
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 }
|
|
}
|