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,33 +17,50 @@ function getTencentSymbol(asset: { symbol: string; exchange: string | null }): s
}
}
export async function syncAllStockPrices() {
const stockAssets = await db
export async function syncAllMarketPrices() {
const allAssets = await db
.select()
.from(assets)
.where(eq(assets.type, 'STOCK'));
.from(assets);
let successCount = 0;
for (const asset of stockAssets) {
for (const asset of allAssets) {
try {
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);
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];
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++;
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) {

View File

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

View File

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