feat: 心跳修复 + 接口测试代理命令(GET_APIS/HTTP_PROXY)

- ws_client: start() 连接改为独立线程,修复心跳线程被阻塞导致 B 端'暂无心跳数据'
- dispatcher: 新增 GET_APIS(返回主程序接口清单) + HTTP_PROXY(代理调用前置机接口)
- main_app_client: 新增 get_apis/http_proxy,自动按 path 加鉴权(/order→Basic, /api/admin→X-Admin-Token)
- config: 新增 order_basic 配置项
- process_guard: 看门狗支持 nssm restart 拉起服务(优先于 start.cmd)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-02 13:50:01 +08:00
co-authored by Claude
parent f07bbbc49d
commit b572697e17
4 changed files with 93 additions and 5 deletions
+62
View File
@@ -77,6 +77,68 @@ class MainAppClient:
)
return body is not None
def get_apis(self) -> str | None:
"""获取主程序 /order 接口清单(原始 JSON 文本)。"""
return self._get_text(self._url("/api/admin/apis"))
def http_proxy(self, method: str, path: str, query: dict | None = None,
body: str | None = None, extra_headers: dict | None = None) -> dict:
"""
代理调用前置机任意接口(本机环回,穿透白名单)。
自动按 path 加鉴权头:
- /order/** → Authorization: Basic(读 config.order_basic
- /api/admin/** → X-Admin-Token(读 config.main_admin_token
:return {"ok":bool, "statusCode":int, "body":str, "headers":dict, "error":str}
"""
base = self.config.main_health_url.split("/api/")[0]
url = base + path
# 拼 query
if query:
qs = "&".join(f"{quote(k)}={quote(str(v))}" for k, v in query.items() if v is not None)
if qs:
url += ("&" if "?" in url else "?") + qs
headers = {}
# 按路径自动加鉴权
if path.startswith("/order"):
if self.config.order_basic:
b64 = __import__("base64").b64encode(
self.config.order_basic.encode("utf-8")
).decode("ascii")
headers["Authorization"] = f"Basic {b64}"
elif path.startswith("/api/admin"):
if self.config.main_admin_token:
headers["X-Admin-Token"] = self.config.main_admin_token
# 合并外部传入的 headers(覆盖默认)
if extra_headers:
headers.update(extra_headers)
try:
method_upper = method.upper()
if method_upper == "GET":
resp = self._session.get(url, headers=headers, timeout=(5, 60))
elif method_upper == "POST":
ct = headers.get("Content-Type", "application/json")
headers.setdefault("Content-Type", ct)
resp = self._session.post(url, headers=headers, data=body.encode("utf-8") if body else None, timeout=(5, 60))
elif method_upper in ("PUT", "DELETE"):
resp = self._session.request(method_upper, url, headers=headers,
data=body.encode("utf-8") if body else None, timeout=(5, 60))
else:
return {"ok": False, "error": f"不支持的 method{method}"}
return {
"ok": True,
"statusCode": resp.status_code,
"body": resp.text,
"headers": dict(resp.headers),
}
except Exception as e:
log.warning("HTTP_PROXY %s %s 失败:%s", method, path, e)
return {"ok": False, "error": str(e)}
# ================= 基础 HTTP =================
def _url(self, path: str) -> str: