fix(config): v1.19.4 MAC客户端不再污染标准best_host(取不到数据根因)

根因:mac/client.py 的 MacClient.from_best_host 和 AsyncMacClient.from_best_host
调用 save_best_host(best),把选中的 MAC 协议服务器(如 121.36.248.138)
写进了全局 best_host 字段——但这个字段是标准 TDX 协议用的。之后标准
AsyncTdxClient 用 get_best_host 读到这个 MAC host,用标准协议请求 MAC
服务器,返回空 body(偏移 2 剩余 0)。web app lifespan 启动 MAC 客户端
时触发污染。

修复:
- config.py 新增独立的 best_mac_host 字段 + get_best_mac_host / save_best_mac_host
- mac/client.py 4 处改用新字段(不再碰 save_best_host / get_best_host)
- get_best_host 加交叉污染校验:缓存 host 不在标准列表里自动重置
  (自动修复已被污染的 config.json,用户无需手动操作)

测试:新增 5 个回归测试(污染检测 + MAC host 隔离),907 全过。
This commit is contained in:
Justin Gu
2026-07-07 20:36:44 +08:00
parent 032f5a40c0
commit 3bc15e620c
5 changed files with 144 additions and 9 deletions
+48 -2
View File
@@ -160,12 +160,29 @@ def _save(data: dict[str, Any]) -> None:
def get_best_host() -> str:
"""返回当前最佳主机地址。优先级:环境变量 > config.json > 默认列表首个。"""
"""返回当前最佳主机地址。优先级:环境变量 > config.json > 默认列表首个。
含交叉污染校验:历史上 ``MacClient.from_best_host`` 曾误调 ``save_best_host``
把 MAC 服务器写入 ``best_host``v1.19.3 "取不到行情" bug)。这里检测到
缓存的 host 不在标准 host 候选列表里时,自动重置为默认首个,避免用错协议。
"""
env = os.environ.get("EASY_TDX_HOST")
if env:
return env
cfg = _load()
return cast(str, cfg.get("best_host", _FALLBACK_HOSTS[0]))
cached = cast(str, cfg.get("best_host", _FALLBACK_HOSTS[0]))
# 交叉污染校验:cached 必须在标准 host 候选列表(known_hosts + 源码默认)里。
# 如果是 MAC host121.36.x.x 等),不在标准列表里 → 重置。
all_std_hosts = set(cfg.get("known_hosts", [])) | set(_FALLBACK_HOSTS)
if cached not in all_std_hosts:
# 被污染了(MAC/ex host 混入),重置为默认首个并持久化
cfg["best_host"] = _FALLBACK_HOSTS[0]
cfg["best_host_updated_at"] = datetime.now(_SHANGHAI_TZ).isoformat()
_save(cfg)
return _FALLBACK_HOSTS[0]
return cached
def get_known_hosts() -> list[str]:
@@ -189,6 +206,20 @@ def get_mac_hosts() -> list[str]:
return cast(list[str], cfg.get("mac_hosts", list(_FALLBACK_MAC_HOSTS)))
def get_best_mac_host() -> str:
"""返回当前最佳 MAC 协议主机。
与 ``get_best_host``(标准 TDX 协议)分开存:MAC 和标准协议用不同的
服务器列表和协议格式,共用同一个字段会导致 MAC 客户端选出的 host
被标准客户端误用,用标准协议请求 MAC 服务器返回空 body。
"""
env = os.environ.get("EASY_TDX_MAC_HOST")
if env:
return env
cfg = _load()
return cast(str, cfg.get("best_mac_host", _FALLBACK_MAC_HOSTS[0]))
def get_ex_hosts() -> list[str]:
"""返回扩展行情服务器列表。"""
cfg = _load()
@@ -282,3 +313,18 @@ def save_best_mac_ex_host(host: str) -> None:
if "mac_ex_hosts" not in cfg:
cfg["mac_ex_hosts"] = list(_FALLBACK_MAC_EX_HOSTS)
_save(cfg)
def save_best_mac_host(host: str) -> None:
"""保存最佳 MAC 协议主机到配置文件(独立于标准 TDX 的 best_host)。
MAC 客户端必须用这个,不能复用 ``save_best_host``——否则选出的 MAC
服务器会污染标准 TDX 协议的 ``best_host``,导致标准客户端用错协议请求
MAC 服务器,返回空 bodyv1.19.3 "取不到行情" bug 的根因)。
"""
cfg = _load()
cfg["best_mac_host"] = host
cfg["best_mac_host_updated_at"] = datetime.now(_SHANGHAI_TZ).isoformat()
if "mac_hosts" not in cfg:
cfg["mac_hosts"] = list(_FALLBACK_MAC_HOSTS)
_save(cfg)
+11 -5
View File
@@ -16,7 +16,13 @@ from .._df import _apply_bar_time_align_df, _period_to_minutes, _to_df
from .._reconnect import _RETRY_DELAYS, AsyncHeartbeatMixin
from ..codec.bitmap import Fields, PresetField
from ..commands.base import BaseCommand
from ..config import get_best_host, get_mac_hosts, get_port, get_timeout, save_best_host
from ..config import (
get_best_mac_host,
get_mac_hosts,
get_port,
get_timeout,
save_best_mac_host,
)
from ..exceptions import TdxConnectionError
from ..transport.async_ import AsyncTdxConnection
from ..transport.sync import TdxConnection, ping_mac_all
@@ -147,7 +153,7 @@ class MacClient:
auto_reconnect: bool = True,
heartbeat_interval: float = 15.0,
) -> None:
self._host = host if host is not None else get_best_host()
self._host = host if host is not None else get_best_mac_host()
self._port = port if port is not None else get_port()
self._timeout = timeout if timeout is not None else get_timeout()
self._auto_reconnect = auto_reconnect
@@ -180,7 +186,7 @@ class MacClient:
timeout = get_timeout()
ranked = ping_mac_all(hosts, port, ping_timeout)
best = ranked[0][0] if ranked else hosts[0]
save_best_host(best)
save_best_mac_host(best)
return cls(best, port, timeout, auto_reconnect, heartbeat_interval)
@staticmethod
@@ -1156,7 +1162,7 @@ class AsyncMacClient(AsyncHeartbeatMixin):
auto_reconnect: bool = True,
heartbeat_interval: float = 15.0,
) -> None:
self._host = host if host is not None else get_best_host()
self._host = host if host is not None else get_best_mac_host()
self._port = port if port is not None else get_port()
self._timeout = timeout if timeout is not None else get_timeout()
self._auto_reconnect = auto_reconnect
@@ -1191,7 +1197,7 @@ class AsyncMacClient(AsyncHeartbeatMixin):
timeout = get_timeout()
ranked = ping_mac_all(hosts, port, ping_timeout)
best = ranked[0][0] if ranked else hosts[0]
save_best_host(best)
save_best_mac_host(best)
return cls(best, port, timeout, auto_reconnect, heartbeat_interval)
@staticmethod