cleanup_api_keys.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 清理 api_keys 表中的过期数据
  5. - 每天执行一次
  6. - 删除两天前的所有数据(基于 created_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 对象"""
  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
  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_keys 数据
  44. 返回: 删除的记录数量
  45. """
  46. now_dt = beijing_now()
  47. now_str = now_dt.strftime("%Y-%m-%d %H:%M:%S")
  48. # 计算两天前的时间戳
  49. two_days_ago = now_dt - timedelta(days=2)
  50. two_days_ago_str = two_days_ago.strftime("%Y-%m-%d %H:%M:%S")
  51. conn = get_db_connection(config)
  52. try:
  53. with conn.cursor() as cursor:
  54. # 先查询要删除的数据数量
  55. cursor.execute(
  56. "SELECT COUNT(*) as delete_count FROM api_keys WHERE created_at < %s",
  57. (two_days_ago_str,)
  58. )
  59. delete_count = cursor.fetchone()["delete_count"]
  60. if delete_count == 0:
  61. print(f"[{now_str}] 无两天前数据需要清理")
  62. return 0
  63. # 删除两天前的数据
  64. cursor.execute(
  65. "DELETE FROM api_keys WHERE created_at < %s",
  66. (two_days_ago_str,)
  67. )
  68. deleted_count = cursor.rowcount
  69. conn.commit()
  70. # 查询剩余记录数
  71. cursor.execute("SELECT COUNT(*) as remaining_count FROM api_keys")
  72. remaining_count = cursor.fetchone()["remaining_count"]
  73. print(f"[{now_str}] 清理完成: 删除 {deleted_count} 条两天前数据 (cutoff: {two_days_ago_str}), 剩余 {remaining_count} 条")
  74. return deleted_count
  75. finally:
  76. conn.close()
  77. def main():
  78. config = load_config()
  79. interval_hours = config.getint("script", "interval_hours", fallback=24)
  80. print(f"=" * 50)
  81. print(f"api_keys 清理脚本已启动")
  82. print(f"清理周期: 每 {interval_hours} 小时执行一次")
  83. print(f"清理条件: 删除两天前的所有数据")
  84. print(f"启动时间: {beijing_now().strftime('%Y-%m-%d %H:%M:%S')}")
  85. print(f"=" * 50)
  86. # 启动时先执行一次清理
  87. try:
  88. run_once(config)
  89. except Exception as e:
  90. print(f"[{beijing_now()}] 启动时清理失败: {e}", file=sys.stderr)
  91. print(f"\n开始定时清理,每 {interval_hours} 小时执行一次...\n")
  92. while True:
  93. time.sleep(interval_hours * 3600)
  94. try:
  95. run_once(config)
  96. except Exception as e:
  97. print(f"[{beijing_now()}] 清理异常: {e}", file=sys.stderr)
  98. if __name__ == "__main__":
  99. main()