import COS from 'cos-js-sdk-v5' import { apiFetch } from '@/lib/api' interface TempKeyResponse { credentials: { tmpSecretId: string tmpSecretKey: string sessionToken: string } startTime: number expiredTime: number bucket: string region: string } let cachedKey: TempKeyResponse | null = null async function getTempKey(forceRefresh = false): Promise { const now = Math.floor(Date.now() / 1000) // 提前 60 秒失效,避免边界问题 if (!forceRefresh && cachedKey && cachedKey.expiredTime - 60 > now) { return cachedKey } const res = await apiFetch('/api/cos/temp-key') if (!res.ok) { throw new Error('获取上传凭证失败') } const data = (await res.json()) as TempKeyResponse cachedKey = data return data } function buildCosClient(key: TempKeyResponse): COS { return new COS({ getAuthorization: (_options, callback) => { callback({ TmpSecretId: key.credentials.tmpSecretId, TmpSecretKey: key.credentials.tmpSecretKey, SecurityToken: key.credentials.sessionToken, StartTime: key.startTime, ExpiredTime: key.expiredTime, }) }, }) } function randomFilename(originalName: string): string { const ext = originalName.split('.').pop() || 'jpg' const timestamp = Date.now() const randomStr = Math.random().toString(36).substring(2, 8) return `mini_game/images/${timestamp}_${randomStr}.${ext}` } /** * 上传单个文件到腾讯云 COS,返回可访问的 URL。 */ export async function uploadToCos(file: File | Blob, originalName?: string): Promise { const name = originalName || (file as File).name || 'upload.jpg' const key = await getTempKey() const cos = buildCosClient(key) const filename = randomFilename(name) return new Promise((resolve, reject) => { cos.putObject( { Bucket: key.bucket, Region: key.region, Key: filename, Body: file as File, }, (err) => { if (err) { reject(new Error(err.message || '上传失败')) return } const url = `https://${key.bucket}.cos.${key.region}.myqcloud.com/${filename}` resolve(url) } ) }) }