ahserver/aidocs/loadplugins.md
2025-10-05 12:07:12 +08:00

212 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 技术文档:插件加载模块
## 概述
`load_plugins(p_dir)` 是一个用于动态加载指定目录下 Python 插件模块的函数。该函数通过扫描 `plugins` 子目录中的 `.py` 文件,将非 `__init__.py` 的模块导入运行时环境,并支持在插件中访问关键系统和框架对象(如 `sys`, `ServerEnv` 等)。
此功能常用于扩展应用功能,实现基于插件架构的灵活系统设计。
---
## 依赖说明
### 第三方/内部模块依赖
| 模块名 | 来源 | 用途 |
|--------|------|------|
| `os`, `sys` | Python 标准库 | 路径操作与模块路径管理 |
| `appPublic.folderUtils.listFile` | 内部公共库 | 列出指定目录中符合后缀条件的文件 |
| `appPublic.ExecFile` | 内部公共库 | 提供可执行上下文环境,用于注入变量到模块中 |
| `ahserver.serverenv.ServerEnv` | AHServer 框架 | 服务器运行时环境类,供插件使用 |
| `appPublic`, `sqlor`, `ahserver` | 内部包引用 | 确保相关模块已初始化并可被插件导入 |
> ⚠️ 注意:所有 `appPublic.*` 和 `ahserver.*` 均为项目自定义模块,需确保已正确安装或部署。
---
## 函数定义
```python
def load_plugins(p_dir):
"""
动态加载指定主目录下的 plugins 子目录中所有合法的 Python 插件模块。
参数:
p_dir (str): 主路径,插件目录应位于 p_dir/plugins/
返回值:
None
行为:
- 若 plugins 目录不存在,则直接返回。
- 将 plugins 目录添加至 sys.path使模块可被 import。
- 使用 ExecFile 注入全局依赖对象(如 sys, ServerEnv
- 遍历所有 .py 文件,排除 __init__.py逐一导入模块。
"""
```
---
## 实现细节
### 1. 初始化执行环境
```python
ef = ExecFile()
```
- 创建一个 `ExecFile` 实例,可用于后续向模块执行环境中注入变量(目前仅设置,未实际执行文件内容)。
### 2. 构建插件目录路径
```python
pdir = os.path.join(p_dir, 'plugins')
```
- 插件必须存放在传入路径 `p_dir` 下的 `plugins` 子目录中。
### 3. 目录存在性检查
```python
if not os.path.isdir(pdir):
return
```
- 如果 `plugins` 目录不存在,函数静默退出,不进行任何操作。
### 4. 添加插件路径至模块搜索路径
```python
sys.path.append(pdir)
```
- 将插件目录加入 `sys.path`,使得后续 `__import__` 可以直接导入这些模块。
> ❗ 安全提示:动态修改 `sys.path` 可能带来命名冲突或安全风险,请确保插件来源可信。
### 5. 注入共享对象到执行环境
```python
ef.set('sys', sys)
ef.set('ServerEnv', ServerEnv)
```
- 虽然设置了 `ExecFile` 上下文,但当前代码并未调用其执行方法(如 `execfile`),因此这一步可能是预留接口或冗余代码。
- 实际上,插件模块是否能访问 `sys``ServerEnv` 取决于它们自身是否显式导入。
### 6. 遍历并加载插件模块
```python
for m in listFile(pdir, suffixs='.py'):
if m == '__init__.py':
continue
if not m.endswith('.py'):
continue
module = os.path.basename(m[:-3])
__import__(module, locals(), globals())
```
#### 步骤说明:
- 使用 `listFile(pdir, suffixs='.py')` 获取所有 `.py` 结尾的文件路径。
- 跳过 `__init__.py` 文件(通常用于包初始化,非独立插件)。
- 提取文件名(不含 `.py` 扩展名)作为模块名。
- 使用 `__import__()` 动态导入模块,触发其代码执行。
> ✅ 效果:每个插件模块只要被成功导入,其顶层代码即被执行,可用于注册服务、绑定事件、初始化资源等。
---
## 使用示例
假设目录结构如下:
```
/app_root/
└── plugins/
├── plugin_a.py
├── plugin_b.py
└── __init__.py
```
调用方式:
```python
load_plugins('/app_root')
```
结果:
- `/app_root/plugins` 被加入 `sys.path`
- `plugin_a``plugin_b` 被导入并执行
---
## 插件编写规范
插件模块(如 `plugin_x.py`)应满足以下要求:
1. 必须是有效的 `.py` 文件;
2. 不得命名为 `__init__.py`
3. 应包含必要的导入语句自行获取所需依赖;
4. 可在模块级执行注册逻辑,例如:
```python
# plugin_hello.py
from ahserver.serverenv import ServerEnv
def hello():
print("Hello from plugin!")
# 自动注册钩子
ServerEnv.register_hook('startup', hello)
```
---
## 注意事项与建议
| 项目 | 说明 |
|------|------|
| 🛑 **异常处理缺失** | 当前代码未捕获导入错误(如语法错误、依赖缺失),可能导致启动失败。建议包裹 `try-except` 并记录日志。 |
| 🔍 **ExecFile 设置未生效** | `ef.set(...)` 设置的对象未传递给导入的模块,除非 `ExecFile.execfile()` 被调用,否则无实际作用。若无需执行脚本,可移除 `ef` 相关代码。 |
| 🧹 **冗余判断** | `listFile` 已按 `.py` 过滤,内层 `endswith('.py')` 判断可省略。 |
| 📁 **路径污染风险** | 多次调用 `load_plugins` 可能重复添加相同路径到 `sys.path`,建议去重或检查是否存在。 |
---
## 改进建议
```python
import os
import sys
from appPublic.folderUtils import listFile
from ahserver.serverenv import ServerEnv
def load_plugins(p_dir):
pdir = os.path.join(p_dir, 'plugins')
if not os.path.isdir(pdir):
return
# 避免重复添加路径
if pdir not in sys.path:
sys.path.insert(0, pdir) # 更推荐插入开头
for file_path in listFile(pdir, suffixs='.py'):
filename = os.path.basename(file_path)
if filename == '__init__.py':
continue
module_name = filename[:-3] # 去除 .py
try:
__import__(module_name, locals(), globals())
except Exception as e:
print(f"Failed to load plugin {module_name}: {e}")
# 建议替换为 logger.error(...)
```
---
## 总结
`load_plugins` 是一个轻量级插件加载器,适用于基于文件系统的模块自动发现机制。尽管当前实现较为基础,但具备良好的扩展潜力。通过合理组织插件目录和规范插件行为,可构建出高内聚、低耦合的服务扩展体系。
---
📌 **版本信息**
- 语言Python 3.x
- 框架依赖AHServer + appPublic 工具集
- 适用场景:服务端插件化架构、模块热加载、动态功能扩展