feat(api): 接入币安公共接口,升级为股票+加密货币的双轨制全市场行情引擎
This commit is contained in:
parent
80029817a9
commit
48e834e338
@ -17,33 +17,50 @@ 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 {
|
||||||
const tCode = getTencentSymbol(asset);
|
if (asset.type === 'STOCK') {
|
||||||
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
|
const tCode = getTencentSymbol(asset);
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
|
||||||
const decoder = new TextDecoder('gbk');
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const text = decoder.decode(arrayBuffer);
|
const decoder = new TextDecoder('gbk');
|
||||||
|
const text = decoder.decode(arrayBuffer);
|
||||||
|
|
||||||
const match = text.match(/="([^"]+)"/);
|
const match = text.match(/="([^"]+)"/);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
const dataArr = match[1].split('~');
|
const dataArr = match[1].split('~');
|
||||||
const latestPrice = dataArr[3];
|
const latestPrice = dataArr[3];
|
||||||
|
|
||||||
if (latestPrice && !isNaN(Number(latestPrice)) && Number(latestPrice) > 0) {
|
if (latestPrice && !isNaN(Number(latestPrice)) && Number(latestPrice) > 0) {
|
||||||
const stockName = dataArr[1] || null;
|
const stockName = dataArr[1] || null;
|
||||||
await db.update(assets)
|
await db.update(assets)
|
||||||
.set({ latestPrice: latestPrice, name: stockName })
|
.set({ latestPrice: latestPrice, name: stockName })
|
||||||
.where(eq(assets.id, asset.id));
|
.where(eq(assets.id, asset.id));
|
||||||
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) {
|
||||||
|
|||||||
@ -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="选择交易所" />
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user