feat(plugins): 插件支持热加载与启用/停用清单
- pugins/__init__.py 重写:load_plugins / discover / set_enabled / reload
- 热加载:把 study_tool.pugins.<名字> 的模块从 sys.modules 摘掉后重新 import;
重载前先 registry.unregister 旧 id,平台 id 改了也不会留残影
- 启用清单存设置项 plugins({"disabled": [...]}):未列出的默认启用,新插件自动生效;
停用即从注册表摘掉,/api/platforms 与账号平台下拉同步消失
- platforms/registry.py 新增 unregister(pid)
- 新接口:GET /api/plugins、POST /api/plugins/{name}/enable|disable、
POST /api/plugins/reload、POST /api/plugins/{name}/reload;失败返回 ok=false
- 同步 docs/pugin.md(新增「加载与启停」)、docs/architecture.md、docs/OPTIMIZATION.md、AGENTS.md
This commit is contained in:
@@ -2,6 +2,30 @@
|
||||
|
||||
已实现、但还需要你实测确认的事项。确认没问题后把对应条目删掉。
|
||||
|
||||
## 插件热加载 + 启用/停用清单
|
||||
|
||||
**状态**:已实现,待实测确认。
|
||||
|
||||
### 做了什么
|
||||
|
||||
- `pugins/__init__.py` 重写:`load_plugins()` / `discover()` / `set_enabled()` / `reload()`。
|
||||
- 热加载:把 `study_tool.pugins.<名字>`(含子模块)从 `sys.modules` 摘掉后重新 import,
|
||||
import 时 `registry.register()` 覆盖;重载前先 `registry.unregister()` 旧 id,改 id 不留残影。
|
||||
- 启用清单存在设置项 `plugins`(`{"disabled": [...]}`):没列进去的默认启用,
|
||||
新丢进目录的插件自动生效;停用的插件被摘出注册表,`/api/platforms` 与账号平台下拉同步消失。
|
||||
- `platforms/registry.py` 新增 `unregister(pid)`。
|
||||
- 新增接口:`GET /api/plugins`、`POST /api/plugins/{name}/enable|disable`、
|
||||
`POST /api/plugins/reload`、`POST /api/plugins/{name}/reload`;失败返回 `{"ok": false, "error": ...}`。
|
||||
- 引擎 / 路由 / 前端 / 平台实现均未改动。
|
||||
|
||||
### 怎么测
|
||||
|
||||
1. `GET /api/plugins` 列出 `gdgxk`(enabled / loaded 为 true,capabilities 七项)。
|
||||
2. `POST /api/plugins/gdgxk/disable` → `GET /api/platforms` 里 gdgxk 消失;再 enable 回来。
|
||||
3. 改 `pugins/gdgxk/main.py` 里一句日志文案 → `POST /api/plugins/gdgxk/reload` →
|
||||
不重启服务再跑一次学习,日志应变成新文案。
|
||||
4. 重启服务:停用状态应被记住(设置项 `plugins`)。
|
||||
|
||||
## 平台契约改为子类实现(注册方式调整)
|
||||
|
||||
**状态**:已实现,待实测确认。
|
||||
|
||||
@@ -29,7 +29,7 @@ src/study_tool/
|
||||
jspx.py 莞易学旧版:会话 / 登录 / 课程抓取 / 旧版学习流程
|
||||
study.py 莞易学新版 SPA:StudyClient / 接口学习 / 视频流程
|
||||
pugins/ 插件式平台(加载器扫目录,新增插件不改代码)
|
||||
__init__.py load_plugins():扫 pugins/*/pugin.json 并 import main.py
|
||||
__init__.py load_plugins / discover / set_enabled / reload(启停 + 热重载)
|
||||
gdgxk/
|
||||
pugin.json 注册信息:id / title / name(只放元数据)
|
||||
main.py 广东公需课:SSO 换票 + 视频进度心跳
|
||||
|
||||
+31
-17
@@ -2,8 +2,10 @@
|
||||
|
||||
分层与整体结构见 [architecture.md](architecture.md),本文只讲「怎么写一个平台插件」。
|
||||
|
||||
一个平台 = `src/study_tool/platforms/<平台>.py` 里的一个 `Platform` 子类:类属性放元数据,
|
||||
自己确实有的阶段实现成方法,模块末尾注册一次。引擎、路由、前端都不需要改。
|
||||
一个平台 = 一个 `Platform` 子类:类属性放元数据,自己确实有的阶段实现成方法,模块末尾
|
||||
注册一次。插件式平台放 `src/study_tool/pugins/<名字>/`(`pugin.json` + `main.py`),由加载器
|
||||
扫目录自动加载;跟共享实现模块关系紧密的放 `src/study_tool/platforms/<名字>.py`。
|
||||
引擎、路由、前端都不需要改。
|
||||
|
||||
## 插件骨架
|
||||
|
||||
@@ -75,16 +77,12 @@ PLATFORM = registry.register(MyPlatform())
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/study_tool/platforms/
|
||||
base.py Platform 基类:id / label / entries + 8 个槽位默认 None
|
||||
contract.py 槽位 ↔ 能力名映射,capabilities() 用它推导
|
||||
errors.py SessionExpired(会话失效,引擎据此续期 / 重登)
|
||||
registry.py register / get / detect / all
|
||||
detect.py 页面代际识别(legacy 旧版 / spa 新版)
|
||||
guanyixue.py 平台:莞易学(jspx 登录入口 + study 学习入口,按 generation 分发)
|
||||
jspx.py 同平台实现模块:旧版页面(不是插件,不注册)
|
||||
study.py 同平台实现模块:新版 SPA(不是插件,不注册)
|
||||
gdgxk.py 平台:广东公需课
|
||||
src/study_tool/pugins/
|
||||
__init__.py 加载器:load_plugins / discover / set_enabled / reload
|
||||
<名字>/
|
||||
pugin.json 注册信息:id / title / name(不写能力绑定)
|
||||
main.py class <名字>(Platform)
|
||||
gdgxk/ 广东公需课:pugin.json + main.py
|
||||
```
|
||||
|
||||
约定:
|
||||
@@ -96,6 +94,21 @@ src/study_tool/platforms/
|
||||
- 平台私有的工具函数放同一模块或同平台的实现模块里;跨平台共用的能力放 `base.py` /
|
||||
`contract.py` / `errors.py`。
|
||||
|
||||
## 加载与启停
|
||||
|
||||
加载器在 `pugins/__init__.py`,对外四个函数 + 对应 HTTP 接口:
|
||||
|
||||
| 函数 | 接口 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| `load_plugins()` | 启动时自动调用 | 按启用清单 import 全部插件 |
|
||||
| `discover()` | `GET /api/plugins` | 磁盘清单 + 是否启用 / 已加载 / label / capabilities |
|
||||
| `set_enabled(name, bool)` | `POST /api/plugins/{name}/enable`、`/disable` | 立即加载或从注册表摘掉,并落库 |
|
||||
| `reload(name=None)` | `POST /api/plugins/reload`、`/api/plugins/{name}/reload` | 重新 import,改完插件代码不用重启 |
|
||||
|
||||
启用清单存在设置项 `plugins`(`{"disabled": [...]}`):没列进去的默认启用,新丢进目录的插件自动生效。
|
||||
停用的插件会 `registry.unregister()`,从 `/api/platforms` 消失,账号的平台下拉里也不会出现。
|
||||
热重载先把旧实现从注册表摘掉再 import;正在跑的学习任务持有的是旧实例,跑到结束不受影响。
|
||||
|
||||
## 槽位契约
|
||||
|
||||
| 槽位 | 方法签名 | 调用方 | 要求 |
|
||||
@@ -121,11 +134,12 @@ quiz_key / cookies / plan / project / name`,插件只取自己需要的。
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. 新建 `src/study_tool/platforms/<名字>.py`,按上面的骨架写 `class <名字>(Platform)`:
|
||||
类属性 `id` / `label` / `entries`,按需实现槽位方法。
|
||||
2. 模块末尾 `PLATFORM = registry.register(<名字>())`。
|
||||
3. 在 `platforms/__init__.py` 的 `load_builtin()` 里加一行
|
||||
`from study_tool.platforms import <名字>`。
|
||||
1. 新建 `src/study_tool/pugins/<名字>/`,放两个文件:`pugin.json`(`id` / `title` / `name`)
|
||||
和 `main.py`(按上面的骨架写 `class <名字>(Platform)`)。
|
||||
2. `main.py` 末尾 `PLATFORM = registry.register(<名字>())`。
|
||||
3. 不需要改 `load_builtin()`、路由或前端 —— 加载器扫目录时自动 import 各插件。
|
||||
跟 `jspx` / `study` 这类共享实现模块关系紧密的平台,仍可以放 `platforms/<名字>.py`,
|
||||
在 `load_builtin()` 里加一行 import。
|
||||
4. 启动服务,`GET /api/platforms` 应出现该平台。
|
||||
5. 联调顺序:`/api/login` → `/api/courses` → `/api/discover` → `/api/study`。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user