From 96e937e523c792595ae0ee5e5c87da7cc88db907 Mon Sep 17 00:00:00 2001 From: Zichao Lin Date: Wed, 5 Aug 2026 10:21:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=88=E4=B8=8D=E8=AE=B0=E5=BD=95=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E4=BF=A1=E6=81=AF=EF=BC=89=20=20-=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=91=98=E8=A6=81=EF=BC=9A=E7=AB=AF=E5=8F=A3/?= =?UTF-8?q?=E9=97=B4=E9=9A=94/=E9=87=8D=E8=AF=95/=E8=B6=85=E6=97=B6/?= =?UTF-8?q?=E5=B9=B6=E5=8F=91/Telegram=20=E7=8A=B6=E6=80=81/=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E5=B9=B3=E5=8F=B0/=E8=B4=A6=E5=8F=B7=E6=95=B0/?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=B7=AF=E5=BE=84=20-=20=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E7=BB=93=E6=9E=9C=EF=BC=9A=E6=88=90=E5=8A=9F=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=E3=80=81=E8=AE=A4=E8=AF=81=E5=A4=B1=E8=B4=A5=E7=A6=81?= =?UTF-8?q?=E7=94=A8=E3=80=81=E5=A4=B1=E8=B4=A5=E5=8E=9F=E5=9B=A0=20-=20?= =?UTF-8?q?=E6=8F=90=E9=86=92=E7=8A=B6=E6=80=81=E6=9C=BA=EF=BC=9A=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E6=8F=90=E9=86=92/=E4=BD=99=E9=A2=9D=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=20-=20API=20=E6=93=8D=E4=BD=9C=EF=BC=9A=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E3=80=81=E5=B9=B3=E5=8F=B0/=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E5=A2=9E=E5=88=A0=E6=94=B9=E3=80=81=E8=AE=BE=E7=BD=AE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E3=80=81=E5=AF=86=E7=A0=81=E4=BF=AE=E6=94=B9=20-=20Te?= =?UTF-8?q?legram=20=E5=8F=91=E9=80=81=E6=88=90=E5=8A=9F=E3=80=81=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E8=80=97=E6=97=B6=EF=BC=88DEBUG=EF=BC=89=20-=20?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=97=A5=E5=BF=97=E4=B8=8D=E5=90=AB=20apikey?= =?UTF-8?q?/token/=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/alert.py | 2 ++ app/api.py | 10 ++++++++++ app/fetcher.py | 4 +++- app/monitor.py | 4 ++++ app/telegram.py | 1 + main.py | 16 +++++++++++++++- 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/alert.py b/app/alert.py index 83dfa89..0cb1895 100644 --- a/app/alert.py +++ b/app/alert.py @@ -52,8 +52,10 @@ def evaluate(account: dict, platform: dict, balance: float, cfg: Config) -> None below = balance < threshold if below and armed: kind = "below" + logger.info("触发提醒: %s / %s 余额 %s < 阈值 %s(%s)", platform["name"], account["name"], _fmt(balance), _fmt(threshold), platform["currency"]) elif not below and not armed: kind = "recovered" + logger.info("余额恢复: %s / %s 余额 %s >= 阈值 %s(%s)", platform["name"], account["name"], _fmt(balance), _fmt(threshold), platform["currency"]) else: return message = build_alert_message(kind, account, platform, balance) diff --git a/app/api.py b/app/api.py index 4a12bc0..4adedcb 100644 --- a/app/api.py +++ b/app/api.py @@ -118,9 +118,11 @@ def create_app(cfg: Config | None = None) -> FastAPI: def login(body: LoginRequest): import time if body.password != cfg.password: + logger.warning("登录失败:密码错误") raise HTTPException(status_code=401, detail="密码错误") token = secrets.token_urlsafe(32) _tokens[token] = time.time() + TOKEN_TTL + logger.info("登录成功") return {"token": token} @app.post("/api/logout") @@ -172,6 +174,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: if "UNIQUE" in str(exc): raise HTTPException(status_code=409, detail="平台名称已存在") raise HTTPException(status_code=400, detail=str(exc)) + logger.info("创建平台: %s (id=%s, provider=%s)", body.name, pid, body.provider_id) return {"id": pid} @app.put("/api/platforms/{pid}", dependencies=[Depends(require_auth)]) @@ -219,6 +222,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: raise HTTPException(status_code=400, detail=str(exc)) if cur.rowcount == 0: raise HTTPException(status_code=404, detail="平台不存在") + logger.info("更新平台 id=%s 字段: %s", pid, ", ".join(fields)) return {"id": pid} @app.delete("/api/platforms/{pid}", dependencies=[Depends(require_auth)]) @@ -227,6 +231,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: cur = conn.execute("DELETE FROM platforms WHERE id=?", (pid,)) if cur.rowcount == 0: raise HTTPException(status_code=404, detail="平台不存在") + logger.info("删除平台 id=%s", pid) return {"ok": True} # ---------- 账号 ---------- @@ -267,6 +272,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: aid = cur.lastrowid except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) + logger.info("创建账号: %s (id=%s, 平台id=%s)", body.name, aid, body.platform_id) return {"id": aid} @app.put("/api/accounts/{aid}", dependencies=[Depends(require_auth)]) @@ -300,6 +306,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: ) if cur.rowcount == 0: raise HTTPException(status_code=404, detail="账号不存在") + logger.info("更新账号 id=%s 字段: %s", aid, ", ".join(fields)) return {"id": aid} @app.delete("/api/accounts/{aid}", dependencies=[Depends(require_auth)]) @@ -308,6 +315,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: cur = conn.execute("DELETE FROM accounts WHERE id=?", (aid,)) if cur.rowcount == 0: raise HTTPException(status_code=404, detail="账号不存在") + logger.info("删除账号 id=%s", aid) return {"ok": True} @app.post("/api/accounts/{aid}/check", dependencies=[Depends(require_auth)]) @@ -392,6 +400,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: changed = True if changed: save_config(cfg) + logger.info("设置已更新: %s", ", ".join(f for f in ("global_interval_seconds", "retry_count", "timeout_seconds", "max_workers", "telegram_bot_token", "telegram_chat_id") if getattr(body, f) is not None)) return {"ok": True} @app.post("/api/settings/password", dependencies=[Depends(require_auth)]) @@ -400,6 +409,7 @@ def create_app(cfg: Config | None = None) -> FastAPI: raise HTTPException(status_code=401, detail="旧密码错误") cfg.password = body.new_password save_config(cfg) + logger.info("管理密码已修改") return {"ok": True} # ---------- 静态前端 ---------- diff --git a/app/fetcher.py b/app/fetcher.py index 6970df7..10f14bd 100644 --- a/app/fetcher.py +++ b/app/fetcher.py @@ -60,13 +60,15 @@ def fetch_balance( headers["Content-Type"] = "application/json" attempts = retry_count + 1 for attempt in range(attempts): + t0 = time.time() try: if method == "GET": resp = requests.get(url, headers=headers, timeout=timeout) else: resp = requests.post(url, headers=headers, data=body, timeout=timeout) + logger.debug("请求完成 %s -> %s(%.0fms)", url, resp.status_code, (time.time() - t0) * 1000) except requests.RequestException as exc: - logger.warning("请求异常(%s/%s) %s: %s", attempt + 1, attempts, url, exc) + logger.warning("请求异常(%s/%s) %s: %s(%.0fms)", attempt + 1, attempts, url, exc, (time.time() - t0) * 1000) if attempt < attempts - 1: time.sleep(2) continue diff --git a/app/monitor.py b/app/monitor.py index 4af0562..edff4e1 100644 --- a/app/monitor.py +++ b/app/monitor.py @@ -182,9 +182,13 @@ class Monitor: ) if result.auth_error: + logger.warning("认证失败已禁用: %s / %s(%s)", platform["name"], account["name"], result.error) notify_disabled(account, platform, cfg) elif result.ok: + logger.info("检查成功: %s / %s 余额 %.4f %s", platform["name"], account["name"], result.balance, platform["currency"]) evaluate(account, platform, result.balance, cfg) + else: + logger.warning("检查失败: %s / %s -> %s", platform["name"], account["name"], result.error) # 调度:无论成功失败,按间隔排下一次 with self._lock: diff --git a/app/telegram.py b/app/telegram.py index b2fd65a..d1bfd58 100644 --- a/app/telegram.py +++ b/app/telegram.py @@ -23,6 +23,7 @@ def send_message(bot_token: str, chat_id: str, text: str) -> bool: timeout=10, ) if resp.status_code == 200: + logger.info("Telegram 消息已发送(%d 字符)", len(text)) return True logger.error("Telegram 发送失败 status=%s body=%s", resp.status_code, resp.text[:200]) except requests.RequestException as exc: diff --git a/main.py b/main.py index dc417b3..ee6dbe8 100644 --- a/main.py +++ b/main.py @@ -6,19 +6,33 @@ import logging import uvicorn +from app import db from app.api import create_app from app.config import load_config +from app.providers import list_providers logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) +logger = logging.getLogger("monitor.main") def main() -> None: cfg = load_config() app = create_app(cfg) - print(f"AI Balance Monitor 已启动: http://127.0.0.1:{cfg.port}") + + tg = "已配置" if cfg.telegram_bot_token and cfg.telegram_chat_id else "未配置" + with db.get_conn() as conn: + total = conn.execute("SELECT COUNT(*) FROM accounts").fetchone()[0] + enabled = conn.execute("SELECT COUNT(*) FROM accounts WHERE enabled=1").fetchone()[0] + logger.info("========== AI Balance Monitor 启动 ==========") + logger.info("端口 %s | 全局间隔 %ss | 重试 %s | 超时 %ss | 并发 %s", cfg.port, cfg.global_interval_seconds, cfg.retry_count, cfg.timeout_seconds, cfg.max_workers) + logger.info("Telegram 提醒: %s", tg) + logger.info("内置平台: %s", ", ".join(p["name"] for p in list_providers())) + logger.info("账号: %s 个(启用 %s)| 数据库: %s", total, enabled, db.DB_PATH) + logger.info("面板地址: http://127.0.0.1:%s", cfg.port) + uvicorn.run(app, host="0.0.0.0", port=cfg.port, log_level="warning")