feat: 实现动态插件系统 (6阶段完成)

- 阶段1: 消息类型序列化 (Serialize/Deserialize, &'static str → String)
- 阶段2: FFI 边界类型 + Plugin SDK (plugin_abi, showen-plugin-sdk crate)
- 阶段3: PluginLoader + DynamicPlugin (libloading 动态加载 .so)
- 阶段4: 版本管理 + 错误策略 (VersionManager, PluginState, 自动回退)
- 阶段5: 远程仓库客户端 (HTTP 下载 + tar.gz 安装)
- 阶段6: 示例插件 + HTTP 管理 API + 全目录 README 文档

54/54 测试通过,0 warnings。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
showen
2026-03-13 03:38:08 +08:00
parent 5dcc1ad98e
commit 7135f28545
62 changed files with 3501 additions and 299 deletions

View File

@@ -1,5 +1,6 @@
use anyhow::Result;
use showen_v2::core::config::AppConfig;
use showen_v2::core::plugin_loader::PluginLoader;
use showen_v2::core::service_manager::ServiceManager;
use showen_v2::plugins::{
ble::BlePlugin, http::HttpPlugin, screen::ScreenPlugin, video::VideoPlugin, wifi::WifiPlugin,
@@ -64,6 +65,46 @@ fn main() -> Result<()> {
manager.register(Box::new(HttpPlugin::new()));
println!(" ✓ HttpPlugin");
// 加载动态插件
let plugin_store = std::path::Path::new("plugin_store");
if plugin_store.exists() {
println!("扫描动态插件...");
let loader = PluginLoader::new(plugin_store);
match loader.load_registry() {
Ok(registry) => {
for (plugin_id, entry) in &registry.plugins {
if !entry.enabled {
println!(" - {plugin_id} (禁用)");
continue;
}
match loader.load_plugin(plugin_id, Some(&entry.active_version)) {
Ok((plugin, manifest)) => {
manager.register_dynamic(
Box::new(plugin),
manifest.error_policy,
entry.max_errors,
);
println!(
"{} v{} (动态)",
plugin_id, entry.active_version
);
}
Err(e) => {
eprintln!(
"{} v{} 加载失败: {e}",
plugin_id, entry.active_version
);
}
}
}
}
Err(e) => {
eprintln!("读取插件注册表失败: {e}");
}
}
}
// 设置 Ctrl+C 信号处理
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();