如何按 modern pattern 为 Seelen-UI 新增一个后端系统模块(application/infrastructure)? 📅 发布时间:2026/9/14 4:23:08 👁 浏览次数: 如何按 modern pattern 为 Seelen-UI 新增一个后端系统模块application/infrastructure【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UISeelen-UI 是基于 Rust Tauri 的 Windows 10/11 可定制桌面环境。当你要在 Rust 后端新增一个系统能力例如新的系统状态读取器仓库规定src/background/modules/下的模块必须遵循 modern pattern懒初始化lazy init 懒 Tauri 事件注册lazy tauri registration业务逻辑集中在application.rsTauri 命令与 webview 事件桥接放在infrastructure.rs。本文给出从建目录、写两层代码到在libs/core声明命令/事件、重新生成 TypeScript 绑定最后用cargo check验证的完整路径。准备条件已能构建仓库。初始环境按 AGENTS.md 的要求执行npm install npm run dev开发迭代中用cargo check做快速类型校验。AGENTS.md 明确要求不要用cargo build --release做测试、类型检查或本地迭代仅在需要二进制时才用cargo builddebug。了解仓库布局共享库在libs/libs/core是核心库 由 Rust 生成的 TypeScript 绑定主应用 Rust 后端在src/background/。第一步创建模块目录与 mod.rs在src/background/modules/下新建module/下文所有module、YourManager、YourEvent、YourType均为需要替换成你自己模块名的占位符按 AGENTS.md 的 Suggested layout 组织src/background/modules/module/ mod.rs application.rs infrastructure.rs # 或 handlers.rs domain.rs # 可选mod.rs参考现有模块如 notifications/mod.rsmod application; mod domain; pub mod infrastructure;然后把pub mod module;加入 modules/mod.rs与pub mod notifications;等现有声明并列。第二步编写 application 侧单例 内部事件application.rs拥有单例 manager 并只发内部事件不直接碰 webview。AGENTS.md 给出的最小模式use std::sync::LazyLock; pub struct YourManager { // fields } #[derive(Debug, Clone)] pub enum YourEvent { DataChanged, } event_manager!(YourManager, YourEvent); impl YourManager { fn new() - Self { Self { /* init */ } } pub fn instance() - static Self { static MANAGER: LazyLockYourManager LazyLock::new(|| { let mut m YourManager::new(); m.init().log_error(); m }); MANAGER } fn init(mut self) - Result() { self.setup_listeners()?; Ok(()) } fn setup_listeners(mut self) - Result() { // Listen to OS signals; emit internal YourEvent::* (not webview events) Ok(()) } pub fn get_data(self) - VecYourType { // return data vec![] } }要点均来自 AGENTS.md 与仓库代码event_manager!宏定义在 modules/mod.rs它给 manager 提供基于 crossbeam channel 的send/subscribe/unsubscribe/set_event_handler_priority。实际用法可对照 notifications/application.rs声明NotificationEvent后写event_manager!(NotificationManager, NotificationEvent);状态变更处调用Self::send(NotificationEvent::...)。init()里监听 OS 信号、只发内部YourEvent::*注释里明确写了 not webview events。若模块要访问 WinRT 事件对象遵循 AGENTS.md 的 WinRT Wrapper Pattern存事件 token通常是i64不要用结构体字段保存TypedEventHandler并实现Drop自动注销事件。notifications/application.rs 中的event_token: Optioni64字段就是这个模式的实例。第三步编写 infrastructure 侧懒注册 Tauri 命令infrastructure.rs拥有 Tauri 命令并把内部事件桥接到 webview。Tauri 事件注册发生在首次命令访问时通过Once这就是 lazy tauri registration 的含义。AGENTS.md 最小模式use std::sync::Once; use seelen_core::handlers::SeelenEvent; use crate::{app::emit_to_webviews, error::Result}; use super::{YourEvent, YourManager}; fn get_manager() - static YourManager { static REGISTER: Once Once::new(); REGISTER.call_once(|| { YourManager::subscribe(|_event: YourEvent| { // Keep this small and side-effect focused. if let Ok(data) get_your_data() { emit_to_webviews(SeelenEvent::YourDataChanged, data); } }); }); YourManager::instance() } #[tauri::command(async)] pub fn get_your_data() - ResultVecYourType { let manager get_manager(); Ok(manager.get_data()) }对照真实实现 notifications/infrastructure.rsget_notification_manager()用static TAURI_EVENT_REGISTRATION: Once在call_once内调用NotificationManager::subscribe(...)桥接事件然后返回NotificationManager::instance()每个#[tauri::command(async)]函数都通过get_notification_manager()获取单例。两条硬性规则AGENTS.md Backend architecture rules模块必须走 modern patternlazy init lazy tauri registration。业务逻辑application 层不允许直接调用emit_to_webviews只能由 infrastructure 层的订阅回调发射。emit_to_webviews本身定义在 app.rs当会话非交互态IS_INTERACTIVE_SESSION为 false时会静默跳过发射这是仓库代码的实际行为。第四步在 libs/core 声明命令与事件模块暴露给 UI 后需要在libs/core登记命令和事件名libs/core/src/handlers/commands.rs 的slu_commands_declaration! { ... }块中加入一行格式对照现有GetNotifications条目slu_commands_declaration! { GetYourData get_your_data() - VecYourType, }libs/core/src/handlers/events.rs 的slu_events_declaration! { ... }块中加入一行事件字符串遵循模块名::事件名惯例如现有media-sessions、power-statusslu_events_declaration! { YourDataChanged(VecYourType) as your-module::data-changed, }命令无需手动注册 handlerslu_commands_declaration!会生成command_handler_list!宏而 exposed.rs 中app_builder.invoke_handler(command_handler_list!())用它展开tauri::generate_handler!声明即注册。第五步重新生成 TypeScript 绑定在 libs/core/deno.json 中定义了build:rs任务按 AGENTS.md 执行cd libs/core deno task build:rs该任务由 rust_bindings.ts 驱动重新生成 Rust - TypeScript 绑定使前端可以通过SeelenCommand/SeelenEvent枚举访问你新增的条目。验证按 AGENTS.md 的 Testing Expectations 使用快速反馈循环cargo check—— 确认新模块与libs/core声明编译通过deno lint、npm run type-check—— 前端侧检查若你同时改了 UI 代码。运行时行为上的验证依据infrastructure 层的Once保证事件订阅只在首次命令调用时发生一次因此前端第一次invoke你的命令后即触发懒初始化之后内部事件每次都会经订阅回调emit_to_webviews推送到各 webview。注意emit_to_webviews在非交互会话下会跳过发射排查前端收不到事件时先确认当前会话状态。前端消费可选如果新命令需要在前端 widget 中使用AGENTS.md 的 LazySignal 模式给出了配套用法LazySignal位于libs/widgets-shared/适用于异步获取、事件更新、跨 webview 共享的状态import { lazySignal } from libs/widgets-shared/LazySignal; import { invoke, SeelenCommand, SeelenEvent, subscribe } from seelen-ui/lib; const $data lazySignal(async () { return await invoke(SeelenCommand.GetYourData); }); subscribe(SeelenEvent.YourDataChanged, (event) { $data.value event.payload; }); await $data.init();AGENTS.md 对该模式有一条关键顺序约束先注册事件监听事件可能立即触发最后再调用.init()且它不得覆盖事件已写入的值。边界与必须遵守的约定锁顺序避免死锁AGENTS.md1. CLI locks2. DATA locks3. EVENT locks。Serde 打标枚举避免元组变体#[serde(tag type)]的枚举成员应写成WithData { data: String }而不是WithData(String)AGENTS.md Rust Types: Tagged Enums。翻译文件在迭代期间手工添加npm run translate只在最终提交前运行AGENTS.md Translations 规则。若模块需要 WinRT 事件参照 AGENTS.md 的 WinRtWrapper 示例实现Drop自动注销token 类型通常为i64。【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考