diff --git a/.env.example b/.env.example index f477036..5867239 100644 --- a/.env.example +++ b/.env.example @@ -59,3 +59,5 @@ OPTION_QTY_ETH=2 # 企业微信群机器人(系统设置页可改) WECOM_ENABLED=0 WECOM_WEBHOOK_URL= +# 推送标题前缀,多机时区分,如 云A / 云B +WECOM_MACHINE_NAME= diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index ba1603d..312e80b 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -558,19 +558,20 @@ async def put_runtime_settings( class NotifySettingsBody(BaseModel): enabled: bool | None = None webhook_url: str | None = None + machine_name: str | None = Field(default=None, max_length=64) def _notify_payload() -> dict: from ..notify import wecom from ..env_store import mask_secret - s = get_settings() url = wecom.wecom_webhook_url() return { "enabled": wecom.wecom_enabled(), "webhook_configured": bool(url), "webhook_url_masked": mask_secret(url) if url else None, "venue_label": wecom.venue_label(), + "machine_name": wecom.wecom_machine_name() or "", } @@ -591,8 +592,13 @@ async def put_notify_settings( if body.webhook_url is not None and body.webhook_url.strip(): updates["WECOM_WEBHOOK_URL"] = body.webhook_url.strip() get_db().set_setting("wecom_webhook_url", body.webhook_url.strip()) + if body.machine_name is not None: + name = body.machine_name.strip()[:64] + updates["WECOM_MACHINE_NAME"] = name + get_db().set_setting("wecom_machine_name", name) if updates: upsert_env_keys(updates) + get_settings.cache_clear() return _notify_payload() diff --git a/backend/app/config.py b/backend/app/config.py index 9be1440..04089e6 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -85,6 +85,7 @@ class Settings(BaseSettings): # 企业微信群机器人 wecom_enabled: bool = False wecom_webhook_url: str = "" + wecom_machine_name: str = "" @property def is_sim(self) -> bool: diff --git a/backend/app/notify/wecom.py b/backend/app/notify/wecom.py index daf6c8c..bb515b5 100644 --- a/backend/app/notify/wecom.py +++ b/backend/app/notify/wecom.py @@ -55,6 +55,18 @@ def wecom_webhook_url() -> str: return "" +def wecom_machine_name() -> str: + """多机推送区分用的机器名(设置页 / WECOM_MACHINE_NAME)。""" + s = get_settings() + name = (getattr(s, "wecom_machine_name", None) or "").strip() + if name: + return name[:64] + try: + return (get_db().get_setting("wecom_machine_name", "") or "").strip()[:64] + except Exception: + return "" + + def venue_label() -> str | None: """实盘时返回「实盘·交易所」;SIM 不展示盘口标签。""" s = get_settings() @@ -74,13 +86,21 @@ def venue_label() -> str | None: def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str: body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or [])) + machine = wecom_machine_name() venue = venue_label() - head = f"## 【{venue}】{title}" if venue else f"## {title}" + prefix_parts: list[str] = [] + if machine: + prefix_parts.append(f"【{machine}】") + if venue: + prefix_parts.append(f"【{venue}】") + head = f"## {''.join(prefix_parts)}{title}" parts = [ head, f"> **标识**: `{tag}`", f"> **时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}", ] + if machine: + parts.append(f"> **机器**: {machine}") if body: parts.append("") parts.append(body) @@ -216,7 +236,10 @@ def notify_fault(*, title: str, detail: str, dedupe_key: str | None = None) -> N def notify_test() -> tuple[bool, str]: venue = venue_label() + machine = wecom_machine_name() lines = ["企业微信通知已连通。"] + if machine: + lines.append(f"机器名称: **{machine}**") if venue: lines.append(f"当前盘口标签: **{venue}**") content = build_markdown( diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index a2b8383..9a21eb8 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -359,6 +359,7 @@ export type NotifySettings = { webhook_configured: boolean; webhook_url_masked: string | null; venue_label: string; + machine_name?: string; }; export async function downloadBackup(name: string): Promise { diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index cde2def..14b82e9 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -117,6 +117,7 @@ export default function SettingsPage() { const [runtimeOk, setRuntimeOk] = useState(""); const [wecomEnabled, setWecomEnabled] = useState(false); const [wecomWebhook, setWecomWebhook] = useState(""); + const [wecomMachineName, setWecomMachineName] = useState(""); const [wecomMeta, setWecomMeta] = useState(null); const [wecomOk, setWecomOk] = useState(""); @@ -153,6 +154,7 @@ export default function SettingsPage() { .then((n) => { setWecomMeta(n); setWecomEnabled(n.enabled === true); + setWecomMachineName(n.machine_name || ""); }) .catch(() => undefined); } @@ -452,7 +454,10 @@ export default function SettingsPage() { setErr(""); setWecomOk(""); try { - const body: Record = { enabled: wecomEnabled }; + const body: Record = { + enabled: wecomEnabled, + machine_name: wecomMachineName.trim(), + }; if (wecomWebhook.trim()) body.webhook_url = wecomWebhook.trim(); const n = await apiFetch("/api/settings/notify", { method: "PUT", @@ -460,6 +465,7 @@ export default function SettingsPage() { }); setWecomMeta(n); setWecomEnabled(n.enabled === true); + setWecomMachineName(n.machine_name || ""); setWecomWebhook(""); setWecomOk( n.enabled @@ -483,6 +489,7 @@ export default function SettingsPage() { ); setWecomMeta(n); setWecomOk(n.detail || "测试消息已发送,请查看企业微信群"); + if (n.machine_name != null) setWecomMachineName(n.machine_name || ""); } catch (ex) { setErr(ex instanceof Error ? ex.message : String(ex)); } @@ -1379,6 +1386,18 @@ export default function SettingsPage() {

企业微信通知

+
+ + setWecomMachineName(e.target.value)} + /> +