2b9797d feat: add customer hooks plugin system (v1.1.0) 26d2731 chore: add VERSION file (1.0.0) git-subtree-dir: core git-subtree-split: 2b9797d61b501ecbaa73253f6f4001769917a24f
93 lines
3.0 KiB
Python
93 lines
3.0 KiB
Python
"""客户钩子加载器 — 从 customers/{CUSTOMER}/hooks/ 动态加载客户自定义钩子
|
|
|
|
加载逻辑:
|
|
1. 读取 CUSTOMER 环境变量
|
|
2. 查找 customers/{CUSTOMER}/hooks/__init__.py
|
|
3. 导入并返回 hooks 实例
|
|
4. 找不到则返回默认空钩子(所有方法为空操作)
|
|
"""
|
|
import importlib
|
|
import importlib.util
|
|
import logging
|
|
import os
|
|
import sys
|
|
from functools import lru_cache
|
|
from typing import Optional
|
|
|
|
from app.hooks.base import CustomerHooks
|
|
from app.core.config import get_settings
|
|
|
|
logger = logging.getLogger("hooks.loader")
|
|
|
|
_hooks_instance: Optional[CustomerHooks] = None
|
|
|
|
|
|
def _find_hooks_dir() -> Optional[str]:
|
|
"""Find the customer hooks directory, searching multiple locations."""
|
|
settings = get_settings()
|
|
customer = settings.CUSTOMER
|
|
config_path = settings.customer_config_path
|
|
|
|
hooks_dir = os.path.join(config_path, "hooks")
|
|
if os.path.isdir(hooks_dir) and os.path.exists(os.path.join(hooks_dir, "__init__.py")):
|
|
return hooks_dir
|
|
return None
|
|
|
|
|
|
def _load_hooks_from_dir(hooks_dir: str) -> Optional[CustomerHooks]:
|
|
"""Load hooks module from a directory path."""
|
|
try:
|
|
init_path = os.path.join(hooks_dir, "__init__.py")
|
|
spec = importlib.util.spec_from_file_location("customer_hooks", init_path)
|
|
if spec and spec.loader:
|
|
# Add parent dir to sys.path so relative imports work
|
|
parent_dir = os.path.dirname(os.path.dirname(hooks_dir))
|
|
if parent_dir not in sys.path:
|
|
sys.path.insert(0, parent_dir)
|
|
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
hooks = getattr(module, "hooks", None)
|
|
if isinstance(hooks, CustomerHooks):
|
|
return hooks
|
|
else:
|
|
logger.warning(
|
|
f"Customer hooks module loaded but 'hooks' attribute is not a CustomerHooks instance"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to load customer hooks: {e}", exc_info=True)
|
|
return None
|
|
|
|
|
|
def get_hooks() -> CustomerHooks:
|
|
"""获取当前客户的钩子实例。线程安全,全局单例。
|
|
|
|
Returns:
|
|
CustomerHooks: 客户钩子实例,如果客户没有自定义钩子则返回默认空钩子
|
|
"""
|
|
global _hooks_instance
|
|
if _hooks_instance is not None:
|
|
return _hooks_instance
|
|
|
|
settings = get_settings()
|
|
hooks_dir = _find_hooks_dir()
|
|
|
|
if hooks_dir:
|
|
loaded = _load_hooks_from_dir(hooks_dir)
|
|
if loaded:
|
|
logger.info(f"Loaded customer hooks for '{settings.CUSTOMER}' from {hooks_dir}")
|
|
_hooks_instance = loaded
|
|
return _hooks_instance
|
|
|
|
logger.info(f"No custom hooks for '{settings.CUSTOMER}', using defaults")
|
|
_hooks_instance = CustomerHooks()
|
|
return _hooks_instance
|
|
|
|
|
|
def reload_hooks() -> CustomerHooks:
|
|
"""重新加载客户钩子(开发时热重载用)"""
|
|
global _hooks_instance
|
|
_hooks_instance = None
|
|
return get_hooks()
|