// Common API 相关接口 import { BASE_URL } from './constants' export interface ApiResponse { code: number successful: boolean message: string data: T } export interface TokenResponse { code: number message: string data: { token: string } successful: boolean } export interface TokenWithDomainResponse { code: number message: string data: { token: string domain: string } successful: boolean } export interface QiniuUploadResponse { hash: string key: string url?: string } // 获取七牛云上传token(包含domain) export const getUploadToken = async (timeoutMs: number = 10000): Promise<{ token: string, domain: string }> => { // 添加超时控制 const controller = new AbortController() const timeoutId = setTimeout(() => { console.log(`Request timeout(${timeoutMs / 1000}s),aborting...`) controller.abort() }, timeoutMs) try { const response = await fetch(`${BASE_URL}/common/get-upload-token`, { method: "GET", headers: { Accept: "application/json", "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, signal: controller.signal, mode: "cors", }) // 请求完成后清除超时 clearTimeout(timeoutId) if (!response.ok) { const errorText = await response.text() console.error("Get token response error:", response.status, errorText) throw new Error(`Get token failed: ${response.status} ${response.statusText}`) } const result: TokenWithDomainResponse | TokenResponse = await response.json() console.log("Get token response:", result) if (result.code === 0 && result.successful && result.data.token) { console.log("Successfully get token") // Support both old and new API response formats const domain = 'domain' in result.data ? result.data.domain : 'cdn.qikongjian.com' return { token: result.data.token, domain: domain } } else { throw new Error(result.message || "Get token failed, server did not return a valid token") } } catch (error) { clearTimeout(timeoutId) console.error("Get upload token failed:", error) if (error instanceof Error) { if (error.name === "AbortError") { throw new Error("Request timeout, please check your network connection") } else if (error.message.includes("Failed to fetch")) { throw new Error("Network connection failed, possibly due to CORS policy or server unreachable") } } throw error } } // 生成唯一文件名 export const generateUniqueFileName = (originalName: string): string => { const timestamp = Date.now() const randomStr = Math.random().toString(36).substring(2, 8) const extension = originalName.split(".").pop() return `videos/${timestamp}_${randomStr}.${extension}` } // 七牛云上传 export const uploadToQiniu = async ( file: File, token: string, onProgress?: (progress: number) => void ): Promise => { const uniqueFileName = generateUniqueFileName(file.name) const formData = new FormData() formData.append("token", token) formData.append("key", uniqueFileName) formData.append("file", file) return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.upload.addEventListener("progress", (event) => { if (event.lengthComputable && onProgress) { const progress = Math.round((event.loaded / event.total) * 100) onProgress(progress) } }) xhr.addEventListener("load", () => { if (xhr.status >= 200 && xhr.status < 300) { try { const response: QiniuUploadResponse = JSON.parse(xhr.responseText) const qiniuUrl = `https://cdn.qikongjian.com/${response.key || uniqueFileName}` console.log("Qiniu cloud upload success:", response) resolve(qiniuUrl) } catch (error) { console.error("Parse response failed:", error, "Original response:", xhr.responseText) reject(new Error(`Parse upload response failed: ${xhr.responseText}`)) } } else { console.error("Qiniu cloud upload failed:", xhr.status, xhr.statusText, xhr.responseText) reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`)) } }) xhr.addEventListener("error", (e) => { console.error("Upload network error:", e) reject(new Error("Network error, upload failed")) }) xhr.addEventListener("abort", () => { reject(new Error("Upload aborted")) }) xhr.open("POST", "https://up-z2.qiniup.com") xhr.send(formData) }) }