main_booker.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import os
  2. import time
  3. import json
  4. import argparse
  5. from typing import List
  6. from vs_types import GroupConfig
  7. from gco_wrapper import GCOWrapper
  8. from logger_setup import setup_app_logger
  9. from booker_builtin import BuiltinBookerGCO
  10. from booker_order import OrderBookerGCO
  11. from toolkit.vs_cloud_api import VSCloudApi
  12. def load_config(path: str):
  13. if not os.path.exists(path):
  14. return {}
  15. with open(path, "r", encoding="utf-8") as f:
  16. return json.load(f)
  17. def save_config(path: str, new_config):
  18. new_config_str = json.dumps(new_config, indent=2)
  19. with open(path, "w", encoding="utf-8") as f:
  20. f.write(new_config_str)
  21. def main():
  22. # ===== 1️⃣ 命令行参数 =====
  23. parser = argparse.ArgumentParser(description="Booker Runner")
  24. parser.add_argument(
  25. "-c", "--config",
  26. type=str,
  27. required=False,
  28. default="config/config_booker.json",
  29. help="Path to booker config.json"
  30. )
  31. args = parser.parse_args()
  32. config_path = args.config
  33. CONF_NAME = 'COORDINATOR_BOOKER'
  34. # ===== 2️⃣ 日志 =====
  35. app_logger = setup_app_logger("Booker")
  36. app_logger.info("Booker Logger is ready!")
  37. # ===== 3️⃣ 加载配置 =====
  38. cfg_data = load_config(config_path)
  39. current_version = cfg_data.get('version', 0)
  40. redis_conf = cfg_data.get('redis')
  41. groups_conf = cfg_data.get('group_list', [])
  42. wrappers: List[GCOWrapper] = []
  43. # ===== 4️⃣ 启动逻辑 =====
  44. for item in groups_conf:
  45. cfg = GroupConfig.from_json(item)
  46. if not cfg.enable:
  47. continue
  48. if cfg.booker.account_source == "order":
  49. gco_class = OrderBookerGCO
  50. app_logger.info(f"[{cfg.identifier}] Mode: ORDER (Bound)")
  51. else:
  52. gco_class = BuiltinBookerGCO
  53. app_logger.info(f"[{cfg.identifier}] Mode: BUILT-IN (Unbound)")
  54. wrapper = GCOWrapper(
  55. gco_class=gco_class,
  56. gco_cfg=cfg,
  57. redis_conf=redis_conf
  58. )
  59. wrapper.load()
  60. wrapper.start()
  61. wrappers.append(wrapper)
  62. app_logger.info(f"Started {len(wrappers)} Booker groups. Press Ctrl+C to stop.")
  63. # ===== 5️⃣ 保持运行 & 热更新监听 =====
  64. try:
  65. while True:
  66. time.sleep(30) # 每 3 秒检查一次文件状态
  67. try:
  68. new_cfg_data = VSCloudApi.Instance().get_dynamic_config(config_name=CONF_NAME)
  69. new_version = new_cfg_data.get('version')
  70. # 如果 API 返回的没有 version 字段,或者版本号没变,则跳过
  71. if new_version == current_version:
  72. continue
  73. app_logger.info("Config file modification detected! Reloading Booker configurations...")
  74. save_config(config_path, new_cfg_data)
  75. current_version = new_version
  76. redis_conf = new_cfg_data.get('redis')
  77. new_groups_conf = new_cfg_data.get('group_list', [])
  78. # 转换新配置为字典格式 {identifier: config_dict}
  79. new_cfg_dict = {
  80. item.get("identifier"): item
  81. for item in new_groups_conf
  82. if item.get("identifier")
  83. }
  84. # ---------------- A. 处理【参数更新】、【删除/禁用】、【模式切换】 ----------------
  85. surviving_wrappers = []
  86. for wrapper in wrappers:
  87. current_id = wrapper.m_cfg.identifier
  88. if current_id in new_cfg_dict:
  89. new_group_cfg = GroupConfig.from_json(new_cfg_dict[current_id])
  90. # 1. 后端禁用了该组
  91. if not new_group_cfg.enable:
  92. app_logger.info(f"Group [{current_id}] disabled by backend. Stopping...")
  93. wrapper.stop()
  94. # 2. 账号来源模式发生了改变 (例如 built-in 变 order) -> 需要更换底层类,必须重启该组
  95. elif wrapper.m_cfg.booker.account_source != new_group_cfg.booker.account_source:
  96. app_logger.info(f"Group [{current_id}] Mode changed ({wrapper.m_cfg.booker.account_source} -> {new_group_cfg.booker.account_source}). Stopping old instance to recreate...")
  97. wrapper.stop()
  98. # 不加入 surviving_wrappers,让下面的步骤 B 重新创建它
  99. # 3. 模式没变,依然启用 -> 热更新参数
  100. else:
  101. wrapper.update_config(new_group_cfg)
  102. surviving_wrappers.append(wrapper)
  103. else:
  104. # 4. 该组被从 JSON 中删除了
  105. app_logger.info(f"Group [{current_id}] deleted from config. Stopping...")
  106. wrapper.stop()
  107. wrappers = surviving_wrappers
  108. # ---------------- B. 处理【新增组】、【重新启用组】、【模式切换后的重建】 ----------------
  109. existing_ids = {w.m_cfg.identifier for w in wrappers}
  110. for new_id, item_data in new_cfg_dict.items():
  111. if new_id not in existing_ids:
  112. cfg = GroupConfig.from_json(item_data)
  113. if not cfg.enable:
  114. continue
  115. # 判断采用哪个底层类
  116. if cfg.booker.account_source == "order":
  117. gco_class = OrderBookerGCO
  118. mode_str = "ORDER (Bound)"
  119. else:
  120. gco_class = BuiltinBookerGCO
  121. mode_str = "BUILT-IN (Unbound)"
  122. app_logger.info(f"Dynamically starting NEW/RECREATED Booker group [{cfg.identifier}] Mode: {mode_str}...")
  123. new_wrapper = GCOWrapper(
  124. gco_class=gco_class,
  125. gco_cfg=cfg,
  126. redis_conf=redis_conf
  127. )
  128. try:
  129. new_wrapper.load()
  130. new_wrapper.start()
  131. wrappers.append(new_wrapper)
  132. except Exception as e:
  133. app_logger.error(f"Failed to dynamically start Booker group [{cfg.identifier}]: {e}")
  134. except json.JSONDecodeError:
  135. app_logger.warning("Config file is currently invalid JSON (maybe still writing), skipping this reload.")
  136. except Exception as e:
  137. app_logger.error(f"Error while hot-reloading config: {e}")
  138. except KeyboardInterrupt:
  139. app_logger.info("Shutting down Bookers...")
  140. for wrapper in wrappers:
  141. wrapper.stop()
  142. if __name__ == "__main__":
  143. main()