cleanup_nonce.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 清理 api_nonces 表中的过期数据
  5. - 每 scan_interval_seconds 秒执行一次
  6. - 删除 expires_at < 当前时间 的过期记录
  7. - 记录清理结果到日志
  8. """
  9. import os
  10. import sys
  11. import time
  12. import configparser
  13. from datetime import datetime, timezone, timedelta
  14. from pathlib import Path
  15. import pymysql
  16. # 北京时间 UTC+8,与 backend-client 一致
  17. BEIJING_TZ = timezone(timedelta(hours=8))
  18. def beijing_now() -> datetime:
  19. """当前北京时间(naive)"""
  20. return datetime.now(BEIJING_TZ).replace(tzinfo=None)
  21. def load_config():
  22. """从 config.ini 加载配置,返回 (config, config 文件路径)"""
  23. conf_path = Path(__file__).resolve().parent / "config.ini"
  24. if not conf_path.exists():
  25. print(f"错误: 未找到配置文件 {conf_path},请复制 config.ini.example 为 config.ini 并填写", file=sys.stderr)
  26. sys.exit(1)
  27. config = configparser.ConfigParser()
  28. config.read(conf_path, encoding="utf-8")
  29. return config, conf_path
  30. def get_db_connection(config):
  31. """创建数据库连接"""
  32. return pymysql.connect(
  33. host=config.get("database", "host"),
  34. port=config.getint("database", "port"),
  35. user=config.get("database", "user"),
  36. password=config.get("database", "password"),
  37. database=config.get("database", "database"),
  38. charset=config.get("database", "charset"),
  39. cursorclass=pymysql.cursors.DictCursor,
  40. )
  41. def run_once(config) -> int:
  42. """
  43. 执行一次清理:删除过期的 api_nonces 记录
  44. 返回: 删除的记录数量
  45. """
  46. now_dt = beijing_now()
  47. now_str = now_dt.strftime("%Y-%m-%d %H:%M:%S")
  48. conn = get_db_connection(config)
  49. try:
  50. with conn.cursor() as cursor:
  51. # 先查询过期数据数量
  52. cursor.execute(
  53. "SELECT COUNT(*) as expired_count FROM api_nonces WHERE expires_at < %s",
  54. (now_str,)
  55. )
  56. expired_count = cursor.fetchone()["expired_count"]
  57. if expired_count == 0:
  58. print(f"[{now_str}] 无过期数据需要清理")
  59. return 0
  60. # 删除过期数据
  61. cursor.execute(
  62. "DELETE FROM api_nonces WHERE expires_at < %s",
  63. (now_str,)
  64. )
  65. deleted_count = cursor.rowcount
  66. conn.commit()
  67. # 查询剩余记录数
  68. cursor.execute("SELECT COUNT(*) as remaining_count FROM api_nonces")
  69. remaining_count = cursor.fetchone()["remaining_count"]
  70. print(f"[{now_str}] 清理完成: 删除 {deleted_count} 条过期数据, 剩余 {remaining_count} 条")
  71. return deleted_count
  72. finally:
  73. conn.close()
  74. def main():
  75. config, _ = load_config()
  76. interval = config.getint("script", "scan_interval_seconds", fallback=600)
  77. print(f"=" * 50)
  78. print(f"api_nonces 清理脚本已启动")
  79. print(f"扫描间隔: {interval} 秒 ({interval // 60} 分钟)")
  80. print(f"启动时间: {beijing_now().strftime('%Y-%m-%d %H:%M:%S')}")
  81. print(f"=" * 50)
  82. # 启动时先执行一次清理
  83. try:
  84. run_once(config)
  85. except Exception as e:
  86. print(f"[{beijing_now()}] 启动时清理失败: {e}", file=sys.stderr)
  87. print(f"\n开始定时清理,每 {interval} 秒执行一次...\n")
  88. while True:
  89. time.sleep(interval)
  90. try:
  91. run_once(config)
  92. except Exception as e:
  93. print(f"[{beijing_now()}] 清理异常: {e}", file=sys.stderr)
  94. if __name__ == "__main__":
  95. main()