feat(crypto-tail): 尾盘策略页币安 API 状态监控与重连间隔调整

- 尾盘策略页:进入时检测币安 API/WebSocket,异常时红色强提醒
- 异常时提供「重新检测」按钮,不跳转 API 健康页、不做 60s 轮询
- 文案面向小白:无法连接币安 API、需行情数据、稍后重新检测
- 多语言:zh-CN/zh-TW/en 新增 cryptoTailStrategy.binanceApiAlert
- 币安 K 线 WS 重连延迟由 10s 改为 3s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-02-15 01:09:34 +08:00
parent a62f1bd6e8
commit a64d1edbf4
5 changed files with 77 additions and 2 deletions
@@ -130,7 +130,7 @@ class BinanceKlineService {
private fun scheduleReconnect() {
if (reconnectJob?.isActive == true) return
reconnectJob = scope.launch {
delay(10_000)
delay(3_000)
reconnectJob = null
ws5m?.close(1000, "reconnect")
ws15m?.close(1000, "reconnect")
+5
View File
@@ -1402,6 +1402,11 @@
"rerunFailed": "Re-run failed"
},
"cryptoTailStrategy": {
"binanceApiAlert": {
"title": "Cannot connect to Binance API — strategy cannot run for now",
"description": "Tail strategy needs Binance market data to work. The connection failed; this may be a network issue or Binance outage. Try again later by clicking the button below.",
"recheck": "Re-check"
},
"list": {
"title": "Crypto Tail Strategy",
"walletTip": "Use a dedicated wallet for tail strategy. Do not use it for manual trading or copy trading to avoid balance/position issues.",
+5
View File
@@ -1401,6 +1401,11 @@
"rerunFailed": "重新测试失败"
},
"cryptoTailStrategy": {
"binanceApiAlert": {
"title": "无法连接币安 API,策略暂时不能运行",
"description": "尾盘策略需要从币安获取行情数据才能工作。当前连接失败,可能是网络问题或币安服务异常,请稍后点击下方按钮重新检测。",
"recheck": "重新检测"
},
"list": {
"title": "加密尾盘策略",
"walletTip": "请使用单独的钱包运行尾盘策略,避免该钱包用于手动交易、跟单等其他操作,否则可能导致余额或仓位变化,造成策略执行异常。",
+5
View File
@@ -1402,6 +1402,11 @@
"rerunFailed": "重新測試失敗"
},
"cryptoTailStrategy": {
"binanceApiAlert": {
"title": "無法連接幣安 API,策略暫時不能運行",
"description": "尾盤策略需要從幣安取得行情資料才能運作。目前連線失敗,可能是網路問題或幣安服務異常,請稍後點擊下方按鈕重新檢測。",
"recheck": "重新檢測"
},
"list": {
"title": "加密尾盤策略",
"walletTip": "請使用單獨的錢包運行尾盤策略,避免該錢包用於手動交易、跟單等其他操作,否則可能導致餘額或倉位變化,造成策略執行異常。",
+61 -1
View File
@@ -19,7 +19,7 @@ import {
Spin,
Tooltip
} from 'antd'
import { PlusOutlined, EditOutlined, UnorderedListOutlined, InfoCircleOutlined } from '@ant-design/icons'
import { PlusOutlined, EditOutlined, UnorderedListOutlined, InfoCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useMediaQuery } from 'react-responsive'
import { apiService } from '../services/api'
@@ -47,12 +47,41 @@ const CryptoTailStrategyList: React.FC = () => {
const [triggersLoading, setTriggersLoading] = useState(false)
const [form] = Form.useForm()
/** 币安 API 健康状态:仅保留「不可用」的项,用于强提醒 */
const [binanceUnhealthy, setBinanceUnhealthy] = useState<Array<{ name: string; message: string }>>([])
const [binanceCheckLoading, setBinanceCheckLoading] = useState(false)
const BINANCE_API_NAMES = ['币安 API', '币安 WebSocket']
const fetchBinanceApiStatus = async () => {
setBinanceCheckLoading(true)
try {
const res = await apiService.proxyConfig.checkApiHealth()
if (res.data.code === 0 && res.data.data?.apis) {
const unhealthy = res.data.data.apis.filter(
(api) => BINANCE_API_NAMES.includes(api.name) && api.status !== 'success'
)
setBinanceUnhealthy(unhealthy.map((api) => ({ name: api.name, message: api.message })))
} else {
setBinanceUnhealthy([])
}
} catch {
setBinanceUnhealthy([{ name: '币安 API', message: '' }, { name: '币安 WebSocket', message: '' }])
} finally {
setBinanceCheckLoading(false)
}
}
useEffect(() => {
fetchAccounts()
fetchSystemConfig()
fetchMarketOptions()
}, [])
useEffect(() => {
fetchBinanceApiStatus()
}, [])
useEffect(() => {
fetchList()
}, [filters])
@@ -411,6 +440,37 @@ const CryptoTailStrategyList: React.FC = () => {
return (
<div style={{ padding: isMobile ? 12 : 24 }}>
<h1 style={{ marginBottom: 16, fontSize: isMobile ? 20 : 24 }}>{t('cryptoTailStrategy.list.title')}</h1>
{binanceUnhealthy.length > 0 && (
<Alert
type="error"
showIcon
icon={<WarningOutlined />}
message={t('cryptoTailStrategy.binanceApiAlert.title')}
description={
<div>
<p style={{ marginBottom: 8 }}>{t('cryptoTailStrategy.binanceApiAlert.description')}</p>
<ul style={{ marginBottom: 8, paddingLeft: 20 }}>
{binanceUnhealthy.map((item, i) => (
<li key={i}>
<strong>{item.name}</strong>
{item.message ? `: ${item.message}` : ''}
</li>
))}
</ul>
<Button
type="primary"
danger
size="small"
loading={binanceCheckLoading}
onClick={fetchBinanceApiStatus}
>
{t('cryptoTailStrategy.binanceApiAlert.recheck')}
</Button>
</div>
}
style={{ marginBottom: 16 }}
/>
)}
<Alert
type="warning"
showIcon