runner.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env python3
  2. """
  3. JiGuang 算法 CLI 桥接入口。
  4. Electron 调用协议(与旧 api_test.exe 保持一致):
  5. runner <apiName> <base64(JSON)>
  6. stdout 仅输出 JSON 结果,日志与错误信息写入 stderr。
  7. """
  8. from __future__ import annotations
  9. import base64
  10. import glob
  11. import importlib.util
  12. import json
  13. import os
  14. import sys
  15. import traceback
  16. # 无 GUI 环境使用 Agg 后端,避免 matplotlib 依赖 Qt/Tk
  17. os.environ.setdefault("MPLBACKEND", "Agg")
  18. # 确保工作目录为脚本所在目录,便于加载 entry 模块与 license 文件
  19. _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  20. if _SCRIPT_DIR not in sys.path:
  21. sys.path.insert(0, _SCRIPT_DIR)
  22. os.chdir(_SCRIPT_DIR)
  23. def _ensure_dependencies() -> None:
  24. try:
  25. import cryptography # noqa: F401
  26. except ImportError as exc:
  27. message = (
  28. "缺少 cryptography 库,授权校验无法运行。"
  29. "请执行: python -m pip install cryptography"
  30. )
  31. print(json.dumps({"error": message}, ensure_ascii=False))
  32. raise SystemExit(1) from exc
  33. _ensure_dependencies()
  34. def _load_entry_module():
  35. """只加载 Nuitka 编译的 .pyd/.so,优先使用 JiGuang-program 根目录下最新的 entry 文件。"""
  36. patterns = ("entry.cp*.pyd", "entry.cpython-*.so", "entry*.so")
  37. candidates = []
  38. # 根目录优先:更新 entry.pyd 后无需重新编译 api_runner.exe
  39. search_dirs = [
  40. os.path.dirname(_SCRIPT_DIR),
  41. _SCRIPT_DIR,
  42. ]
  43. for search_dir in search_dirs:
  44. if not os.path.isdir(search_dir):
  45. continue
  46. for pattern in patterns:
  47. candidates.extend(glob.glob(os.path.join(search_dir, pattern)))
  48. if candidates:
  49. # 同名文件可能在根目录和 runner.dist 各有一份,取最新修改时间
  50. entry_path = max(candidates, key=os.path.getmtime)
  51. spec = importlib.util.spec_from_file_location("entry", entry_path)
  52. if spec is None or spec.loader is None:
  53. raise ImportError(f"无法加载 entry 模块: {entry_path}")
  54. module = importlib.util.module_from_spec(spec)
  55. sys.modules["entry"] = module
  56. spec.loader.exec_module(module)
  57. return module
  58. raise ImportError(
  59. "未找到 entry.cp*.pyd / entry*.so。请确认 entry 编译模块位于 "
  60. "JiGuang-program 或 runner.dist 目录。"
  61. )
  62. entry = _load_entry_module()
  63. API_MAP = {
  64. "loaddata": entry.load_data,
  65. "getpath": entry.get_path,
  66. "historydata": entry.history_data,
  67. "deletedata": entry.delete_data,
  68. }
  69. def _verify_license() -> None:
  70. if hasattr(entry, "verify_license"):
  71. entry.verify_license()
  72. def _decode_payload(b64_payload: str):
  73. if not b64_payload:
  74. return None
  75. raw = base64.b64decode(b64_payload).decode("utf-8")
  76. return json.loads(raw)
  77. def _is_empty_params(params) -> bool:
  78. return params is None or params == "" or params == {} or params == []
  79. def _dispatch(api_name: str, b64_payload: str):
  80. func = API_MAP.get(api_name)
  81. if func is None:
  82. raise ValueError(f"未知 API: {api_name}")
  83. params = _decode_payload(b64_payload)
  84. if _is_empty_params(params):
  85. return func()
  86. return func(b64_payload)
  87. def main() -> int:
  88. if len(sys.argv) < 2:
  89. print(
  90. json.dumps({"error": "用法: runner <apiName> [base64_json]"}, ensure_ascii=False),
  91. file=sys.stderr,
  92. )
  93. return 1
  94. api_name = sys.argv[1]
  95. b64_payload = sys.argv[2] if len(sys.argv) > 2 else ""
  96. try:
  97. _verify_license()
  98. result = _dispatch(api_name, b64_payload)
  99. print(json.dumps(result, ensure_ascii=False))
  100. return 0
  101. except Exception as exc: # noqa: BLE001
  102. traceback.print_exc(file=sys.stderr)
  103. print(
  104. json.dumps({"error": str(exc)}, ensure_ascii=False),
  105. file=sys.stdout,
  106. )
  107. return 1
  108. if __name__ == "__main__":
  109. raise SystemExit(main())