feat(api): 接入币安公共接口,升级为股票+加密货币的双轨制全市场行情引擎

This commit is contained in:
kennethcheng 2026-04-28 13:21:28 +08:00
parent 80029817a9
commit 48e834e338
3 changed files with 49 additions and 24 deletions

View File

@ -17,16 +17,16 @@ function getTencentSymbol(asset: { symbol: string; exchange: string | null }): s
} }
} }
export async function syncAllStockPrices() { export async function syncAllMarketPrices() {
const stockAssets = await db const allAssets = await db
.select() .select()
.from(assets) .from(assets);
.where(eq(assets.type, 'STOCK'));
let successCount = 0; let successCount = 0;
for (const asset of stockAssets) { for (const asset of allAssets) {
try { try {
if (asset.type === 'STOCK') {
const tCode = getTencentSymbol(asset); const tCode = getTencentSymbol(asset);
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' }); const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
@ -46,6 +46,23 @@ export async function syncAllStockPrices() {
successCount++; 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) { } catch (error) {
console.warn(`[行情引擎] 同步 ${asset.symbol} 失败,保持原价:`, error); console.warn(`[行情引擎] 同步 ${asset.symbol} 失败,保持原价:`, error);
} }

View File

@ -67,6 +67,7 @@ export function AddAssetDialog() {
}); });
const exchangeValue = useWatch({ control: form.control, name: 'exchange' }); const exchangeValue = useWatch({ control: form.control, name: 'exchange' });
const typeValue = useWatch({ control: form.control, name: 'type' });
useEffect(() => { useEffect(() => {
if (!exchangeValue) return; if (!exchangeValue) return;
@ -82,6 +83,13 @@ export function AddAssetDialog() {
} }
}, [exchangeValue, form]); }, [exchangeValue, form]);
useEffect(() => {
if (typeValue === 'CRYPTO') {
form.setValue('exchange', 'CRYPTO', { shouldValidate: true });
form.setValue('baseCurrency', 'USD', { shouldValidate: true });
}
}, [typeValue, form]);
function onSubmit(values: AddAssetForm) { function onSubmit(values: AddAssetForm) {
startTransition(async () => { startTransition(async () => {
const result = await createAsset(values); const result = await createAsset(values);
@ -149,7 +157,7 @@ export function AddAssetDialog() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> / (Exchange)</FormLabel> <FormLabel> / (Exchange)</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}> <Select onValueChange={field.onChange} defaultValue={field.value} disabled={typeValue === 'CRYPTO'}>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="选择交易所" /> <SelectValue placeholder="选择交易所" />

View File

@ -3,21 +3,21 @@
import { useState, useTransition } from 'react'; import { useState, useTransition } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react'; import { RefreshCw } from 'lucide-react';
import { syncAllStockPrices } from '@/actions/market'; import { syncAllMarketPrices } from '@/actions/market';
export function SyncButton() { export function SyncButton() {
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
function handleClick() { function handleClick() {
startTransition(async () => { startTransition(async () => {
await syncAllStockPrices(); await syncAllMarketPrices();
}); });
} }
return ( return (
<Button onClick={handleClick} disabled={isPending}> <Button onClick={handleClick} disabled={isPending}>
<RefreshCw className={`h-4 w-4 mr-2 ${isPending ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-4 w-4 mr-2 ${isPending ? 'animate-spin' : ''}`} />
{isPending ? '同步中...' : '同步股票行情'} {isPending ? '同步中...' : '同步全市场行情'}
</Button> </Button>
); );
} }