account_manager.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import threading
  2. import time
  3. import json
  4. import os
  5. import random
  6. from typing import Optional, Dict, Any, List
  7. from vs_log_macros import VSC_DEBUG, VSC_WARN, VSC_INFO, VSC_ERROR # type: ignore
  8. class AccountManager:
  9. """
  10. 账户管理器 (仅本地配置文件模式)
  11. 读取 config/accounts.json
  12. """
  13. _instance = None
  14. _lock = threading.RLock()
  15. def __new__(cls):
  16. with cls._lock:
  17. if cls._instance is None:
  18. cls._instance = super().__new__(cls)
  19. cls._instance._init_data()
  20. return cls._instance
  21. @staticmethod
  22. def Instance():
  23. return AccountManager()
  24. def _init_data(self):
  25. self._accounts: Dict[str, List[Dict]] = {} # pool_name -> [account_dict]
  26. self._account_lock = threading.RLock()
  27. self._config_path = "config/accounts.json"
  28. # 初始化时加载
  29. self.reload_config()
  30. def reload_config(self):
  31. """(重新)加载本地配置文件"""
  32. if not os.path.exists(self._config_path):
  33. VSC_WARN("acc_mgr", f"Config file not found: {self._config_path}. Account pools are empty.")
  34. return
  35. try:
  36. with open(self._config_path, 'r', encoding='utf-8') as f:
  37. data = json.load(f)
  38. count = 0
  39. with self._account_lock:
  40. self._accounts.clear()
  41. for pool_name, acc_list in data.items():
  42. processed_list = []
  43. for acc in acc_list:
  44. # 确保必要字段存在,并初始化状态
  45. if "id" not in acc or "username" not in acc:
  46. continue
  47. acc.setdefault('lock_until', 0)
  48. # bound_data 用于存储绑定的预约人信息,配置文件里可以是 null
  49. acc.setdefault('bound_data', None)
  50. processed_list.append(acc)
  51. count += 1
  52. self._accounts[pool_name] = processed_list
  53. VSC_INFO("acc_mgr", f"Loaded {count} accounts from {self._config_path}")
  54. except json.JSONDecodeError:
  55. VSC_ERROR("acc_mgr", f"Invalid JSON format in {self._config_path}")
  56. except Exception as e:
  57. VSC_ERROR("acc_mgr", f"Failed to load config: {e}")
  58. def get_next_account(self, pool_name: str) -> Optional[Dict[str, Any]]:
  59. """
  60. 获取下一个可用账号 (随机策略)
  61. """
  62. with self._account_lock:
  63. accounts = self._accounts.get(pool_name, [])
  64. if not accounts:
  65. VSC_WARN("acc_mgr", "No accounts found for pool '%s'", pool_name)
  66. return None
  67. now = time.time()
  68. # 筛选未锁定的账号
  69. available = [acc for acc in accounts if acc.get("lock_until", 0) <= now]
  70. if not available:
  71. VSC_DEBUG("acc_mgr", "Pool '%s' has %d accounts but all are locked.", pool_name, len(accounts))
  72. return None
  73. # 随机选择
  74. selected = random.choice(available)
  75. VSC_DEBUG("acc_mgr", "Selected account %s (id=%s) from pool %s",
  76. selected.get("username"), selected.get("id"), pool_name)
  77. return selected
  78. def lock_account(self, pool_name: str, account_id: int, duration_seconds: int):
  79. """
  80. 锁定账号一段时间
  81. """
  82. with self._account_lock:
  83. accounts = self._accounts.get(pool_name, [])
  84. for acc in accounts:
  85. if acc["id"] == account_id:
  86. acc["lock_until"] = time.time() + duration_seconds
  87. VSC_INFO("acc_mgr", "Locked account %s (id=%d) for %ds",
  88. acc.get("username"), account_id, duration_seconds)
  89. return
  90. VSC_WARN("acc_mgr", "Account %d not found in pool %s to lock", account_id, pool_name)
  91. def remove_account(self, pool_name: str, account_id: int, reason: str = "success", extra_data: dict = None):
  92. """
  93. 从内存中移除账号 (预订成功后不再使用)
  94. 注意:这不会修改磁盘上的 accounts.json 文件,重启程序后账号会恢复
  95. """
  96. with self._account_lock:
  97. accounts = self._accounts.get(pool_name, [])
  98. target_acc = None
  99. for acc in accounts:
  100. if acc["id"] == account_id:
  101. target_acc = acc
  102. break
  103. if target_acc:
  104. accounts.remove(target_acc)
  105. VSC_INFO("acc_mgr", "Removed account %s (id=%d) from memory pool %s. Reason: %s",
  106. target_acc.get("username"), account_id, pool_name, reason)
  107. else:
  108. VSC_WARN("acc_mgr", "Attempt to remove non-existent account %d from pool %s", account_id, pool_name)