From 493621c4407c1ef45ee5dc986030de86806c367d Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 16 Jun 2026 15:05:21 +0800 Subject: [PATCH] feat: add Uptime Kuma heartbeat monitoring - Add heartbeat configuration to config.yaml - Implement HeartbeatMonitor class in watch.py - Send heartbeat every 40 seconds to Uptime Kuma push endpoint - Update config.yaml.example with heartbeat settings - Add heartbeat logging at INFO level Co-Authored-By: Claude --- .gitignore | 3 ++ config.yaml.example | 22 +++++++++ watch.py | 112 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index dd386de..d958f90 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ config.yaml # Excel temp files Excel/ *.tmp + +.claude/ +.agents/ \ No newline at end of file diff --git a/config.yaml.example b/config.yaml.example index f982bb7..4fab4b6 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -1,5 +1,27 @@ # Excel to SQL Server Migration Configuration +# ================= 定时检查参数 ================= +check_interval_minutes: 5 # 每隔多少分钟扫描一次源文件 + +# ================= 日志配置 ================= +logging: + level: "INFO" # DEBUG / INFO / WARNING + log_dir: "logs" # 日志目录(相对于项目根目录) + max_keep_days: 7 # 日志文件保留天数 + +# ================= Uptime Kuma 心跳检测 ================= +heartbeat: + enabled: false # 是否启用心跳检测 + interval_seconds: 40 # 心跳发送间隔(秒) + url: "YOUR_UPTIME_KUMA_PUSH_URL" # Uptime Kuma 推送端点 URL + +# ================= LAN 源文件路径 ================= +# key = 本地相对路径 (对应 tables.sources[].file) +# value = 局域网 UNC 绝对路径 +lan_sources: + "PG/生产执行卡2022.xlsm": "\\\\192.168.110.113\\生产执行卡\\往年生产执行卡\\生产执行卡2022.xlsm" + # ... 其他 LAN 源文件路径 + # ================= SQL Server 连接 ================= sql_server: driver: "ODBC Driver 18 for SQL Server" diff --git a/watch.py b/watch.py index 0fd2e5a..fba14ed 100644 --- a/watch.py +++ b/watch.py @@ -6,6 +6,9 @@ import argparse import logging import os import time +import threading +import urllib.request +import urllib.error from logger import setup_logger from migrate import load_config, get_connection, create_schema, migrate_table @@ -14,6 +17,77 @@ from sync import sync_all_files, determine_tables_to_migrate logger = logging.getLogger('app') +class HeartbeatMonitor: + """Uptime Kuma heartbeat monitor running in a separate thread.""" + + def __init__(self, url, interval_seconds): + self.url = url + self.interval_seconds = interval_seconds + self.running = False + self.thread = None + logger.info(f"心跳监控初始化: 间隔 {interval_seconds} 秒") + + def _send_heartbeat(self): + """Send a heartbeat request to Uptime Kuma.""" + try: + # Add current timestamp to ping parameter + ping = str(int(time.time())) + full_url = f"{self.url}{ping}" + + request = urllib.request.Request(full_url, method='GET') + request.add_header('User-Agent', 'Excel-to-SQL-Server-WatchDaemon/1.0') + + with urllib.request.urlopen(request, timeout=10) as response: + if response.status == 200: + logger.info(f"心跳发送成功") + else: + logger.warning(f"心跳返回异常状态码: {response.status}") + except urllib.error.URLError as e: + logger.error(f"心跳发送失败 (网络错误): {e}") + except Exception as e: + logger.error(f"心跳发送失败 (未知错误): {e}") + + def _run_loop(self): + """Main heartbeat loop running in the thread.""" + logger.info("心跳监控线程已启动") + + while self.running: + self._send_heartbeat() + + # Wait for the interval or until stopped + for _ in range(self.interval_seconds): + if not self.running: + break + time.sleep(1) + + logger.info("心跳监控线程已停止") + + def start(self): + """Start the heartbeat monitor thread.""" + if self.running: + logger.warning("心跳监控已在运行") + return + + self.running = True + self.thread = threading.Thread(target=self._run_loop, daemon=True) + self.thread.start() + logger.info("心跳监控已启动") + + def stop(self): + """Stop the heartbeat monitor thread.""" + if not self.running: + return + + logger.info("正在停止心跳监控...") + self.running = False + + # Wait for thread to finish + if self.thread and self.thread.is_alive(): + self.thread.join(timeout=5) + + logger.info("心跳监控已停止") + + def run_once(cfg): """Execute one sync + migrate cycle.""" excel_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Excel') @@ -100,18 +174,36 @@ def main(): logger.info(f" 同步间隔: {interval} 分钟") logger.info("=" * 50) - while True: - try: - run_once(cfg) - except Exception as e: - logger.error(f"运行周期异常: {e}", exc_info=True) + # Initialize heartbeat monitor if configured + heartbeat_monitor = None + heartbeat_cfg = cfg.get('heartbeat', {}) + if heartbeat_cfg.get('enabled', False): + url = heartbeat_cfg.get('url', '').strip() + interval_seconds = heartbeat_cfg.get('interval_seconds', 40) - if args.once: - logger.info("--once 模式,退出") - break + if url: + heartbeat_monitor = HeartbeatMonitor(url, interval_seconds) + heartbeat_monitor.start() + else: + logger.warning("心跳已启用但 URL 未配置,跳过心跳监控") - logger.info(f"下次检查: {interval} 分钟后") - time.sleep(interval * 60) + try: + while True: + try: + run_once(cfg) + except Exception as e: + logger.error(f"运行周期异常: {e}", exc_info=True) + + if args.once: + logger.info("--once 模式,退出") + break + + logger.info(f"下次检查: {interval} 分钟后") + time.sleep(interval * 60) + finally: + # Stop heartbeat monitor on exit + if heartbeat_monitor: + heartbeat_monitor.stop() if __name__ == '__main__':