From 395cda2e8b4ca978aee6642b7c413224b9e9cc6a Mon Sep 17 00:00:00 2001 From: Falck Date: Sat, 25 Apr 2026 06:43:45 +0800 Subject: [PATCH] chore: add website directory to gitignore and update VSCode config - Add `website/` to .gitignore to exclude website build artifacts - Add Node.js debug configurations for FutureOSS website in launch.json - Update VSCode color theme to "Default Dark Modern" - Refactor plugin loader to simplify dependency and lifecycle plugin loading logic --- .gitignore | 3 +- .vscode/launch.json | 55 +- .vscode/settings.json | 4 +- ai.md | 661 +++++++++++++ .../@{FutureOSS}/plugin-loader/PL_EXAMPLE.md | 172 ++++ store/@{FutureOSS}/plugin-loader/main.py | 898 +++++++++--------- 6 files changed, 1314 insertions(+), 479 deletions(-) create mode 100644 ai.md create mode 100644 store/@{FutureOSS}/plugin-loader/PL_EXAMPLE.md diff --git a/.gitignore b/.gitignore index fa48c81..8ecdab5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ data/signature-verifier/keys/private/ # 签名文件(可选,本地开发可能不需要) # store/**/SIGNATURE -.clinerules \ No newline at end of file +.clinerules +website/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 2e467d5..a024fc6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -165,6 +165,59 @@ }, "cwd": "${workspaceFolder}", "python": "${command:python.interpreterPath}" + }, + { + "name": "FutureOSS 网站: 启动Node.js服务器", + "type": "node", + "request": "launch", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/website/src/server.js", + "outFiles": [ + "${workspaceFolder}/website/**/*.js" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "env": { + "NODE_ENV": "development" + }, + "autoAttachChildProcesses": true, + "sourceMaps": true, + "showAsyncStacks": true, + "smartStep": true, + "pauseForSourceMap": true, + "cwd": "${workspaceFolder}/website" + }, + { + "name": "FutureOSS 网站: 开发模式启动", + "type": "node", + "request": "launch", + "skipFiles": [ + "/**" + ], + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "env": { + "NODE_ENV": "development" + }, + "autoAttachChildProcesses": true, + "sourceMaps": true, + "cwd": "${workspaceFolder}/website" + }, + { + "name": "FutureOSS 网站: 附加到Node.js进程", + "type": "node", + "request": "attach", + "port": 9229, + "skipFiles": [ + "/**" + ], + "restart": true, + "localRoot": "${workspaceFolder}/website", + "remoteRoot": "${workspaceFolder}/website" } ], "inputs": [ @@ -185,4 +238,4 @@ "stopAll": true } ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bee2024..9015180 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -108,7 +108,7 @@ "workbench.editor.enablePreview": false, "workbench.editor.enablePreviewFromQuickOpen": false, "workbench.startupEditor": "none", - "workbench.colorTheme": "Dark Modern", + "workbench.colorTheme": "Default Dark Modern", "workbench.iconTheme": "vs-seti", "breadcrumbs.enabled": true, "editor.minimap.enabled": true, @@ -150,4 +150,4 @@ "editor.suggest.showValues": true, "editor.suggest.showVariables": true, "editor.suggest.showWords": true -} +} \ No newline at end of file diff --git a/ai.md b/ai.md new file mode 100644 index 0000000..482b933 --- /dev/null +++ b/ai.md @@ -0,0 +1,661 @@ +# FutureOSS - AI 专用项目介绍 + +## 项目概述 + +**FutureOSS** 是一个面向开发者的插件化运行时框架,采用「一切皆为插件」的设计理念。它是一个轻量级、安全、灵活的底层支撑系统,适用于构建微服务、开发工具链和可扩展的业务系统。 + +**核心设计哲学**:最小化核心框架,最大化插件扩展能力。核心框架仅提供最基本的插件加载和管理功能,所有其他功能(HTTP服务、Web界面、日志系统等)都通过插件实现。 + +## 技术栈 + +- **语言**: Python 3.10+ +- **主要依赖**: + - `click`: CLI 框架 + - `pyyaml`: 配置解析 + - `websockets`: WebSocket 支持 + - `psutil`: 系统监控 + - `cryptography`: 加密和签名验证 +- **架构**: 插件化微内核架构 +- **协议支持**: HTTP RESTful API, WebSocket, TCP HTTP +- **部署**: Docker 容器化支持 + +## 项目结构 + +``` +/root/future-oss/ +├── 📁 oss/ # 核心框架代码 +│ ├── cli.py # CLI 命令入口 +│ ├── config/ # 配置系统 +│ ├── logger/ # 日志系统 +│ ├── plugin/ # 插件框架核心 +│ │ ├── capabilities.py # 能力接口定义 +│ │ ├── loader.py # 插件加载器 +│ │ ├── manager.py # 插件生命周期管理 +│ │ └── types.py # 类型定义 +│ └── shared/ # 共享组件 +│ └── router.py # 统一路由系统 +├── 📁 store/ # 插件仓库(核心功能) +│ ├── @{FutureOSS}/ # 官方核心插件 +│ │ ├── plugin-loader/ # 插件加载器(核心) +│ │ ├── http-api/ # HTTP API 服务 +│ │ ├── http-tcp/ # TCP HTTP 服务 +│ │ ├── ws-api/ # WebSocket API +│ │ ├── dashboard/ # Web 控制台 +│ │ ├── dependency/ # 依赖解析 +│ │ ├── signature-verifier/ # 签名验证 +│ │ ├── plugin-bridge/ # 插件间通信 +│ │ ├── plugin-storage/ # 数据持久化 +│ │ ├── pkg-manager/ # 包管理 +│ │ ├── log-terminal/ # 日志终端 +│ │ ├── json-codec/ # JSON 编解码器 +│ │ └── webui/ # Web 用户界面 +│ └── @{Falck}/ # 社区插件 +│ ├── html-render/ # HTML 渲染引擎 +│ └── web-toolkit/ # Web 开发工具集 +├── 📁 data/ # 运行时数据目录 +├── 📁 static/ # 静态资源 +├── 📁 templates/ # 模板文件 +├── 📁 tools/ # 开发工具脚本 +├── 📁 video/ # 演示视频和文档 +├── 📄 pyproject.toml # Python 项目配置 +├── 📄 requirements.txt # Python 依赖 +├── 📄 docker-compose.yml # Docker 编排配置 +├── 📄 Dockerfile # Docker 构建文件 +├── 📄 README.md # 项目说明文档 +└── 📄 LICENSE # Apache 2.0 许可证 +``` + +## 核心架构 + +### 1. 插件系统架构 + +FutureOSS 采用三层插件架构: + +1. **核心框架层** (`oss/`): 提供最基本的插件加载和管理能力 +2. **核心插件层** (`store/@{FutureOSS}/`): 官方提供的核心功能插件 +3. **社区插件层** (`store/@{Falck}/`): 第三方社区插件 + +### 2. 插件生命周期 + +``` +加载 (load) → 初始化 (init) → 启动 (start) → 运行 (run) → 停止 (stop) +``` + +### 3. 插件元数据格式 + +每个插件必须包含 `manifest.json` 文件,格式如下: + +```json +{ + "metadata": { + "name": "插件名称", + "version": "版本号", + "author": "作者", + "description": "功能描述", + "type": "插件类型 (core/protocol/utility)" + }, + "config": { + "enabled": true/false, + "args": { + "参数名": "参数值" + } + }, + "dependencies": ["依赖插件列表"], + "permissions": ["所需权限列表"] +} +``` + +### 4. 安全机制 + +- **数字签名验证**: 每个插件包含 SIGNATURE 文件 +- **权限分级控制**: 插件声明所需权限 +- **沙箱环境**: 可选的安全隔离 +- **来源验证**: 插件作者命名空间 (@{作者名}) + +## 核心插件功能 + +### 系统核心插件 + +1. **plugin-loader**: 插件扫描、加载与生命周期管理(必需) +2. **http-api**: HTTP RESTful API 服务(端口 8080) +3. **http-tcp**: TCP 高性能 HTTP 服务(端口 8082) +4. **ws-api**: WebSocket API 服务(端口 8081) +5. **dashboard**: Web 可视化监控仪表盘 +6. **dependency**: 插件依赖解析与自动安装 +7. **signature-verifier**: 插件数字签名验证 +8. **plugin-bridge**: 插件间通信桥接 +9. **plugin-storage**: 插件数据持久化存储 +10. **pkg-manager**: 插件包管理(安装/卸载/搜索) +11. **log-terminal**: 日志终端实时输出 +12. **json-codec**: 统一 JSON 编解码器 +13. **webui**: Web 用户界面框架 + +### 社区插件 + +1. **html-render**: HTML 模板渲染引擎 +2. **web-toolkit**: Web 开发工具集(静态文件/模板/路由) + +### 禁用插件(默认不加载) + +1. **hot-reload**: 开发模式热重载 +2. **i18n**: 国际化支持 +3. **lifecycle**: 插件生命周期钩子 +4. **code-reviewer**: 代码审查工具 +5. **plugin-loader-pro**: 高级插件加载器 + +## PL 注入机制 + +PL 注入是 plugin-loader 插件提供的一种扩展机制,允许插件通过 `PL/` 文件夹向插件加载器注册自定义功能。 + +### 工作原理 + +插件加载器在启动时自动扫描所有插件,检查其 `manifest.json` 中是否声明了 `pl_injection` 配置项: + +``` +插件加载器启动 + ↓ +扫描所有插件 manifest.json + ↓ +检查 config.args.pl_injection + ├── true → 检查 PL/ 文件夹 + │ ├── 存在 PL/main.py → 沙箱执行 → 调用 register(injector) → ✅ 正常加载 + │ └── 缺少 PL/ 或 PL/main.py → ⚠️ 警告并 ❌ 拒绝加载 + └── false/未声明 → ✅ 正常加载(跳过 PL 检查) +``` + +### 使用方式 + +#### 1. 在 manifest.json 中声明 + +```json +{ + "config": { + "args": { + "pl_injection": true + } + } +} +``` + +#### 2. 创建 PL/main.py + +``` +store/@{作者名}/插件名/ +├── manifest.json # 声明 pl_injection: true +├── main.py # 插件主逻辑 +├── PL/ # PL 注入文件夹 +│ └── main.py # 注入逻辑(必须包含 register() 函数) +└── README.md +``` + +#### 3. 实现 register() 函数 + +```python +# PL/main.py +def register(injector): + """向插件加载器注册功能 + + Args: + injector: PLInjector 实例 + """ + # 注册普通功能 + injector.register_function("my_helper", my_func, "功能描述") + + # 注册 HTTP 路由 + injector.register_route("GET", "/pl/hello", handler) + + # 注册事件处理器 + injector.register_event_handler("plugin.started", on_started) +``` + +### 注入器 API + +| 方法 | 说明 | +|------|------| +| `register_function(name, func, description="")` | 注册注入功能 | +| `register_route(method, path, handler)` | 注册 HTTP 路由 | +| `register_event_handler(event_name, handler)` | 注册事件处理器 | +| `get_injected_functions(name=None)` | 获取已注册的注入功能 | +| `get_injection_info(plugin_name=None)` | 获取注入信息 | +| `has_injection(plugin_name)` | 检查插件是否有 PL 注入 | +| `get_registry_info()` | 获取注册表完整信息 | + +### 安全限制 + +PL 注入机制实施了多层安全限制: + +| 限制类型 | 具体措施 | +|---------|---------| +| **文件类型限制** | 禁止 PL 文件夹中包含 `.sh`、`.bat`、`.exe`、`.dll`、`.so` 等可执行文件 | +| **静态源码检查** | 编译前扫描源码,禁止导入 `os/sys/subprocess/socket/ctypes` 等系统模块,禁止 `exec/eval/compile/open/__import__` | +| **沙箱执行** | 在受限沙箱中执行 PL/main.py,仅提供安全的 builtins | +| **参数校验** | 功能名称、路由路径、HTTP 方法、事件名称均通过正则校验 | +| **数量限制** | 每个插件最多注册 50 个功能,每个名称最多被注册 10 次 | +| **异常安全** | 所有注册函数自动包装 try-catch,异常不影响主流程 | +| **调用者溯源** | 通过栈帧回溯自动识别调用者插件名,防止冒充注册 | + +### 行为说明 + +| 场景 | 结果 | +|------|------| +| `pl_injection: true` + 存在 `PL/main.py` | ✅ 正常加载,执行注入 | +| `pl_injection: true` + 缺少 `PL/` 文件夹 | ❌ 警告并拒绝加载该插件 | +| `pl_injection: true` + 存在 `PL/` 但缺少 `main.py` | ❌ 警告并拒绝加载该插件 | +| 未声明 `pl_injection` | ✅ 正常加载,跳过 PL 检查 | +| `pl_injection: false` | ✅ 正常加载,跳过 PL 检查 | + +## 开发与部署 + +### 开发环境设置 + +```bash +# 安装依赖 +pip install -e . + +# 启动开发服务器 +oss serve + +# 访问 Web 控制台 +# http://localhost:8080 +``` + +### Docker 部署 + +```bash +# 使用 Docker Compose +docker-compose up -d + +# 暴露端口 +# 8080: HTTP API + 网站 +# 8081: WebSocket +# 8082: HTTP TCP +``` + +### 插件开发 + +1. **创建插件目录**: `store/@{作者名}/{插件名}/` +2. **编写 manifest.json**: 定义插件元数据 +3. **实现 main.py**: 插件主逻辑 +4. **添加 SIGNATURE**: 数字签名(可选) +5. **测试插件**: 通过 pkg-manager 安装测试 + +## API 接口 + +### HTTP API (端口 8080) + +- `GET /health`: 健康检查 +- `GET /api/plugins`: 获取插件列表 +- `GET /api/plugins/{name}`: 获取插件详情 +- `POST /api/plugins/{name}/enable`: 启用插件 +- `POST /api/plugins/{name}/disable`: 禁用插件 + +### WebSocket API (端口 8081) + +- 实时日志推送 +- 系统状态监控 +- 插件事件通知 + +### TCP HTTP (端口 8082) + +- 高性能 HTTP 服务 +- 兼容 HTTP/1.1 协议 + +## 配置系统 + +### 配置文件位置 + +1. **全局配置**: `config.yaml` (可选) +2. **插件配置**: `store/@{作者名}/{插件名}/config.json` +3. **环境变量**: 支持 Docker 环境变量覆盖 + +### 配置优先级 + +``` +环境变量 > 全局配置文件 > 插件默认配置 +``` + +## 数据存储 + +### 数据目录结构 + +``` +data/ +├── html-render/ # 网站渲染文件 +├── web-toolkit/ # Web 工具配置 +├── plugin-storage/ # 插件持久化数据 +└── DCIM/ # 共享资源存储 +``` + +### 存储类型 + +1. **临时存储**: 内存缓存 +2. **持久化存储**: 文件系统 (data/ 目录) +3. **插件私有存储**: 每个插件独立存储空间 + +## 监控与日志 + +### 日志系统 + +- **日志级别**: DEBUG, INFO, WARNING, ERROR, CRITICAL +- **输出目标**: 控制台、文件、WebSocket +- **日志格式**: 结构化 JSON 日志 + +### 监控指标 + +- 系统资源使用率 (CPU/内存/磁盘) +- 插件运行状态 +- API 请求统计 +- 错误率和异常监控 + +## 扩展能力 + +### 自定义插件开发 + +FutureOSS 支持多种插件类型: + +1. **协议插件**: 实现网络协议 (HTTP/WebSocket/TCP) +2. **工具插件**: 提供开发工具功能 +3. **界面插件**: 扩展 Web 控制台 +4. **存储插件**: 实现数据存储后端 +5. **中间件插件**: 请求处理管道 + +### 插件间通信 + +- **事件系统**: 发布/订阅模式 +- **直接调用**: 通过插件管理器 +- **共享存储**: 通过 plugin-storage +- **消息队列**: 通过 plugin-bridge + +## 最佳实践 + +### 性能优化 + +1. **懒加载插件**: 按需加载非核心插件 +2. **连接池管理**: 数据库和网络连接复用 +3. **缓存策略**: 合理使用内存缓存 +4. **异步处理**: I/O 密集型操作异步化 + +### 安全建议 + +1. **签名验证**: 生产环境启用所有插件签名验证 +2. **权限最小化**: 插件只申请必要权限 +3. **沙箱隔离**: 不可信插件启用沙箱模式 +4. **定期更新**: 及时更新插件和安全补丁 + +### 高可用性 + +1. **健康检查**: 配置完整的健康检查端点 +2. **故障转移**: 关键插件多实例部署 +3. **监控告警**: 设置系统监控和告警 +4. **备份恢复**: 定期备份插件配置和数据 + +## 故障排除 + +### 常见问题 + +1. **插件加载失败**: 检查 manifest.json 格式和依赖 +2. **服务启动失败**: 检查端口冲突和权限 +3. **性能问题**: 监控系统资源使用情况 +4. **内存泄漏**: 检查插件资源释放 + +### 调试工具 + +1. **日志终端**: 实时查看系统日志 +2. **Web 控制台**: 可视化监控插件状态 +3. **API 接口**: 通过 REST API 获取系统信息 +4. **开发工具**: tools/ 目录下的辅助脚本 + +## 官方网站项目 + +### 项目位置 +`/root/future-oss/website/` - FutureOSS 官方宣传网站 + +### 技术栈 +- **后端**: Node.js + Express.js +- **前端**: EJS 模板引擎 + 原生 JavaScript + CSS3 +- **构建工具**: npm +- **开发工具**: VS Code 运行与调试配置 + +### 项目结构 +``` +website/ +├── 📁 public/ # 静态资源 +│ ├── css/ # 样式文件 +│ │ ├── main.css # 基础样式和变量 +│ │ ├── components.css # 组件样式 +│ │ ├── animations.css # 动画效果 +│ │ └── pages/ # 页面特定样式 +│ ├── js/ # JavaScript 文件 +│ │ ├── main.js # 主逻辑 +│ │ ├── router.js # 前端路由 +│ │ ├── animations.js # 动画控制 +│ │ └── pages/ # 页面特定脚本 +│ └── images/ # 图片资源 +├── 📁 src/ # 源代码 +│ ├── server.js # Express 服务器 +│ ├── router.js # 路由配置 +│ ├── controllers/ # 控制器 +│ │ ├── pagesController.js # 页面控制器 +│ │ └── apiController.js # API 控制器 +│ ├── middleware/ # 中间件 +│ │ └── performance.js # 性能优化中间件 +│ ├── components/ # 组件(预留) +│ ├── pages/ # 页面逻辑(预留) +│ └── styles/ # 样式源码(预留) +├── 📁 views/ # EJS 视图模板 +│ ├── layouts/ # 布局文件 +│ │ └── main.ejs # 主布局 +│ ├── pages/ # 页面模板 +│ │ ├── home.ejs # 首页 +│ │ ├── features.ejs # 特性页 +│ │ ├── architecture.ejs # 架构页 +│ │ └── plugins.ejs # 插件页 +│ └── partials/ # 局部组件 +│ ├── navbar.ejs # 导航栏 +│ └── footer.ejs # 页脚 +├── 📄 package.json # npm 配置 +├── 📄 package-lock.json # 依赖锁定 +└── 📄 server.log # 服务器日志 +``` + +### 核心功能 + +#### 1. 多页面网站 +- **首页** (`/`): 项目介绍和快速开始 +- **特性页** (`/features`): 核心功能展示 +- **架构页** (`/architecture`): 技术架构说明 +- **插件页** (`/plugins`): 插件生态系统 + +#### 2. 性能优化特性 +- **响应时间监控**: X-Response-Time 头部 +- **缓存控制**: 静态资源长期缓存 +- **压缩支持**: Gzip 和预压缩 +- **安全头**: CSP、XSS 保护等 +- **内存监控**: 实时内存使用监控 + +#### 3. 用户体验增强 +- **加载动画**: 页面加载旋转指示器 +- **页面切换动画**: 平滑的页面过渡效果 +- **图片懒加载**: 延迟加载非视口图片 +- **骨架屏**: 内容加载占位符 +- **响应式设计**: 完整的移动端适配 + +#### 4. API 接口 +- `GET /api/health`: 健康检查 +- `GET /api/metrics`: 性能指标 +- `GET /api/info`: 服务器信息 +- `GET /api/stress-test`: 压力测试(仅开发环境) + +### 技术特点 + +#### 服务器特性 +- **端口自动切换**: 8080被占用时自动使用8081、8082等 +- **优雅关闭**: 支持 SIGTERM/SIGINT 信号优雅关闭 +- **错误处理**: 完善的错误处理和404页面 +- **中间件栈**: 完整的性能优化中间件 + +#### 前端特性 +- **组件化设计**: 导航栏、页脚等独立组件 +- **前端路由**: 支持无刷新页面切换 +- **CSS 变量**: 统一的主题变量系统 +- **动画系统**: 丰富的CSS动画和过渡效果 + +#### 开发工具 +- **VS Code 调试配置**: 支持直接运行与调试 +- **热重载支持**: nodemon 开发模式 +- **性能监控**: 实时性能指标输出 +- **日志系统**: 结构化服务器日志 + +### 部署与运行 + +#### 开发模式 +```bash +cd /root/future-oss/website +npm install +npm start +# 访问 http://localhost:8080 (或自动切换的端口) +``` + +#### 生产部署 +```bash +# 使用 PM2 进程管理 +pm2 start src/server.js --name "futureoss-website" + +# 使用 Docker +docker build -t futureoss-website . +docker run -p 8080:8080 futureoss-website +``` + +#### VS Code 调试 +1. 打开运行与调试面板 (Ctrl+Shift+D) +2. 选择 "FutureOSS 网站: 启动Node.js服务器" +3. 按 F5 启动调试 + +### 性能优化措施 + +#### 已实施的优化 +1. **响应头优化**: 缓存控制、安全头、压缩头 +2. **静态资源优化**: 长期缓存、预压缩支持 +3. **代码分割**: 按页面加载CSS和JS +4. **图片优化**: 懒加载、占位符、响应式图片 +5. **动画优化**: 减少重绘、will-change提示 + +#### 监控指标 +- **响应时间**: X-Response-Time 头部 +- **内存使用**: X-Memory-Usage 头部(开发环境) +- **慢响应日志**: 超过1秒的请求记录 +- **错误率**: 500错误监控和记录 + +### 已知问题和解决方案 + +#### 1. ERR_HTTP_HEADERS_SENT 错误 +- **问题**: 在响应已发送后设置响应头 +- **原因**: `responseTime` 中间件在 `finish` 事件中设置头部 +- **解决方案**: 使用 `headers` 事件或重写 `end` 方法 +- **修复代码**: + ```javascript + // 错误写法(在 finish 事件中): + res.on('finish', () => { + res.setHeader('X-Response-Time', `${duration}ms`); // 错误! + }); + + // 正确写法(重写 end 方法): + const originalEnd = res.end; + res.end = function(...args) { + if (!res.headersSent) { + res.setHeader('X-Response-Time', `${duration}ms`); + } + return originalEnd.apply(this, args); + }; + ``` + +#### 2. CSS 加载问题 +- **问题**: 页面只显示纯文本,CSS未加载 +- **原因**: EJS 布局系统未正确配置 +- **解决方案**: 安装并配置 `express-ejs-layouts` +- **修复步骤**: + 1. `npm install express-ejs-layouts` + 2. 在 server.js 中添加中间件 + 3. 设置默认布局 `app.set('layout', 'layouts/main')` + +#### 3. 加载性能问题 +- **问题**: 页面加载慢,无加载反馈 +- **解决方案**: 添加加载动画和性能优化 +- **实施措施**: + 1. 添加页面加载旋转动画 + 2. 实现图片懒加载 + 3. 添加骨架屏占位符 + 4. 优化静态资源缓存 + +### 扩展和定制 + +#### 添加新页面 +1. 在 `views/pages/` 创建新的 `.ejs` 文件 +2. 在 `controllers/pagesController.js` 添加渲染函数 +3. 在 `src/router.js` 添加路由 +4. 在 `public/css/pages/` 添加页面特定CSS +5. 在 `public/js/pages/` 添加页面特定JS + +#### 添加新组件 +1. 在 `views/partials/` 创建组件 `.ejs` 文件 +2. 在 `public/css/components.css` 添加组件样式 +3. 在布局或页面中使用 `<%- include('partials/组件名') %>` + +#### 主题定制 +通过修改 CSS 变量实现主题切换: +```css +:root { + --primary-color: #1e6fbb; /* 主色调 */ + --text-primary: #333333; /* 主要文字 */ + --bg-primary: #ffffff; /* 背景色 */ + /* 更多变量... */ +} +``` + +### 维护指南 + +#### 日常维护 +1. **日志监控**: 定期检查 `server.log` 文件 +2. **性能监控**: 关注慢响应日志 +3. **错误处理**: 及时修复报告的错误 +4. **依赖更新**: 定期更新 npm 包 + +#### 故障排除 +1. **服务器无法启动**: 检查端口占用,查看日志 +2. **页面样式异常**: 检查CSS文件路径和网络请求 +3. **API 无响应**: 检查路由配置和控制器 +4. **性能下降**: 使用 `/api/metrics` 接口诊断 + +#### 备份策略 +1. **代码备份**: Git 版本控制 +2. **配置备份**: 备份 `package.json` 和服务器配置 +3. **数据备份**: 备份用户上传内容(如有) +4. **日志归档**: 定期归档服务器日志 + +## 未来发展方向 + +### 短期规划 + +1. 插件市场功能完善 +2. 更多官方插件开发 +3. 性能优化和稳定性提升 +4. 官方网站功能增强 + +### 长期愿景 + +1. 跨语言插件支持 +2. 云原生集成 +3. 企业级功能扩展 +4. 生态系统建设 + +## 注意事项 + +1. **生产部署**: 建议使用 Docker 容器化部署 +2. **数据备份**: 定期备份 data/ 目录重要数据 +3. **安全更新**: 关注安全公告并及时更新 +4. **社区支持**: 通过 Gitee Issues 获取帮助 +5. **网站维护**: 定期更新官方网站内容和功能 + +--- + +*本文件专为 AI 助手设计,提供 FutureOSS 项目的全面技术概述,帮助 AI 理解项目架构、功能和使用方式。更新日期:2026年4月19日* \ No newline at end of file diff --git a/store/@{FutureOSS}/plugin-loader/PL_EXAMPLE.md b/store/@{FutureOSS}/plugin-loader/PL_EXAMPLE.md new file mode 100644 index 0000000..ffeb653 --- /dev/null +++ b/store/@{FutureOSS}/plugin-loader/PL_EXAMPLE.md @@ -0,0 +1,172 @@ +# PL 注入机制使用说明 + +## 概述 + +PL 注入机制允许插件通过 `PL/` 文件夹向插件加载器注册自定义功能。插件加载器在启动时会自动扫描所有插件,检查其 `manifest.json` 中是否声明了 `pl_injection` 配置项。 + +## 使用步骤 + +### 1. 在 manifest.json 中声明 pl_injection + +在插件的 `manifest.json` 的 `config.args` 中添加 `"pl_injection": true`: + +```json +{ + "metadata": { + "name": "my-plugin", + "version": "1.0.0", + "author": "MyName", + "description": "我的插件", + "type": "utility" + }, + "config": { + "enabled": true, + "args": { + "pl_injection": true + } + }, + "dependencies": [], + "permissions": [] +} +``` + +### 2. 创建 PL/ 文件夹和 PL/main.py + +在插件目录下创建 `PL/` 文件夹,并在其中创建 `main.py`: + +``` +store/@{MyName}/my-plugin/ +├── manifest.json # 声明 pl_injection: true +├── main.py # 插件主逻辑 +├── PL/ # PL 注入文件夹 +│ └── main.py # 注入逻辑(必须包含 register() 函数) +└── README.md +``` + +### 3. 实现 PL/main.py + +`PL/main.py` 必须导出一个 `register(injector)` 函数,接收一个 `PLInjector` 实例: + +```python +# PL/main.py +"""PL 注入 - 向插件加载器注册功能""" + +def register(injector): + """向插件加载器注册功能 + + Args: + injector: PLInjector 实例,提供以下注册方法: + - register_function(name, func, description="") + - register_route(method, path, handler) + - register_event_handler(event_name, handler) + """ + + # 示例 1: 注册一个普通功能 + def my_helper(): + print("这是从 PL 注入的功能") + + injector.register_function("my_helper", my_helper, "一个辅助功能") + + # 示例 2: 注册 HTTP 路由 + def hello_handler(request): + return {"message": "Hello from PL injection!"} + + injector.register_route("GET", "/pl/hello", hello_handler) + + # 示例 3: 注册事件处理器 + def on_plugin_started(plugin_name): + print(f"插件 {plugin_name} 已启动") + + injector.register_event_handler("plugin.started", on_plugin_started) +``` + +### 4. 引用其他文件 + +`PL/main.py` 可以引用 `PL/` 文件夹下的其他 Python 文件: + +``` +store/@{MyName}/my-plugin/PL/ +├── main.py # 入口,包含 register() 函数 +├── helpers.py # 辅助函数(被 main.py 引用) +└── routes.py # 路由定义(被 main.py 引用) +``` + +```python +# PL/main.py +from .helpers import format_response +from .routes import register_routes + +def register(injector): + def my_handler(): + return format_response("Hello") + injector.register_function("my_handler", my_handler) + register_routes(injector) +``` + +## 行为说明 + +| 场景 | 结果 | +|------|------| +| manifest.json 中 `pl_injection: true` + 存在 `PL/main.py` | ✅ 正常加载,执行注入 | +| manifest.json 中 `pl_injection: true` + 缺少 `PL/` 文件夹 | ❌ 警告并拒绝加载该插件 | +| manifest.json 中 `pl_injection: true` + 存在 `PL/` 但缺少 `main.py` | ❌ 警告并拒绝加载该插件 | +| manifest.json 中未声明 `pl_injection` | ✅ 正常加载,跳过 PL 检查 | +| manifest.json 中 `pl_injection: false` | ✅ 正常加载,跳过 PL 检查 | + +## 安全限制 + +PL 注入机制实施了多层安全限制,防止恶意代码注入: + +### 1. 文件类型限制 +- PL 文件夹中禁止包含 `.sh`、`.bat`、`.exe`、`.dll`、`.so`、`.dylib`、`.bin` 等可执行/二进制文件 +- 违反则拒绝加载该插件 + +### 2. 静态源码安全检查 +PL/main.py 源码在编译前会进行静态扫描,禁止以下操作: +- 导入系统级模块(`os`、`sys`、`subprocess`、`shutil`、`socket`、`ctypes`、`cffi`、`multiprocessing`、`threading`) +- 使用 `__import__`、`exec`、`eval`、`compile` +- 直接操作文件(`open`) +- 访问 `__builtins__` + +### 3. 沙箱执行环境 +PL/main.py 在受限的沙箱中执行,仅提供安全的 builtins: +- 基础类型:`dict`、`list`、`str`、`int`、`float`、`bool`、`tuple`、`set` +- 安全函数:`len`、`range`、`enumerate`、`zip`、`map`、`filter`、`sorted` 等 +- 异常类型:`Exception`、`ValueError`、`TypeError`、`KeyError`、`IndexError` + +### 4. 参数校验 +| 校验项 | 限制 | +|--------|------| +| 功能名称 | 仅允许字母、数字、下划线、冒号、斜杠、连字符、点,最长 128 字符 | +| 路由路径 | 必须以 `/` 开头,禁止 `..`、`//`、`/\.`、`~`、`%`,最长 256 字符 | +| HTTP 方法 | 仅允许 GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS | +| 事件名称 | 字母开头,仅允许字母、数字、点、下划线,最长 128 字符 | +| 功能描述 | 最长 256 字符 | + +### 5. 数量限制 +| 限制项 | 上限 | +|--------|------| +| 每个插件最多注册的功能数 | 50 | +| 每个功能名称最多被注册次数 | 10 | + +### 6. 异常安全 +- 所有注册的函数会被自动包装,执行时抛出异常不会影响主流程 +- 异常会被记录到日志,函数返回 `None` + +### 7. 调用者溯源 +- 通过栈帧回溯自动识别调用者插件名 +- 防止其他插件冒充注册 + +## 注入器 API + +`PLInjector` 实例提供以下方法供 `PL/main.py` 调用: + +| 方法 | 说明 | +|------|------| +| `register_function(name, func, description="")` | 注册一个注入功能 | +| `register_route(method, path, handler)` | 注册 HTTP 路由 | +| `register_event_handler(event_name, handler)` | 注册事件处理器 | +| `get_injected_functions(name=None)` | 获取已注册的注入功能 | +| `get_injection_info(plugin_name=None)` | 获取注入信息 | +| `has_injection(plugin_name)` | 检查插件是否有 PL 注入 | +| `get_registry_info()` | 获取注册表完整信息(用于监控) | \ No newline at end of file diff --git a/store/@{FutureOSS}/plugin-loader/main.py b/store/@{FutureOSS}/plugin-loader/main.py index 6653504..18c9a33 100644 --- a/store/@{FutureOSS}/plugin-loader/main.py +++ b/store/@{FutureOSS}/plugin-loader/main.py @@ -1,51 +1,37 @@ -"""插件加载器插件 - 支持能力扫描和扩展""" +"""插件加载器插件 - 支持能力扫描和扩展 + PL 注入机制""" import sys import json +import re +import types +import traceback import importlib.util from pathlib import Path -from typing import Any, Optional +from typing import Any, Optional, Callable from oss.plugin.types import Plugin, register_plugin_type from oss.plugin.capabilities import scan_capabilities -# ===== 彩色日志 ===== class Log: """智能彩色日志""" _TTY = sys.stdout.isatty() - _C = { - "reset": "\033[0m", - "white": "\033[0;37m", - "yellow": "\033[1;33m", - "blue": "\033[1;34m", - "red": "\033[1;31m", - } + _C = {"reset": "\033[0m", "white": "\033[0;37m", "yellow": "\033[1;33m", "blue": "\033[1;34m", "red": "\033[1;31m"} @classmethod def c(cls, text: str, color: str) -> str: - if not cls._TTY: - return text + if not cls._TTY: return text return f"{cls._C.get(color, '')}{text}{cls._C['reset']}" @classmethod - def info(cls, tag: str, msg: str): - print(f"{cls.c(f'[{tag}]', 'white')} {cls.c(msg, 'white')}") - + def info(cls, tag: str, msg: str): print(f"{cls.c(f'[{tag}]', 'white')} {cls.c(msg, 'white')}") @classmethod - def warn(cls, tag: str, msg: str): - print(f"{cls.c(f'[{tag}]', 'yellow')} {cls.c('⚠', 'yellow')} {cls.c(msg, 'yellow')}") - + def warn(cls, tag: str, msg: str): print(f"{cls.c(f'[{tag}]', 'yellow')} {cls.c('⚠', 'yellow')} {cls.c(msg, 'yellow')}") @classmethod - def tip(cls, tag: str, msg: str): - print(f"{cls.c(f'[{tag}]', 'blue')} {cls.c('ℹ', 'blue')} {cls.c(msg, 'blue')}") - + def tip(cls, tag: str, msg: str): print(f"{cls.c(f'[{tag}]', 'blue')} {cls.c('ℹ', 'blue')} {cls.c(msg, 'blue')}") @classmethod - def error(cls, tag: str, msg: str): - print(f"{cls.c(f'[{tag}]', 'red')} {cls.c('✗', 'red')} {cls.c(msg, 'red')}") - + def error(cls, tag: str, msg: str): print(f"{cls.c(f'[{tag}]', 'red')} {cls.c('✗', 'red')} {cls.c(msg, 'red')}") @classmethod - def ok(cls, tag: str, msg: str): - print(f"{cls.c(f'[{tag}]', 'white')} {cls.c(msg, 'white')}") + def ok(cls, tag: str, msg: str): print(f"{cls.c(f'[{tag}]', 'white')} {cls.c(msg, 'white')}") class PluginInfo: @@ -61,6 +47,7 @@ class PluginInfo: self.lifecycle: Any = None self.capabilities: set[str] = set() self.dependencies: list[str] = [] + self.pl_injected: bool = False class PermissionError(Exception): @@ -70,253 +57,382 @@ class PermissionError(Exception): class PluginProxy: """插件代理 - 防止越级访问""" - - def __init__(self, plugin_name: str, plugin_instance: Any, allowed_plugins: list[str], all_plugins: dict[str, dict[str, Any]]): + def __init__(self, plugin_name: str, plugin_instance: Any, allowed_plugins: list[str], all_plugins: dict): self._plugin_name = plugin_name self._plugin_instance = plugin_instance self._allowed_plugins = set(allowed_plugins) self._all_plugins = all_plugins def get_plugin(self, name: str) -> Any: - """获取其他插件实例(带权限检查)""" if name not in self._allowed_plugins and "*" not in self._allowed_plugins: raise PermissionError(f"插件 '{self._plugin_name}' 无权访问插件 '{name}'") - if name not in self._all_plugins: - return None + if name not in self._all_plugins: return None return self._all_plugins[name]["instance"] def list_plugins(self) -> list[str]: - """列出有权限访问的插件""" - if "*" in self._allowed_plugins: - return list(self._all_plugins.keys()) - return [name for name in self._allowed_plugins if name in self._all_plugins] + if "*" in self._allowed_plugins: return list(self._all_plugins.keys()) + return [n for n in self._allowed_plugins if n in self._all_plugins] - def get_capability(self, capability: str) -> Any: - """获取能力(带权限检查)""" - # 能力访问不需要额外权限,能力注册表会自动处理 - return None - - def __getattr__(self, name: str): - """代理其他属性到插件实例""" - return getattr(self._plugin_instance, name) + def get_capability(self, capability: str) -> Any: return None + def __getattr__(self, name: str): return getattr(self._plugin_instance, name) class CapabilityRegistry: """能力注册表""" - def __init__(self, permission_check: bool = True): - self.providers: dict[str, dict[str, Any]] = {} - self.consumers: dict[str, list[str]] = {} + self.providers: dict = {} + self.consumers: dict = {} self.permission_check = permission_check def register_provider(self, capability: str, plugin_name: str, instance: Any): - """注册能力提供者""" - self.providers[capability] = { - "plugin": plugin_name, - "instance": instance, - } - if capability not in self.consumers: - self.consumers[capability] = [] + self.providers[capability] = {"plugin": plugin_name, "instance": instance} + if capability not in self.consumers: self.consumers[capability] = [] def register_consumer(self, capability: str, plugin_name: str): - """注册能力消费者""" - if capability not in self.consumers: - self.consumers[capability] = [] - if plugin_name not in self.consumers[capability]: - self.consumers[capability].append(plugin_name) - - def get_provider(self, capability: str, requester: str = "", allowed_plugins: list[str] = None) -> Optional[Any]: - """获取能力提供者实例(带权限检查)""" - if capability not in self.providers: - return None + if capability not in self.consumers: self.consumers[capability] = [] + if plugin_name not in self.consumers[capability]: self.consumers[capability].append(plugin_name) + def get_provider(self, capability: str, requester: str = "", allowed_plugins: list = None) -> Optional[Any]: + if capability not in self.providers: return None if self.permission_check and allowed_plugins is not None: - provider_name = self.providers[capability]["plugin"] - if provider_name != requester and provider_name not in allowed_plugins and "*" not in allowed_plugins: + pn = self.providers[capability]["plugin"] + if pn != requester and pn not in allowed_plugins and "*" not in allowed_plugins: raise PermissionError(f"插件 '{requester}' 无权使用能力 '{capability}'") - return self.providers[capability]["instance"] - def has_capability(self, capability: str) -> bool: - """检查是否有某个能力""" - return capability in self.providers + def has_capability(self, capability: str) -> bool: return capability in self.providers + def get_consumers(self, capability: str) -> list: return self.consumers.get(capability, []) - def get_consumers(self, capability: str) -> list[str]: - """获取能力消费者列表""" - return self.consumers.get(capability, []) + +class PLValidationError(Exception): + """PL 校验错误""" + pass + + +class PLInjector: + """PL 注入管理器 - 带完整安全限制""" + + MAX_FUNCTIONS_PER_PLUGIN = 50 + MAX_REGISTRATIONS_PER_NAME = 10 + MAX_NAME_LENGTH = 128 + MAX_DESCRIPTION_LENGTH = 256 + + _FUNCTION_NAME_RE = re.compile(r'^[a-zA-Z0-9_:/\-.]+$') + _EVENT_NAME_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9_.]+$') + _ROUTE_PATH_RE = re.compile(r'^/[a-zA-Z0-9_\-/.]+$') + _FORBIDDEN_ROUTE_PATTERNS = [r'\.\.', r'//', r'/\.', r'~', r'\%'] + + def __init__(self, plugin_manager: 'PluginManager'): + self._plugin_manager = plugin_manager + self._injections: dict = {} + self._injection_registry: dict = {} + self._plugin_function_count: dict = {} + + def check_and_load_pl(self, plugin_dir: Path, plugin_name: str) -> bool: + """检查并加载 PL 文件夹,返回 True 表示成功""" + pl_dir = plugin_dir / "PL" + if not pl_dir.exists() or not pl_dir.is_dir(): + Log.warn("plugin-loader", f"插件 '{plugin_name}' 声明了 pl_injection,但缺少 PL/ 文件夹,拒绝加载") + return False + + pl_main = pl_dir / "main.py" + if not pl_main.exists(): + Log.warn("plugin-loader", f"插件 '{plugin_name}' 的 PL/ 文件夹中缺少 main.py,拒绝加载") + return False + + # 禁止危险文件类型 + forbidden_ext = {'.sh', '.bat', '.exe', '.dll', '.so', '.dylib', '.bin'} + for f in pl_dir.rglob('*'): + if f.suffix.lower() in forbidden_ext: + Log.error("plugin-loader", f"插件 '{plugin_name}' 的 PL/ 文件夹包含危险文件: {f.name},拒绝加载") + return False + + try: + # 受限沙箱 + safe_builtins = { + 'True': True, 'False': False, 'None': None, + 'dict': dict, 'list': list, 'str': str, 'int': int, + 'float': float, 'bool': bool, 'tuple': tuple, 'set': set, + 'len': len, 'range': range, 'enumerate': enumerate, + 'zip': zip, 'map': map, 'filter': filter, + 'sorted': sorted, 'reversed': reversed, + 'min': min, 'max': max, 'sum': sum, 'abs': abs, + 'round': round, 'isinstance': isinstance, 'issubclass': issubclass, + 'type': type, 'id': id, 'hash': hash, 'repr': repr, + 'print': print, 'object': object, 'property': property, + 'staticmethod': staticmethod, 'classmethod': classmethod, + 'super': super, 'iter': iter, 'next': next, + 'any': any, 'all': all, 'callable': callable, + 'hasattr': hasattr, 'getattr': getattr, 'setattr': setattr, + 'ValueError': ValueError, 'TypeError': TypeError, + 'KeyError': KeyError, 'IndexError': IndexError, + 'Exception': Exception, 'BaseException': BaseException, + } + safe_globals = { + '__builtins__': safe_builtins, + '__name__': f'plugin.{plugin_name}.PL', + '__package__': f'plugin.{plugin_name}.PL', + '__file__': str(pl_main), + } + + with open(pl_main, 'r', encoding='utf-8') as f: + source = f.read() + + # 静态源码安全检查 + self._static_source_check(source, str(pl_main)) + + code = compile(source, str(pl_main), 'exec') + exec(code, safe_globals) + + register_func = safe_globals.get('register') + if register_func and callable(register_func): + register_func(self) + Log.ok("plugin-loader", f"插件 '{plugin_name}' PL 注入成功") + else: + Log.warn("plugin-loader", f"插件 '{plugin_name}' 的 PL/main.py 缺少 register() 函数,但仍允许加载") + + self._injections[plugin_name] = {"dir": str(pl_dir)} + return True + + except PLValidationError as e: + Log.error("plugin-loader", f"插件 '{plugin_name}' PL 安全检查失败: {e}") + return False + except SyntaxError as e: + Log.error("plugin-loader", f"插件 '{plugin_name}' PL/main.py 语法错误: {e}") + return False + except Exception as e: + Log.error("plugin-loader", f"加载插件 '{plugin_name}' 的 PL 失败: {e}") + return False + + def _static_source_check(self, source: str, file_path: str): + """静态源码安全检查""" + forbidden = [ + (r'^\s*import\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)', '禁止导入系统级模块'), + (r'^\s*from\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)\s+import', '禁止导入系统级模块'), + (r'__import__\s*\(', '禁止使用 __import__'), + (r'exec\s*\(', '禁止使用 exec'), + (r'eval\s*\(', '禁止使用 eval'), + (r'compile\s*\(', '禁止使用 compile'), + (r'open\s*\(', '禁止直接操作文件'), + (r'__builtins__', '禁止访问 __builtins__'), + ] + for line_num, line in enumerate(source.split('\n'), 1): + stripped = line.strip() + if not stripped or stripped.startswith('#'): continue + for pattern, msg in forbidden: + if re.search(pattern, stripped): + raise PLValidationError(f"{file_path}:{line_num} - {msg}: '{stripped}'") + + def _validate_function_name(self, name: str) -> bool: + if not name or not isinstance(name, str): return False + if len(name) > self.MAX_NAME_LENGTH: return False + return bool(self._FUNCTION_NAME_RE.match(name)) + + def _validate_route_path(self, path: str) -> bool: + if not path or not isinstance(path, str): return False + if len(path) > 256: return False + if not self._ROUTE_PATH_RE.match(path): return False + for p in self._FORBIDDEN_ROUTE_PATTERNS: + if re.search(p, path): return False + return True + + def _validate_event_name(self, event_name: str) -> bool: + if not event_name or not isinstance(event_name, str): return False + if len(event_name) > self.MAX_NAME_LENGTH: return False + return bool(self._EVENT_NAME_RE.match(event_name)) + + def _check_plugin_limit(self, plugin_name: str) -> bool: + count = self._plugin_function_count.get(plugin_name, 0) + if count >= self.MAX_FUNCTIONS_PER_PLUGIN: + Log.warn("plugin-loader", f"插件 '{plugin_name}' 注册功能数已达上限 ({self.MAX_FUNCTIONS_PER_PLUGIN})") + return False + return True + + def _check_name_limit(self, name: str) -> bool: + registrations = self._injection_registry.get(name, []) + if len(registrations) >= self.MAX_REGISTRATIONS_PER_NAME: + Log.warn("plugin-loader", f"功能名称 '{name}' 注册次数已达上限 ({self.MAX_REGISTRATIONS_PER_NAME})") + return False + return True + + def _wrap_function(self, func: Callable, plugin_name: str, name: str) -> Callable: + """包装函数,异常安全""" + def _safe_wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + Log.error("plugin-loader", f"PL 注入功能 '{name}' (来自 {plugin_name}) 执行异常: {e}") + return None + return _safe_wrapper + + def _get_caller_plugin_name(self) -> Optional[str]: + """通过栈帧回溯获取调用者插件名""" + stack = traceback.extract_stack() + for frame in stack: + filename = frame.filename + if '/PL/' in filename and 'main.py' in filename: + parts = Path(filename).parts + for i, part in enumerate(parts): + if part == 'PL': + return parts[i - 1] if i > 0 else None + return None + + def register_function(self, name: str, func: Callable, description: str = ""): + """注册注入功能 - 带参数校验和权限限制""" + if not self._validate_function_name(name): + Log.error("plugin-loader", f"PL 注入功能名称非法: '{name}'") + return + if not callable(func): + Log.error("plugin-loader", f"PL 注入功能 '{name}' 不是可调用对象") + return + if description and len(description) > self.MAX_DESCRIPTION_LENGTH: + description = description[:self.MAX_DESCRIPTION_LENGTH] + + plugin_name = self._get_caller_plugin_name() or "unknown" + + if not self._check_plugin_limit(plugin_name): return + if not self._check_name_limit(name): return + + wrapped_func = self._wrap_function(func, plugin_name, name) + + if name not in self._injection_registry: + self._injection_registry[name] = [] + self._injection_registry[name].append({ + "func": wrapped_func, "plugin": plugin_name, "description": description, + }) + self._plugin_function_count[plugin_name] = self._plugin_function_count.get(plugin_name, 0) + 1 + Log.tip("plugin-loader", f"PL 注入功能已注册: '{name}' (来自 {plugin_name})") + + def register_route(self, method: str, path: str, handler: Callable): + """注册 HTTP 路由 - 带路径安全校验""" + valid_methods = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'} + method_upper = method.upper() + if method_upper not in valid_methods: + Log.error("plugin-loader", f"PL 注入路由方法非法: '{method}'") + return + if not self._validate_route_path(path): + Log.error("plugin-loader", f"PL 注入路由路径非法: '{path}'") + return + self.register_function(f"{method_upper}:{path}", handler, f"路由 {method_upper} {path}") + + def register_event_handler(self, event_name: str, handler: Callable): + """注册事件处理器 - 带名称校验""" + if not self._validate_event_name(event_name): + Log.error("plugin-loader", f"PL 注入事件名称非法: '{event_name}'") + return + self.register_function(f"event:{event_name}", handler, f"事件 {event_name}") + + def get_injected_functions(self, name: str = None) -> list[Callable]: + if name: return [e["func"] for e in self._injection_registry.get(name, [])] + return [f for es in self._injection_registry.values() for f in [e["func"] for e in es]] + + def get_injection_info(self, plugin_name: str = None) -> dict: + if plugin_name: return self._injections.get(plugin_name, {}) + return dict(self._injections) + + def has_injection(self, plugin_name: str) -> bool: + return plugin_name in self._injections + + def get_registry_info(self) -> dict: + info = {} + for name, entries in self._injection_registry.items(): + info[name] = { + "count": len(entries), + "plugins": [e["plugin"] for e in entries], + "descriptions": [e["description"] for e in entries], + } + return info class PluginManager: """插件管理器""" def __init__(self, permission_check: bool = True): - self.plugins: dict[str, dict[str, Any]] = {} - self.lifecycle_plugin: Optional[Any] = None - self._dependency_plugin: Optional[Any] = None # dependency 插件引用 - self._signature_verifier: Optional[Any] = None # signature-verifier 插件引用 + self.plugins: dict = {} + self.lifecycle_plugin = None + self._dependency_plugin = None + self._signature_verifier = None self.capability_registry = CapabilityRegistry(permission_check=permission_check) self.permission_check = permission_check - self.enforce_signature = True # 是否强制验证官方插件签名 + self.enforce_signature = True + self.pl_injector = PLInjector(self) - def set_signature_verifier(self, verifier: Any): - """设置签名验证器""" - self._signature_verifier = verifier + def set_signature_verifier(self, verifier): self._signature_verifier = verifier + def set_lifecycle(self, lifecycle_plugin): self.lifecycle_plugin = lifecycle_plugin - def set_lifecycle(self, lifecycle_plugin: Any): - """设置生命周期插件""" - self.lifecycle_plugin = lifecycle_plugin - - def _load_manifest(self, plugin_dir: Path) -> dict[str, Any]: - """加载 manifest.json""" - manifest_file = plugin_dir / "manifest.json" - if not manifest_file.exists(): - return {} - with open(manifest_file, "r", encoding="utf-8") as f: - return json.load(f) + def _load_manifest(self, plugin_dir: Path) -> dict: + mf = plugin_dir / "manifest.json" + if not mf.exists(): return {} + with open(mf, "r", encoding="utf-8") as f: return json.load(f) def _load_readme(self, plugin_dir: Path) -> str: - """加载 README.md""" - readme_file = plugin_dir / "README.md" - if not readme_file.exists(): - return "" - with open(readme_file, "r", encoding="utf-8") as f: - return f.read() + rf = plugin_dir / "README.md" + if not rf.exists(): return "" + with open(rf, "r", encoding="utf-8") as f: return f.read() - def _load_config(self, plugin_dir: Path) -> dict[str, Any]: - """加载 Python 配置文件(带安全措施)""" - config_file = plugin_dir / "config.py" - if not config_file.exists(): - return {} + def _load_config(self, plugin_dir: Path) -> dict: + cf = plugin_dir / "config.py" + if not cf.exists(): return {} + with open(cf, "r", encoding="utf-8") as f: content = f.read() + for p in ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess']: + if p in content: Log.warn("plugin-loader", f"{cf} 包含危险代码: {p}"); return {} + sg = {"__builtins__": {"True": True, "False": False, "None": None, "dict": dict, "list": list, "str": str, "int": int, "float": float, "bool": bool}} + lv = {} + try: code = compile(content, str(cf), "exec"); exec(code, sg, lv) + except Exception as e: Log.error("plugin-loader", f"配置文件解析失败: {e}"); return {} + return {k: v for k, v in lv.items() if not k.startswith("_") and not callable(v)} - # 读取并验证文件内容 - with open(config_file, "r", encoding="utf-8") as f: - content = f.read() - - # 安全检查:禁止危险操作 - dangerous_patterns = ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess'] - for pattern in dangerous_patterns: - if pattern in content: - Log.warn("plugin-loader", f"{config_file} 包含危险代码: {pattern}") - return {} - - safe_globals = { - "__builtins__": { - "True": True, - "False": False, - "None": None, - "dict": dict, - "list": list, - "str": str, - "int": int, - "float": float, - "bool": bool, - } - } - local_vars = {} - - try: - code = compile(content, str(config_file), "exec") - exec(code, safe_globals, local_vars) - except Exception as e: - Log.error("plugin-loader", f"配置文件解析失败: {e}") - return {} - - return { - k: v for k, v in local_vars.items() - if not k.startswith("_") and not callable(v) - } - - def _load_extensions(self, plugin_dir: Path) -> dict[str, Any]: - """加载扩展语法(Python 文件)""" - ext_file = plugin_dir / "extensions.py" - if not ext_file.exists(): - return {} - - # 读取并验证文件内容 - with open(ext_file, "r", encoding="utf-8") as f: - content = f.read() - - # 安全检查:禁止危险操作 - dangerous_patterns = ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess'] - for pattern in dangerous_patterns: - if pattern in content: - Log.warn("plugin-loader", f"{ext_file} 包含危险代码: {pattern}") - return {} - - safe_globals = { - "__builtins__": { - "True": True, - "False": False, - "None": None, - "dict": dict, - "list": list, - "str": str, - "int": int, - "float": float, - "bool": bool, - } - } - local_vars = {} - - try: - code = compile(content, str(ext_file), "exec") - exec(code, safe_globals, local_vars) - except Exception as e: - Log.error("plugin-loader", f"扩展文件解析失败: {e}") - return {} - - return { - k: v for k, v in local_vars.items() - if not k.startswith("_") and not callable(v) - } + def _load_extensions(self, plugin_dir: Path) -> dict: + ef = plugin_dir / "extensions.py" + if not ef.exists(): return {} + with open(ef, "r", encoding="utf-8") as f: content = f.read() + for p in ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess']: + if p in content: Log.warn("plugin-loader", f"{ef} 包含危险代码: {p}"); return {} + sg = {"__builtins__": {"True": True, "False": False, "None": None, "dict": dict, "list": list, "str": str, "int": int, "float": float, "bool": bool}} + lv = {} + try: code = compile(content, str(ef), "exec"); exec(code, sg, lv) + except Exception as e: Log.error("plugin-loader", f"扩展文件解析失败: {e}"); return {} + return {k: v for k, v in lv.items() if not k.startswith("_") and not callable(v)} def load(self, plugin_dir: Path, use_sandbox: bool = True) -> Optional[Any]: """加载单个插件""" main_file = plugin_dir / "main.py" - if not main_file.exists(): - return None + if not main_file.exists(): return None manifest = self._load_manifest(plugin_dir) readme = self._load_readme(plugin_dir) config = self._load_config(plugin_dir) extensions = self._load_extensions(plugin_dir) - - # 自动扫描能力 capabilities = scan_capabilities(plugin_dir) - - plugin_name = plugin_dir.name - - # 清理插件名(去掉 } 后缀) plugin_name = plugin_dir.name.rstrip("}") - # 解析权限 + # PL 注入检查 + pl_injection = manifest.get("config", {}).get("args", {}).get("pl_injection", False) + if pl_injection: + Log.tip("plugin-loader", f"插件 '{plugin_name}' 声明了 pl_injection,正在检查 PL/ 文件夹...") + if not self.pl_injector.check_and_load_pl(plugin_dir, plugin_name): + Log.error("plugin-loader", f"插件 '{plugin_name}' 因 PL 注入检查失败被拒绝加载") + return None + Log.ok("plugin-loader", f"插件 '{plugin_name}' PL 注入检查通过") + permissions = manifest.get("permissions", []) - # 沙箱加载 if use_sandbox: from oss.plugin.loader import PluginLoader as FrameworkLoader - framework_loader = FrameworkLoader(enable_sandbox=True) - result = framework_loader.load_sandbox_plugin(plugin_dir) - if not result: - return None - module = result["module"] - instance = result["instance"] + fl = FrameworkLoader(enable_sandbox=True) + result = fl.load_sandbox_plugin(plugin_dir) + if not result: return None + module, instance = result["module"], result["instance"] else: - spec = importlib.util.spec_from_file_location( - f"plugin.{plugin_name}", str(main_file) - ) + spec = importlib.util.spec_from_file_location(f"plugin.{plugin_name}", str(main_file)) module = importlib.util.module_from_spec(spec) module.__package__ = f"plugin.{plugin_name}" - module.__path__ = [str(plugin_dir)] # 启用相对导入子模块 + module.__path__ = [str(plugin_dir)] sys.modules[spec.name] = module spec.loader.exec_module(module) - - if not hasattr(module, "New"): - return None - + if not hasattr(module, "New"): return None instance = module.New() - # 创建代理包装器 if self.permission_check and permissions: instance = PluginProxy(plugin_name, instance, permissions, self.plugins) @@ -331,315 +447,163 @@ class PluginManager: info.extensions = extensions info.capabilities = capabilities info.dependencies = manifest.get("dependencies", []) + info.pl_injected = pl_injection - # 注册能力 for cap in capabilities: self.capability_registry.register_provider(cap, plugin_name, instance) - - # 创建生命周期 if self.lifecycle_plugin and plugin_name != "lifecycle": - lc = self.lifecycle_plugin.create(plugin_name) - info.lifecycle = lc + info.lifecycle = self.lifecycle_plugin.create(plugin_name) - self.plugins[plugin_name] = { - "instance": instance, - "module": module, - "info": info, - "permissions": permissions, - } + self.plugins[plugin_name] = {"instance": instance, "module": module, "info": info, "permissions": permissions} return instance def load_all(self, store_dir: str = "store"): - """加载 store 下所有插件(跳过自己)""" - # 确保 plugin 命名空间包存在(必须在最开头) - import types if 'plugin' not in sys.modules: - plugin_pkg = types.ModuleType('plugin') - plugin_pkg.__path__ = [] - plugin_pkg.__package__ = 'plugin' - sys.modules['plugin'] = plugin_pkg + pkg = types.ModuleType('plugin') + pkg.__path__ = []; pkg.__package__ = 'plugin' + sys.modules['plugin'] = pkg Log.tip("plugin-loader", "已创建 plugin 命名空间包") - # 检查是否有任何插件存在 - has_plugins = self._check_any_plugins(store_dir) - - if not has_plugins: + if not self._check_any_plugins(store_dir): Log.warn("plugin-loader", "未检测到任何插件,自动引导安装...") self._bootstrap_installation() - # 加载 lifecycle lifecycle_plugin = None - lifecycle_dir = Path(store_dir) / "@{FutureOSS}" / "lifecycle" - if lifecycle_dir.exists() and (lifecycle_dir / "main.py").exists(): + lc_dir = Path(store_dir) / "@{FutureOSS}" / "lifecycle" + if lc_dir.exists() and (lc_dir / "main.py").exists(): try: - instance = self.load(lifecycle_dir) - if instance: - lifecycle_plugin = instance - self.plugins.pop("lifecycle", None) - except Exception: - pass + inst = self.load(lc_dir) + if inst: lifecycle_plugin = inst; self.plugins.pop("lifecycle", None) + except Exception: pass - # 加载 dependency - dependency_plugin = None - dependency_dir = Path(store_dir) / "@{FutureOSS}" / "dependency" - if dependency_dir.exists() and (dependency_dir / "main.py").exists(): + dep_plugin = None + dep_dir = Path(store_dir) / "@{FutureOSS}" / "dependency" + if dep_dir.exists() and (dep_dir / "main.py").exists(): try: - instance = self.load(dependency_dir) - if instance: - dependency_plugin = instance - self._dependency_plugin = instance - self.plugins.pop("dependency", None) - except Exception: - pass + inst = self.load(dep_dir) + if inst: dep_plugin = inst; self._dependency_plugin = inst; self.plugins.pop("dependency", None) + except Exception: pass - # 加载 signature-verifier - signature_verifier = None - sig_verifier_dir = Path(store_dir) / "@{FutureOSS}" / "signature-verifier" - if sig_verifier_dir.exists() and (sig_verifier_dir / "main.py").exists(): + sig_dir = Path(store_dir) / "@{FutureOSS}" / "signature-verifier" + if sig_dir.exists() and (sig_dir / "main.py").exists(): try: - instance = self.load(sig_verifier_dir) - if instance: - signature_verifier = instance - self.set_signature_verifier(instance.verifier) - Log.ok("plugin-loader", "签名验证服务已加载") - except Exception as e: - Log.warn("plugin-loader", f"signature-verifier 加载失败: {e}") + inst = self.load(sig_dir) + if inst: self.set_signature_verifier(inst.verifier); Log.ok("plugin-loader", "签名验证服务已加载") + except Exception as e: Log.warn("plugin-loader", f"signature-verifier 加载失败: {e}") - # 加载 lifecycle - if lifecycle_plugin: - self.set_lifecycle(lifecycle_plugin) - - # 加载其他插件 + if lifecycle_plugin: self.set_lifecycle(lifecycle_plugin) self._load_plugins_from_dir(Path(store_dir)) - - # 按依赖排序 - if dependency_plugin: - self._sort_by_dependencies(dependency_plugin) + if dep_plugin: self._sort_by_dependencies(dep_plugin) def _load_plugins_from_dir(self, store_dir: Path): - """从指定目录加载插件""" - if not store_dir.exists(): - return - - # 核心插件列表(不使用沙箱,允许完整功能) + if not store_dir.exists(): return core_plugins = {"webui", "dashboard", "pkg-manager"} - - # 第一遍:加载所有插件 - for author_dir in store_dir.iterdir(): - if author_dir.is_dir(): - for plugin_dir in author_dir.iterdir(): - if plugin_dir.is_dir() and plugin_dir.name not in ("plugin-loader", "lifecycle", "dependency", "signature-verifier"): - if (plugin_dir / "main.py").exists(): - # 核心插件不使用沙箱 - use_sandbox = plugin_dir.name not in core_plugins - self.load(plugin_dir, use_sandbox=use_sandbox) - - # 第二遍:关联能力 + skip = {"plugin-loader", "lifecycle", "dependency", "signature-verifier"} + for ad in store_dir.iterdir(): + if ad.is_dir(): + for pd in ad.iterdir(): + if pd.is_dir() and pd.name not in skip and (pd / "main.py").exists(): + self.load(pd, use_sandbox=pd.name not in core_plugins) self._link_capabilities() def _check_any_plugins(self, store_dir: str) -> bool: - """检查是否存在任何插件""" - store = Path(store_dir) - if store.exists(): - for author_dir in store.iterdir(): - if author_dir.is_dir(): - for plugin_dir in author_dir.iterdir(): - if plugin_dir.is_dir() and (plugin_dir / "main.py").exists(): - return True - + sp = Path(store_dir) + if sp.exists(): + for ad in sp.iterdir(): + if ad.is_dir(): + for pd in ad.iterdir(): + if pd.is_dir() and (pd / "main.py").exists(): return True return False - def _bootstrap_installation(self): - """引导安装 FutureOSS 官方插件""" - Log.info("plugin-loader", "跳过引导安装(pkg 插件已移除)") + def _bootstrap_installation(self): Log.info("plugin-loader", "跳过引导安装(pkg 插件已移除)") def _sort_by_dependencies(self, dep_plugin): - """按依赖关系排序""" - if not dep_plugin: - return - - # 添加所有插件的依赖 - for name, info in self.plugins.items(): - deps = info["info"].dependencies - dep_plugin.add_plugin(name, deps) - + if not dep_plugin: return + for n, i in self.plugins.items(): dep_plugin.add_plugin(n, i["info"].dependencies) try: order = dep_plugin.resolve() - # 重新排序 plugins - sorted_plugins = {} - for name in order: - if name in self.plugins: - sorted_plugins[name] = self.plugins[name] - - # 检查是否所有插件都在排序结果中 - missing = set(self.plugins.keys()) - set(sorted_plugins.keys()) - for name in missing: - sorted_plugins[name] = self.plugins[name] - - self.plugins = sorted_plugins - except Exception as e: - Log.error("plugin-loader", f"依赖解析失败: {e}") + sp = {} + for n in order: + if n in self.plugins: sp[n] = self.plugins[n] + for n in set(self.plugins.keys()) - set(sp.keys()): sp[n] = self.plugins[n] + self.plugins = sp + except Exception as e: Log.error("plugin-loader", f"依赖解析失败: {e}") def _link_capabilities(self): - """关联能力:带权限检查""" - for plugin_name, info in self.plugins.items(): - caps = info["info"].capabilities - allowed = info.get("permissions", []) - - for cap in caps: - # 如果这个插件是某个能力的提供者 + for pn, info in self.plugins.items(): + for cap in info["info"].capabilities: if self.capability_registry.has_capability(cap): - # 找到所有需要这个能力的消费者 - consumers = self.capability_registry.get_consumers(cap) - for consumer_name in consumers: - if consumer_name in self.plugins: - consumer_info = self.plugins[consumer_name]["info"] - consumer_allowed = self.plugins[consumer_name].get("permissions", []) - # 权限检查 + for cn in self.capability_registry.get_consumers(cap): + if cn in self.plugins: + ci = self.plugins[cn]["info"] + ca = self.plugins[cn].get("permissions", []) try: - provider = self.capability_registry.get_provider( - cap, - requester=consumer_name, - allowed_plugins=consumer_allowed - ) - if provider and hasattr(consumer_info, "extensions"): - consumer_info.extensions[f"_{cap}_provider"] = provider - except PermissionError as e: - Log.error("plugin-loader", f"权限拒绝: {e}") + p = self.capability_registry.get_provider(cap, requester=cn, allowed_plugins=ca) + if p and hasattr(ci, "extensions"): ci.extensions[f"_{cap}_provider"] = p + except PermissionError as e: Log.error("plugin-loader", f"权限拒绝: {e}") def start_all(self): - """启动所有插件(假设已初始化)""" - # 注入依赖实例 self._inject_dependencies() - - # 启动所有插件 - for name, info in self.plugins.items(): - try: - info["instance"].start() - except Exception as e: - Log.error("plugin-loader", f"启动失败 {name}: {e}") + for n, i in self.plugins.items(): + try: i["instance"].start() + except Exception as e: Log.error("plugin-loader", f"启动失败 {n}: {e}") def init_and_start_all(self): - """初始化并启动所有插件 - - 正确顺序: - 1. 注入依赖实例 - 2. 按拓扑顺序 init() 所有插件 - 3. 按拓扑顺序 start() 所有插件 - """ Log.info("plugin-loader", f"init_and_start_all 被调用,plugins={len(self.plugins)}") - - # 1. 注入依赖实例 self._inject_dependencies() - - # 2. 获取拓扑排序 - ordered_plugins = self._get_ordered_plugins() - Log.tip("plugin-loader", f"插件启动顺序: {' -> '.join(ordered_plugins)}") - - # 3. 初始化所有插件(跳过 plugin-loader 自己) - Log.info("plugin-loader", "开始初始化所有插件...") - for name in ordered_plugins: - if "plugin-loader" in name: - continue - info = self.plugins[name] + ordered = self._get_ordered_plugins() + Log.tip("plugin-loader", f"插件启动顺序: {' -> '.join(ordered)}") + for name in ordered: + if "plugin-loader" in name: continue try: Log.info("plugin-loader", f"初始化: {name}") - info["instance"].init() - except Exception as e: - Log.error("plugin-loader", f"初始化失败 {name}: {e}") - - # 4. 启动所有插件(跳过 plugin-loader 自己) - Log.info("plugin-loader", "开始启动所有插件...") - for name in ordered_plugins: - if "plugin-loader" in name: - continue - info = self.plugins[name] + self.plugins[name]["instance"].init() + except Exception as e: Log.error("plugin-loader", f"初始化失败 {name}: {e}") + for name in ordered: + if "plugin-loader" in name: continue try: Log.info("plugin-loader", f"启动: {name}") - info["instance"].start() - except Exception as e: - Log.error("plugin-loader", f"启动失败 {name}: {e}") + self.plugins[name]["instance"].start() + except Exception as e: Log.error("plugin-loader", f"启动失败 {name}: {e}") def _get_ordered_plugins(self) -> list[str]: - """获取按依赖排序的插件列表""" - # 如果没有 dependency 插件,直接返回原始顺序 - if not hasattr(self, '_dependency_plugin') or not self._dependency_plugin: - return list(self.plugins.keys()) - - try: - # 使用 dependency 插件解析 - order = self._dependency_plugin.resolve() - # 过滤出实际存在的插件 - return [name for name in order if name in self.plugins] - except Exception as e: - Log.warn("plugin-loader", f"依赖解析失败,使用原始顺序: {e}") - return list(self.plugins.keys()) + if not self._dependency_plugin: return list(self.plugins.keys()) + try: return [n for n in self._dependency_plugin.resolve() if n in self.plugins] + except Exception as e: Log.warn("plugin-loader", f"依赖解析失败,使用原始顺序: {e}"); return list(self.plugins.keys()) def _inject_dependencies(self): - """注入插件依赖实例""" Log.info("plugin-loader", f"开始注入依赖,共 {len(self.plugins)} 个插件") - - # 构建名称映射(处理 } 后缀问题) - name_map = {} - for name in self.plugins: - clean = name.rstrip("}") - name_map[clean] = name - name_map[clean + "}"] = name - - for name, info in self.plugins.items(): - instance = info["instance"] - info_obj = info.get("info") - if not info_obj: - continue - deps = info_obj.dependencies - if not deps: - continue - - Log.tip("plugin-loader", f"{name} 依赖: {deps}") - for dep_name in deps: - # 使用名称映射查找 - actual_dep = name_map.get(dep_name) or name_map.get(dep_name + "}") - if actual_dep and actual_dep in self.plugins: - dep_instance = self.plugins[actual_dep]["instance"] - setter_name = f"set_{dep_name.replace('-', '_')}" - Log.tip("plugin-loader", f"尝试注入: {name} <- {actual_dep} ({setter_name})") - if hasattr(instance, setter_name): - try: - getattr(instance, setter_name)(dep_instance) - Log.ok("plugin-loader", f"注入成功: {name} <- {actual_dep}") - except Exception as e: - Log.error("plugin-loader", f"注入依赖失败 {name}.{setter_name}: {e}") - else: - Log.warn("plugin-loader", f"{name} 没有 {setter_name} 方法") + nm = {} + for n in self.plugins: + c = n.rstrip("}"); nm[c] = n; nm[c + "}"] = n + for n, i in self.plugins.items(): + inst = i["instance"]; io = i.get("info") + if not io or not io.dependencies: continue + for dn in io.dependencies: + ad = nm.get(dn) or nm.get(dn + "}") + if ad and ad in self.plugins: + sn = f"set_{dn.replace('-', '_')}" + if hasattr(inst, sn): + try: getattr(inst, sn)(self.plugins[ad]["instance"]); Log.ok("plugin-loader", f"注入成功: {n} <- {ad}") + except Exception as e: Log.error("plugin-loader", f"注入依赖失败 {n}.{sn}: {e}") + else: Log.warn("plugin-loader", f"{n} 没有 {sn} 方法") def stop_all(self): - """停止所有插件""" - for name, info in reversed(list(self.plugins.items())): - try: - info["instance"].stop() - except Exception: - pass - if self.lifecycle_plugin: - self.lifecycle_plugin.stop_all() + for n, i in reversed(list(self.plugins.items())): + try: i["instance"].stop() + except Exception: pass + if self.lifecycle_plugin: self.lifecycle_plugin.stop_all() def get_info(self, name: str) -> Optional[PluginInfo]: - """获取插件信息""" - if name in self.plugins: - return self.plugins[name]["info"] + if name in self.plugins: return self.plugins[name]["info"] return None - def has_capability(self, capability: str) -> bool: - """检查系统是否有某个能力""" - return self.capability_registry.has_capability(capability) - - def get_capability_provider(self, capability: str) -> Optional[Any]: - """获取能力提供者""" - return self.capability_registry.get_provider(capability) + def has_capability(self, capability: str) -> bool: return self.capability_registry.has_capability(capability) + def get_capability_provider(self, capability: str) -> Optional[Any]: return self.capability_registry.get_provider(capability) class PluginLoaderPlugin(Plugin): """插件加载器插件""" - def __init__(self): self.manager = PluginManager() self._loaded = False @@ -647,48 +611,32 @@ class PluginLoaderPlugin(Plugin): self._ensure_plugin_package() def _ensure_plugin_package(self): - """确保 plugin 命名空间包存在""" - import types if 'plugin' not in sys.modules: - plugin_pkg = types.ModuleType('plugin') - plugin_pkg.__path__ = [] - sys.modules['plugin'] = plugin_pkg + pkg = types.ModuleType('plugin'); pkg.__path__ = []; sys.modules['plugin'] = pkg def init(self, deps: dict = None): - """加载所有插件""" - if self._loaded: - return + if self._loaded: return self._loaded = True - - # 确保 plugin 命名空间包存在(必须在加载任何插件之前) - import types - if 'plugin' not in sys.modules: - plugin_pkg = types.ModuleType('plugin') - plugin_pkg.__path__ = [] - sys.modules['plugin'] = plugin_pkg - + self._ensure_plugin_package() Log.info("plugin-loader", "开始加载插件...") self.manager.load_all() def start(self): - """启动所有插件""" - if self._started: - return + if self._started: return self._started = True Log.info("plugin-loader", "启动插件...") self.manager.init_and_start_all() def stop(self): - """停止所有插件""" Log.info("plugin-loader", "停止插件...") self.manager.stop_all() -# 注册类型 register_plugin_type("PluginManager", PluginManager) register_plugin_type("PluginInfo", PluginInfo) register_plugin_type("CapabilityRegistry", CapabilityRegistry) +register_plugin_type("PLInjector", PLInjector) def New(): - return PluginLoaderPlugin() + return PluginLoaderPlugin() \ No newline at end of file