| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import os
- import time
- import json
- import argparse
- from typing import List
- from vs_types import GroupConfig
- from gco_wrapper import GCOWrapper
- from logger_setup import setup_app_logger
- from booker_builtin import BuiltinBookerGCO
- from booker_order import OrderBookerGCO
- from toolkit.vs_cloud_api import VSCloudApi
- def load_config(path):
- with open(path, "r", encoding="utf-8") as f:
- return json.load(f)
- def save_config(path: str, new_config):
- new_config_str = json.dumps(new_config, indent=2)
- with open(path, "w", encoding="utf-8") as f:
- f.write(new_config_str)
- def main():
- # ===== 1️⃣ 命令行参数 =====
- parser = argparse.ArgumentParser(description="Booker Runner")
- parser.add_argument(
- "-c", "--config",
- type=str,
- required=False,
- default="config/config_booker.json",
- help="Path to booker config.json"
- )
- args = parser.parse_args()
- config_path = args.config
- CONF_NAME = 'COORDINATOR_BOOKER'
- # ===== 2️⃣ 日志 =====
- app_logger = setup_app_logger("Booker")
- app_logger.info("Booker Logger is ready!")
- # ===== 3️⃣ 加载配置 =====
- cfg_data = load_config(config_path)
-
- current_version = cfg_data.get('version', 0)
- redis_conf = cfg_data.get('redis')
- groups_conf = cfg_data.get('group_list', [])
- wrappers: List[GCOWrapper] = []
- # ===== 4️⃣ 启动逻辑 =====
- for item in groups_conf:
- cfg = GroupConfig.from_json(item)
- if not cfg.enable:
- continue
- if cfg.booker.account_source == "order":
- gco_class = OrderBookerGCO
- app_logger.info(f"[{cfg.identifier}] Mode: ORDER (Bound)")
- else:
- gco_class = BuiltinBookerGCO
- app_logger.info(f"[{cfg.identifier}] Mode: BUILT-IN (Unbound)")
- wrapper = GCOWrapper(
- gco_class=gco_class,
- gco_cfg=cfg,
- redis_conf=redis_conf
- )
- wrapper.load()
- wrapper.start()
- wrappers.append(wrapper)
- app_logger.info(f"Started {len(wrappers)} Booker groups. Press Ctrl+C to stop.")
- # ===== 5️⃣ 保持运行 & 热更新监听 =====
- try:
- while True:
- time.sleep(30) # 每 3 秒检查一次文件状态
-
- if not os.path.exists(config_path):
- continue
-
- try:
- new_cfg_data = VSCloudApi.Instance().get_dynamic_config(config_name=CONF_NAME)
- new_version = new_cfg_data.get('version')
- # 如果 API 返回的没有 version 字段,或者版本号没变,则跳过
- if new_version == current_version:
- continue
- app_logger.info("Config file modification detected! Reloading Booker configurations...")
- save_config(config_path, new_cfg_data)
- current_version = new_version
-
- redis_conf = new_cfg_data.get('redis')
- new_groups_conf = new_cfg_data.get('group_list', [])
-
- # 转换新配置为字典格式 {identifier: config_dict}
- new_cfg_dict = {
- item.get("identifier"): item
- for item in new_groups_conf
- if item.get("identifier")
- }
-
- # ---------------- A. 处理【参数更新】、【删除/禁用】、【模式切换】 ----------------
- surviving_wrappers = []
- for wrapper in wrappers:
- current_id = wrapper.m_cfg.identifier
-
- if current_id in new_cfg_dict:
- new_group_cfg = GroupConfig.from_json(new_cfg_dict[current_id])
-
- # 1. 后端禁用了该组
- if not new_group_cfg.enable:
- app_logger.info(f"Group [{current_id}] disabled by backend. Stopping...")
- wrapper.stop()
-
- # 2. 账号来源模式发生了改变 (例如 built-in 变 order) -> 需要更换底层类,必须重启该组
- elif wrapper.m_cfg.booker.account_source != new_group_cfg.booker.account_source:
- 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...")
- wrapper.stop()
- # 不加入 surviving_wrappers,让下面的步骤 B 重新创建它
-
- # 3. 模式没变,依然启用 -> 热更新参数
- else:
- wrapper.update_config(new_group_cfg)
- surviving_wrappers.append(wrapper)
- else:
- # 4. 该组被从 JSON 中删除了
- app_logger.info(f"Group [{current_id}] deleted from config. Stopping...")
- wrapper.stop()
-
- wrappers = surviving_wrappers
-
- # ---------------- B. 处理【新增组】、【重新启用组】、【模式切换后的重建】 ----------------
- existing_ids = {w.m_cfg.identifier for w in wrappers}
-
- for new_id, item_data in new_cfg_dict.items():
- if new_id not in existing_ids:
- cfg = GroupConfig.from_json(item_data)
-
- if not cfg.enable:
- continue
-
- # 判断采用哪个底层类
- if cfg.booker.account_source == "order":
- gco_class = OrderBookerGCO
- mode_str = "ORDER (Bound)"
- else:
- gco_class = BuiltinBookerGCO
- mode_str = "BUILT-IN (Unbound)"
-
- app_logger.info(f"Dynamically starting NEW/RECREATED Booker group [{cfg.identifier}] Mode: {mode_str}...")
-
- new_wrapper = GCOWrapper(
- gco_class=gco_class,
- gco_cfg=cfg,
- redis_conf=redis_conf
- )
- try:
- new_wrapper.load()
- new_wrapper.start()
- wrappers.append(new_wrapper)
- except Exception as e:
- app_logger.error(f"Failed to dynamically start Booker group [{cfg.identifier}]: {e}")
-
- except json.JSONDecodeError:
- app_logger.warning("Config file is currently invalid JSON (maybe still writing), skipping this reload.")
- except Exception as e:
- app_logger.error(f"Error while hot-reloading config: {e}")
- except KeyboardInterrupt:
- app_logger.info("Shutting down Bookers...")
- for wrapper in wrappers:
- wrapper.stop()
- if __name__ == "__main__":
- main()
|