b3_select_latest_zone.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. """B3: 选今天能玩的最新区 — 纯 DB 查询, 不调 HTTP。
  2. 数据流:
  3. MySQL servers (B1 存的) + zones (B2 存的)
  4. ↓
  5. ZoneStore.get_latest_openable(server_id)
  6. ↓
  7. SQL: WHERE status='open' AND open_time<=NOW() ORDER BY open_time DESC LIMIT 1
  8. ↓
  9. 返单个 Zone (含 host/port 待 C1 填, 这里没有就 None)
  10. ↓
  11. DebugLogger 留痕
  12. 跑法:
  13. uv run python scripts/business/b3_select_latest_zone.py
  14. uv run python scripts/business/b3_select_latest_zone.py --server "玖扬复古176"
  15. uv run python scripts/business/b3_select_latest_zone.py --check
  16. 前置依赖:
  17. 1. B1 跑过 (server 在 MySQL)
  18. 2. B2 跑过 (zones 在 MySQL)
  19. """
  20. from __future__ import annotations
  21. import argparse
  22. import os
  23. import sys
  24. from pathlib import Path
  25. from typing import Any
  26. _HERE = Path(__file__).resolve().parent
  27. _PROJECT = _HERE.parent.parent
  28. sys.path.insert(0, str(_PROJECT / "src"))
  29. from cq2_sdk.env_loader import load_env # noqa: E402
  30. load_env(_PROJECT / ".env")
  31. from cq2_sdk.db_models import Zone # noqa: E402
  32. from cq2_sdk.debug_log import DebugLogger # noqa: E402
  33. from cq2_sdk.store import ServerStore, ZoneStore, connection # noqa: E402
  34. DEFAULT_SERVER_NAME = "玖扬复古176"
  35. def select_latest_zone(server_name: str, log: DebugLogger) -> Zone | None:
  36. """查 MySQL 找今天能玩的最新一个区。"""
  37. log.step("b3_select_latest_zone_start", server_name=server_name)
  38. # 1. server
  39. with connection() as conn:
  40. server = ServerStore(conn).get_by_name(server_name)
  41. if server is None:
  42. raise RuntimeError(
  43. f"server not in MySQL: {server_name!r}。先跑 B1 把它存进去。"
  44. )
  45. log.info("server_loaded",
  46. server_db_id=server.id, server_name=server.server_name,
  47. server_996box_id=server.server_id)
  48. # 2. latest openable zone
  49. zone = ZoneStore(conn).get_latest_openable(server.id)
  50. if zone is None:
  51. log.warn("no_openable_zone",
  52. note="可能 B2 还没跑, 或当前时间早于今天第一个 open_time, "
  53. "或所有 zone 都被 mark_missing_closed 了")
  54. return None
  55. log.info("latest_zone_selected",
  56. zone_db_id=zone.id, zone_id=zone.zone_id, zone_name=zone.zone_name,
  57. open_time=zone.open_time.isoformat() if zone.open_time else None,
  58. status=zone.status,
  59. host=zone.host, port=zone.port,
  60. note="B3 只填 zone 元数据, host/port 待 C1 (launch_game) 拿到")
  61. return zone
  62. def format_zone(zone: Zone | None) -> dict[str, Any]:
  63. """输出友好格式。"""
  64. if zone is None:
  65. return {"found": False}
  66. return {
  67. "found": True,
  68. "db_id": zone.id,
  69. "zone_id": zone.zone_id,
  70. "zone_name": zone.zone_name,
  71. "open_time": zone.open_time.isoformat() if zone.open_time else None,
  72. "status": zone.status,
  73. "host": zone.host,
  74. "port": zone.port,
  75. }
  76. def check_config(server_name: str | None) -> bool:
  77. print("[check] 配置检查:")
  78. print(f" server_name = {server_name!r} [OK]"
  79. if server_name else " server_name = (未设置) [FAIL]")
  80. print(" (实际跑还需要 B1 + B2 已成功跑过, MySQL 里有 server 和 zones)")
  81. return bool(server_name)
  82. def parse_args() -> argparse.Namespace:
  83. p = argparse.ArgumentParser(
  84. description="B3: 从 MySQL 选今天能玩的最新区 (纯 DB, 不调 HTTP)",
  85. formatter_class=argparse.RawDescriptionHelpFormatter,
  86. )
  87. p.add_argument(
  88. "--server",
  89. default=os.environ.get("CQ2_DEFAULT_SERVER", DEFAULT_SERVER_NAME),
  90. help=f"区服名 (默认 {DEFAULT_SERVER_NAME})",
  91. )
  92. p.add_argument(
  93. "--check",
  94. action="store_true",
  95. help="只检查 .env / CLI 配置, 不查 DB",
  96. )
  97. return p.parse_args()
  98. def main() -> int:
  99. args = parse_args()
  100. if args.check:
  101. ok = check_config(args.server)
  102. print(f"\n result: {'READY' if ok else 'NOT READY'}")
  103. return 0 if ok else 1
  104. if not args.server:
  105. check_config(args.server)
  106. return 1
  107. with DebugLogger(extra={"task": "b3", "server_name": args.server}) as log:
  108. log.info("b3_start", server_name=args.server)
  109. try:
  110. zone = select_latest_zone(args.server, log)
  111. except Exception as e:
  112. log.error(e, step="b3_unexpected")
  113. print(f"\n[FAIL] {type(e).__name__}: {e}")
  114. print(f" log file: {log.path}")
  115. return 1
  116. result = format_zone(zone)
  117. if not result["found"]:
  118. print("\n[FAIL] 没找到今天能玩的区")
  119. print(f" log file: {log.path}")
  120. return 2
  121. print("\n[OK] B3 选区成功:")
  122. for k, v in result.items():
  123. print(f" {k:<12} = {v}")
  124. print(f" log file = {log.path}")
  125. return 0
  126. if __name__ == "__main__":
  127. sys.exit(main())