# -*- coding: utf-8 -*- # Author : Charley # Python : 3.12.10 # Date : 2026/09/09 """得卡运维常驻:账号池 cooling 巡检恢复 + 定时清理堆积的报告 Excel。 两个后台定时任务(单进程 schedule 循环): 1) 账号池 cooling 巡检:每 10 分钟把冷却(cooling)到期的账号恢复 healthy(补账号池状态机闭环, 否则软失败转 cooling 的号永不自动恢复,见审查问题4)。 2) 报告 Excel 清理:每天 03:00 删除 EXCEL_DIRS 下超过 EXCEL_KEEP_DAYS 天的 .xlsx(报告每天生成、 堆积占盘),排除 EXCEL_KEEP_FILES 里的固定产物。 ⚠️ 待主公确认(2026/09/09):EXCEL_KEEP_DAYS 保留天数、EXCEL_DIRS 清理目录、EXCEL_KEEP_FILES 白名单。 """ import os import sys import time import glob _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(_ROOT, "common")) os.chdir(_ROOT) import schedule from loguru import logger from mysql_pool import MySQLConnectionPool from account_pool import AccountPool logger.remove() logger.add(sys.stderr, format="[{time:HH:mm:ss}] {level} {message}", level="INFO") logger.add("./logs/maintenance_{time:YYYYMMDD}.log", encoding="utf-8", rotation="00:00", format="[{time:YYYY-MM-DD HH:mm:ss}] {level} {message}", level="INFO", retention="7 day") # ---- 配置(待主公确认)---- EXCEL_KEEP_DAYS = 30 # Excel 保留天数:删除早于此的报告 xlsx EXCEL_DIRS = [ # 要清理的报告输出目录 os.path.join(_ROOT, "output", "onsale"), # 在售报告 os.path.join(_ROOT, "reports", "sold_report"), # 已售日/周/月报等 ] EXCEL_KEEP_FILES = {"球队.xlsx"} # 固定产物白名单:不按天清理(非报告,勿删) COOLING_REVIVE_MINUTES = 10 # cooling 巡检间隔(分钟) KEEPALIVE_SCAN_HOURS = 1 # token 保活巡检间隔(小时) KEEPALIVE_IDLE_HOURS = 6 # 闲置超过该小时数的 healthy 号才主动刷新(远小于 refresh_token 过期时长) EXCEL_CLEAN_AT = "03:00" # 每天几点清理 Excel def keepalive_pool(log) -> None: """巡检:对闲置超过 KEEPALIVE_IDLE_HOURS 的 healthy 号主动刷新 token,避免 refresh_token 闲到过期被判死。 Args: log: 日志对象。 """ try: pool = MySQLConnectionPool(log=log) AccountPool(pool, log=log, task_tag="maintenance").keepalive_idle(KEEPALIVE_IDLE_HOURS, log=log) except Exception as e: log.warning(f"[保活] token 保活异常: {e}") def revive_pool(log) -> None: """巡检:把账号池里 cooling 冷却到期的账号恢复 healthy。 Args: log: 日志对象。 """ try: pool = MySQLConnectionPool(log=log) AccountPool(pool, log=log, task_tag="maintenance").revive_cooling() log.info("[巡检] cooling 冷却到期账号已恢复检查完成") except Exception as e: log.warning(f"[巡检] cooling 恢复异常: {e}") def clean_old_excel(log) -> None: """清理各报告目录下超过 EXCEL_KEEP_DAYS 天的 .xlsx(排除白名单固定产物)。 Args: log: 日志对象。 """ cutoff = time.time() - EXCEL_KEEP_DAYS * 86400 removed = 0 for d in EXCEL_DIRS: if not os.path.isdir(d): continue for f in glob.glob(os.path.join(d, "*.xlsx")): if os.path.basename(f) in EXCEL_KEEP_FILES: # 固定产物不清 continue try: if os.path.getmtime(f) < cutoff: os.remove(f) removed += 1 log.info(f"[清理] 删除旧报告 {f}") except Exception as e: log.warning(f"[清理] 删除失败 {f}: {e}") log.info(f"[清理] Excel 清理完成,删除 {removed} 个(保留最近 {EXCEL_KEEP_DAYS} 天)") def main() -> None: """注册两个维护定时任务并进入单循环调度。""" schedule.every(COOLING_REVIVE_MINUTES).minutes.do(revive_pool, logger) # cooling 巡检 schedule.every(KEEPALIVE_SCAN_HOURS).hours.do(keepalive_pool, logger) # token 保活巡检 schedule.every().day.at(EXCEL_CLEAN_AT).do(clean_old_excel, logger) # Excel 清理 logger.info(f"运维守护启动:cooling 巡检每 {COOLING_REVIVE_MINUTES} 分钟 / " f"token 保活每 {KEEPALIVE_SCAN_HOURS} 小时(闲置>{KEEPALIVE_IDLE_HOURS}h 刷新) / " f"Excel 清理每天 {EXCEL_CLEAN_AT}(保留 {EXCEL_KEEP_DAYS} 天)") revive_pool(logger) # 启动即巡检一次 keepalive_pool(logger) # 启动即保活一次 while True: schedule.run_pending() time.sleep(1) if __name__ == "__main__": main()