120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
/**
|
|
* 上传相关 API(表单上传 + OSS 直传)
|
|
*/
|
|
import apiClient from "../client"
|
|
import { getOrCreateDefaultProject } from "../projects"
|
|
import type { UploadResult, DirectUploadPrepareResult, DirectUploadCompleteResult } from "./types"
|
|
|
|
/** 表单上传素材(小文件) */
|
|
export const uploadAsset = async (formData: FormData): Promise<UploadResult> => {
|
|
const response = await apiClient.post("/upload", formData, {
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
timeout: 30 * 60 * 1000,
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
/** 预签名直传准备 */
|
|
export const prepareDirectUpload = async (data: {
|
|
project_id: string
|
|
library_id: string
|
|
filename: string
|
|
content_type: string
|
|
file_size: number
|
|
}): Promise<DirectUploadPrepareResult> => {
|
|
const response = await apiClient.post("/upload/direct/prepare", data)
|
|
return response.data
|
|
}
|
|
|
|
/** 直传完成确认 */
|
|
export const completeDirectUpload = async (data: {
|
|
project_id: string
|
|
library_id: string
|
|
storage_key: string
|
|
}): Promise<DirectUploadCompleteResult> => {
|
|
const response = await apiClient.post("/upload/direct/complete", data)
|
|
return response.data
|
|
}
|
|
|
|
/** 直传上传(大文件推荐),支持可选进度回调 */
|
|
export const uploadAssetDirect = async (data: {
|
|
file: File
|
|
library_id: string
|
|
onProgress?: (percent: number) => void
|
|
}): Promise<DirectUploadCompleteResult> => {
|
|
const project = await getOrCreateDefaultProject()
|
|
|
|
const prepared = await prepareDirectUpload({
|
|
project_id: project.id,
|
|
library_id: data.library_id,
|
|
filename: data.file.name,
|
|
content_type: data.file.type || "application/octet-stream",
|
|
file_size: data.file.size,
|
|
})
|
|
|
|
const directForm = new FormData()
|
|
Object.entries(prepared.fields).forEach(([key, value]) => directForm.append(key, value))
|
|
directForm.append("file", data.file)
|
|
|
|
// 使用 XMLHttpRequest 以获取上传进度 + 超时控制 + 详细错误诊断
|
|
await new Promise<void>((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest()
|
|
xhr.open(prepared.method, prepared.upload_url)
|
|
|
|
// 超时 10 分钟
|
|
xhr.timeout = 10 * 60 * 1000
|
|
|
|
xhr.upload.onprogress = (e) => {
|
|
if (e.lengthComputable && data.onProgress) {
|
|
data.onProgress(Math.round((e.loaded / e.total) * 100))
|
|
}
|
|
}
|
|
xhr.onload = () => {
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
resolve()
|
|
} else {
|
|
// 解析 OSS 返回的 XML 错误信息
|
|
let ossError = ""
|
|
try {
|
|
const codeMatch = xhr.responseText.match(/<Code>([^<]+)<\/Code>/)
|
|
const msgMatch = xhr.responseText.match(/<Message>([^<]+)<\/Message>/)
|
|
if (codeMatch || msgMatch) {
|
|
ossError = ` [OSS: ${codeMatch?.[1] || "unknown"} - ${msgMatch?.[1] || "unknown"}]`
|
|
}
|
|
} catch {
|
|
// 无法解析响应体
|
|
}
|
|
const detail = `OSS 直传失败: HTTP ${xhr.status} ${xhr.statusText}${ossError}`
|
|
console.error("[OSS Upload] 直传失败:", {
|
|
url: prepared.upload_url,
|
|
storage_key: prepared.storage_key,
|
|
status: xhr.status,
|
|
statusText: xhr.statusText,
|
|
})
|
|
reject(new Error(detail))
|
|
}
|
|
}
|
|
xhr.onerror = () => {
|
|
console.error("[OSS Upload] 网络错误:", {
|
|
url: prepared.upload_url,
|
|
storage_key: prepared.storage_key,
|
|
})
|
|
reject(new Error("OSS 上传网络错误,请检查网络连接"))
|
|
}
|
|
xhr.ontimeout = () => {
|
|
console.error("[OSS Upload] 上传超时:", {
|
|
url: prepared.upload_url,
|
|
storage_key: prepared.storage_key,
|
|
})
|
|
reject(new Error("OSS 上传超时(10分钟),请检查网络或尝试更小的文件"))
|
|
}
|
|
xhr.send(directForm)
|
|
})
|
|
|
|
return completeDirectUpload({
|
|
project_id: project.id,
|
|
library_id: data.library_id,
|
|
storage_key: prepared.storage_key,
|
|
})
|
|
}
|