/** * 获取私有桶文件的预签名访问 URL * * 用于展示/下载 TOS 私有桶中的文件。 * 自动缓存签名 URL,过期前 5 分钟刷新。 */ import { useState, useEffect, useCallback, useRef } from 'react' import { api } from '@/lib/api' import { USE_MOCK } from '@/contexts/AuthContext' const urlCache = new Map() export function useSignedUrl(originalUrl: string | undefined | null) { const [signedUrl, setSignedUrl] = useState(null) const [loading, setLoading] = useState(false) const mountedRef = useRef(true) useEffect(() => { mountedRef.current = true return () => { mountedRef.current = false } }, []) const fetchSignedUrl = useCallback(async () => { if (!originalUrl) { setSignedUrl(null) return } // Mock 模式直接返回原始 URL if (USE_MOCK) { setSignedUrl(originalUrl) return } // 非 TOS URL(如外部链接)直接返回 if (!originalUrl.includes('tos-cn-') && !originalUrl.includes('volces.com') && !originalUrl.startsWith('uploads/')) { setSignedUrl(originalUrl) return } // 检查缓存(提前 5 分钟过期) const cached = urlCache.get(originalUrl) if (cached && cached.expireAt > Date.now() + 5 * 60 * 1000) { setSignedUrl(cached.signedUrl) return } setLoading(true) try { const expireSeconds = 3600 const url = await api.getSignedUrl(originalUrl, expireSeconds) if (mountedRef.current) { setSignedUrl(url) urlCache.set(originalUrl, { signedUrl: url, expireAt: Date.now() + expireSeconds * 1000, }) } } catch { // 签名失败时回退到原始 URL if (mountedRef.current) { setSignedUrl(originalUrl) } } finally { if (mountedRef.current) { setLoading(false) } } }, [originalUrl]) useEffect(() => { fetchSignedUrl() }, [fetchSignedUrl]) return { signedUrl, loading, refresh: fetchSignedUrl } } /** * 批量获取签名 URL 的工具函数 */ export async function getSignedUrls(urls: string[]): Promise> { const result = new Map() if (USE_MOCK) { urls.forEach(u => result.set(u, u)) return result } await Promise.all( urls.map(async (url) => { try { const signed = await api.getSignedUrl(url) result.set(url, signed) } catch { result.set(url, url) } }) ) return result }