cleanup_selection_logs.py 4.5 KB

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