212 lines
6.4 KiB
Markdown
212 lines
6.4 KiB
Markdown
# 技术文档:插件加载模块
|
||
|
||
## 概述
|
||
|
||
`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 工具集
|
||
- 适用场景:服务端插件化架构、模块热加载、动态功能扩展 |