'use server'; import { ProxyAgent, setGlobalDispatcher } from 'undici'; import { db } from '@/db'; import { assets } from '@/db/schema'; import { eq } from 'drizzle-orm'; import { revalidatePath } from 'next/cache'; function getTencentSymbol(asset: { symbol: string; exchange: string | null }): string { const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z]/g, ''); switch (asset.exchange) { case 'SSE': return 'sh' + cleanSymbol; case 'SZSE': return 'sz' + cleanSymbol; case 'HKEX': return 'hk' + cleanSymbol; case 'US': default: return 's_us' + cleanSymbol; } } export async function syncAllMarketPrices() { const allAssets = await db .select() .from(assets); const proxyUrl = process.env.HTTPS_PROXY; if (proxyUrl) { const proxyAgent = new ProxyAgent(proxyUrl); setGlobalDispatcher(proxyAgent); } let successCount = 0; for (const asset of allAssets) { try { if (asset.type === 'STOCK') { const tCode = getTencentSymbol(asset); const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' }); const arrayBuffer = await response.arrayBuffer(); const decoder = new TextDecoder('gbk'); const text = decoder.decode(arrayBuffer); const match = text.match(/="([^"]+)"/); if (match && match[1]) { const dataArr = match[1].split('~'); const latestPrice = dataArr[3]; if (latestPrice && !isNaN(Number(latestPrice)) && Number(latestPrice) > 0) { const stockName = dataArr[1] || null; await db.update(assets) .set({ latestPrice: latestPrice, name: stockName }) .where(eq(assets.id, asset.id)); successCount++; } } } else if (asset.type === 'CRYPTO') { const cryptoSymbol = asset.symbol.trim().toUpperCase() + 'USDT'; const response = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${cryptoSymbol}`, { cache: 'no-store' }); if (response.ok) { const data = await response.json(); if (data.price) { await db.update(assets) .set({ latestPrice: data.price, name: asset.symbol.toUpperCase(), }) .where(eq(assets.id, asset.id)); successCount++; } } } } catch (error) { console.warn(`[行情引擎] 同步 ${asset.symbol} 失败,保持原价:`, error); } } revalidatePath('/dashboard'); revalidatePath('/dashboard/assets'); return { success: true, count: successCount }; }