""" 批次作業排程器 (5.4) 使用 schedule 套件管理所有批次排程 排程清單: - 每日 00:00 - auto_terminate_employees (未來實作) - 每日 02:00 - daily_quota_check - 每日 03:00 - sync_keycloak_users - 每月 1 日 01:00 - archive_audit_logs 啟動方式: python -m app.batch.scheduler """ import logging import signal import sys import time from datetime import datetime logger = logging.getLogger(__name__) def _setup_logging(): logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) def _run_daily_quota_check(): logger.info("觸發: 每日配額檢查批次") try: from app.batch.daily_quota_check import run_daily_quota_check result = run_daily_quota_check() logger.info(f"每日配額檢查批次完成: {result.get('status')}") except Exception as e: logger.error(f"每日配額檢查批次異常: {e}") def _run_sync_keycloak_users(): logger.info("觸發: Keycloak 同步批次") try: from app.batch.sync_keycloak_users import run_sync_keycloak_users result = run_sync_keycloak_users() logger.info(f"Keycloak 同步批次完成: {result.get('status')}") except Exception as e: logger.error(f"Keycloak 同步批次異常: {e}") def _run_archive_audit_logs(): """只在每月 1 日執行""" if datetime.now().day != 1: return logger.info("觸發: 審計日誌歸檔批次 (每月 1 日)") try: from app.batch.archive_audit_logs import run_archive_audit_logs result = run_archive_audit_logs() logger.info(f"審計日誌歸檔批次完成: {result.get('status')}") except Exception as e: logger.error(f"審計日誌歸檔批次異常: {e}") def start_scheduler(): """啟動排程器""" try: import schedule except ImportError: logger.error("缺少 schedule 套件,請執行: pip install schedule") sys.exit(1) logger.info("=== HR Portal 批次排程器啟動 ===") # 每日 02:00 - 配額檢查 schedule.every().day.at("02:00").do(_run_daily_quota_check) # 每日 03:00 - Keycloak 同步 schedule.every().day.at("03:00").do(_run_sync_keycloak_users) # 每日 01:00 - 審計日誌歸檔 (函式內部判斷是否為每月 1 日) schedule.every().day.at("01:00").do(_run_archive_audit_logs) logger.info("排程設定完成:") logger.info(" 02:00 - 每日配額檢查") logger.info(" 03:00 - Keycloak 同步") logger.info(" 01:00 - 審計日誌歸檔 (每月 1 日)") # 處理 SIGTERM (Docker 停止信號) def handle_sigterm(signum, frame): logger.info("收到停止信號,排程器正在關閉...") sys.exit(0) signal.signal(signal.SIGTERM, handle_sigterm) logger.info("排程器運行中,等待任務觸發...") while True: schedule.run_pending() time.sleep(60) # 每分鐘檢查一次 if __name__ == "__main__": _setup_logging() start_scheduler()