-
-
Notifications
You must be signed in to change notification settings - Fork 40
Architecture
Zephyr 采用 Tauri v2 混合架构,以 Rust 后端为核心引擎、原生 JavaScript 前端为交互层、Mihomo 内核为代理执行层,三层协同工作,实现高性能、低资源占用的代理管理体验。
- 架构总览
- 核心启动流程
- 订阅下载流程
- 配置热重载流程
- 系统代理设置流程
- 前端初始化顺序
- 加密体系
- Tauri Commands 分类表
- 事件系统
- 数据结构定义
- 数据流设计
- Rust 后端模块详解
- 前端架构
- 并发安全与原子变量
- 应用生命周期
Zephyr 的架构可以概括为 四层模型,从上到下依次为前端层、IPC 桥接层、Rust 后端层和 Mihomo 内核层。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TB
subgraph Frontend["前端层 (Frontend Layer)"]
direction LR
main_js["main.js<br/>应用入口"]
ui_js["apps/desktop/src/ui/<br/>33 个 UI 模块"]
api_js["api.js<br/>API 封装"]
ws_js["websocket.js<br/>流量监控"]
chart_js["modules/traffic-chart.js<br/>图表渲染"]
rules_js["rules.js<br/>规则转换"]
i18n_js["i18n.js<br/>国际化"]
end
subgraph Bridge["Tauri IPC 桥接层 (Bridge Layer)"]
direction LR
cmd_registry["Command Registry<br/>命令注册表"]
event_sys["Event System<br/>事件系统"]
state_mgr["State Management<br/>状态管理"]
rate_lim["RateLimiter<br/>命令限流"]
end
subgraph Backend["Rust 后端层 (Backend Layer)"]
direction LR
lib_rs["lib.rs<br/>应用入口"]
core_mgr["core_manager.rs<br/>→ core/ 模块门面"]
config_mgr["config_manager.rs<br/>配置管理"]
sys_proxy["sys_proxy.rs<br/>系统代理"]
tray_rs["tray.rs<br/>系统托盘"]
updater["updater.rs<br/>内核更新"]
uwp_loop["uwp_loopback.rs<br/>UWP 环回"]
end
subgraph Core["Mihomo 内核层 (Core Layer)"]
direction LR
rest_api["RESTful API<br/>:9090"]
http_stream["HTTP Streaming<br/>/traffic"]
tun["TUN 虚拟网卡"]
rule_engine["规则引擎"]
dns_resolver["DNS 解析"]
end
Frontend -->|"invoke() / emit()"| Bridge
Bridge -->|"Command Handlers"| Backend
Backend -->|"Process spawn<br/>REST API<br/>System API"| Core
Core -->|"HTTP Streaming<br/>JSON Response"| Frontend
style Frontend fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style Bridge fill:#fff3e0,stroke:#e65100,color:#bf360c
style Backend fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style Core fill:#fce4ec,stroke:#c62828,color:#b71c1c
| 原则 | 实现方式 |
|---|---|
| 零前端框架 | 原生 JavaScript + DOM 操作,无 React/Vue/Svelte 依赖 |
| 零图表库 | 原生 Canvas 2D API 绘制流量面积图 |
| 内存安全 | Rust 所有权系统 + 编译期检查,无 GC 停顿 |
| 纵深防御 | SSRF 防护、命令限流、危险配置清除、AES-256-GCM 加密 |
| 跨平台一致 | 单一代码库覆盖 Windows / macOS / Linux,差异化能力精准适配 |
以下时序图展示了 Zephyr 中 MVVM 架构的完整交互流程,从用户点击 UI 元素到最终视图重新渲染的全链路。
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'labelTextColor': '#999', 'labelBoxBkgColor': '#333', 'labelBoxBorderColor': '#555', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'actorTextColor': '#ccc', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant User as 用户
participant View as View (DOM)
participant VM as ViewModel (src/ui/)
participant API as API Layer (api.js)
participant IPC as Tauri IPC Bridge
participant Rust as Rust Command Handler
participant BL as 业务逻辑<br/>(core_manager / config_manager)
participant Core as Mihomo Core / 文件系统
User->>View: 点击 UI 元素
View->>VM: 事件监听器触发<br/>addEventListener
VM->>VM: 处理交互逻辑<br/>状态校验 / 参数组装
VM->>API: invoke("command_name", params)
API->>IPC: Tauri invoke() 调用
IPC->>Rust: Command Handler 分发
Rust->>Rust: 命令限流检查<br/>RateLimiter
Rust->>BL: 调用业务逻辑模块
BL->>Core: 操作 Mihomo Core<br/>或读写文件系统
Core-->>BL: 返回操作结果
BL-->>Rust: Result<T, String>
Rust-->>IPC: 序列化返回值
IPC-->>API: Promise resolve
API-->>VM: 返回结果数据
VM->>VM: 更新状态 / 缓存
VM->>View: 更新 DOM<br/>textContent / classList / style
View-->>User: 界面重新渲染
%%{init: {'themeVariables': {'fontSize': '10px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '10px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 15, 'rankSpacing': 25}}}%%
graph LR
subgraph CommandPath["命令通路 (请求-响应)"]
A["用户操作"] --> B["src/ui/<br/>事件处理"]
B --> C["api.js<br/>Tauri invoke"]
C --> D["Tauri IPC Bridge"]
D --> E["Rust Command Handler"]
E --> F["core_manager / config_manager<br/>sys_proxy / updater / tray"]
F --> G["Result<T, String><br/>返回前端"]
end
subgraph TrafficPath["流量监控通路 (推送流)"]
H["Mihomo Core"] --> I["GET /traffic<br/>HTTP Streaming"]
I --> J["websocket.js<br/>ReadableStream"]
J --> K["滑动窗口<br/>60 数据点"]
K --> L["modules/traffic-chart.js<br/>Canvas 2D"]
L --> M["requestAnimationFrame<br/>节流渲染"]
end
style CommandPath fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style TrafficPath fill:#fce4ec,stroke:#c62828,color:#b71c1c
start_core 是整个应用最关键的函数之一,负责安全、可靠地启动 Mihomo 内核进程。整个过程包含 15 个严格有序的步骤。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["start_core 调用"] --> B{"命令限流检查<br/>3 秒冷却"}
B -->|"超频调用"| ERR1["返回错误<br/>Rate Limited"]
B -->|"通过"| C{"原子锁检查<br/>CORE_STARTING"}
C -->|"已有启动操作"| ERR2["返回错误<br/>Already Starting"]
C -->|"获取锁成功"| D["设置 10 秒超时<br/>自动释放"]
D --> E{"TUN 模式检查<br/>TUN_MODE_ACTIVE"}
E -->|"TUN 已激活"| ERR3["拒绝普通启动<br/>需先关闭 TUN"]
E -->|"TUN 未激活"| F["终止现有进程<br/>kill_mihomo()<br/>(zephyr-mihomo + 旧名兼容)"]
F --> G["确保目录存在<br/>ensure_dirs()"]
G --> H{"平台判断"}
H -->|"macOS"| I["端口等待<br/>最多 5 秒"]
H -->|"Windows / Linux"| J["解析路径<br/>配置文件 + 内核路径"]
I --> J
J --> K["验证参数<br/>文件存在性 + 合法性"]
K --> L["测试模式验证<br/>mihomo -t -f config.yaml"]
L --> M{"配置语法<br/>是否正确?"}
M -->|"失败"| ERR4["返回错误<br/>Config Test Failed"]
M -->|"通过"| N["生成 API Secret<br/>32 个随机字母数字字符(Alphanumeric)"]
N --> O["注入运行时配置<br/>external-controller<br/>secret + unified-delay"]
O --> P["写入 run_config.yaml<br/>{app_data_dir}/core/run_config.yaml"]
P --> Q["spawn Mihomo 进程<br/>Command::new(mihomo_path)"]
Q --> R["cache.db 锁重试<br/>等待内核释放数据库"]
R --> S["HTTP 健康检查<br/>127.0.0.1:port<br/>指数退避 50ms→1s<br/>最多约 2s"]
S --> T{"健康检查<br/>是否通过?"}
T -->|"失败"| U["优雅终止: SIGTERM → 等待 2s → SIGKILL<br/>返回错误"]
T -->|"成功"| V["更新 CoreData 状态<br/>返回 secret + port"]
U -.->|"终止前"| DRAIN["连接排空<br/>DELETE /connections<br/>2s 超时"]
style ERR1 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR2 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR3 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR4 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style V fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
| 变量 | 类型 | 用途 |
|---|---|---|
TUN_MODE_ACTIVE |
AtomicBool |
标记 TUN 模式是否激活 |
CORE_STARTING |
AtomicBool |
防止内核并发启动的互斥锁(10s 超时) |
download_sub 负责从远程 URL 下载订阅配置,经过严格的安全验证和清洗后安全存储到本地。
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'labelTextColor': '#999', 'labelBoxBkgColor': '#333', 'labelBoxBorderColor': '#555', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'actorTextColor': '#ccc', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant FE as 前端 (src/ui/)
participant TI as Tauri IPC
participant CM as config_manager
participant DNS as DNS 验证
participant HTTP as HTTP 下载
participant B64 as Base64 检测
participant YAML as YAML 解析
participant SEC as 安全清洗
participant ENC as 加密存储
participant FS as 文件系统
FE->>TI: invoke("download_sub", {url, name, user_agent, overwrite})
TI->>CM: download_sub(url, name, user_agent, overwrite)
Note over CM: 命令限流检查 (5 秒冷却)
CM->>DNS: DNS 预解析 URL
DNS-->>CM: IP 地址
CM->>CM: SSRF 防护验证<br/>私有 IP 拦截<br/>协议白名单
alt 直连下载
CM->>HTTP: HTTP GET (直连)
HTTP-->>CM: 响应内容
else 直连失败 → 代理下载
CM->>HTTP: HTTP GET (via Mihomo 代理)
HTTP-->>CM: 响应内容
else 代理失败 → 系统代理
CM->>HTTP: HTTP GET (via 系统代理)
HTTP-->>CM: 响应内容
end
CM->>B64: 检测内容是否 Base64 编码
B64-->>CM: 原始内容 / 解码后内容
CM->>YAML: serde_yaml::from_str()
YAML-->>CM: serde_yaml::Value
CM->>SEC: 清除危险键<br/>- script / script-path<br/>- provider.path
SEC-->>CM: 安全的 YAML Value
CM->>ENC: get_machine_key()<br/>obfuscate_string(url)
ENC-->>CM: 加密后的 URL (v2 格式)
CM->>FS: 原子写入配置文件<br/>写入 ConfigInfo 元数据
FS-->>CM: 写入完成
CM-->>TI: Ok(config_name)
TI-->>FE: resolve(config_name)
Zephyr 实现了双重下载策略,确保在各种网络环境下都能成功获取订阅配置:
- 第一优先级:直连下载 -- 适用于无代理环境
- 第二优先级:通过 Mihomo 代理下载 -- 适用于已启动代理的环境
安全说明:系统代理回退通道已在 v2.0.1 中移除。系统代理可能被任意应用或恶意软件设置,构成不可信的 SSRF 攻击面。仅保留直连和用户主动配置的 Mihomo 代理两条路径。
- DNS 预解析后校验 IP 地址,拦截 RFC 1918 私有地址、环回地址、链路本地地址
- 重定向链校验,最多跟踪 5 跳
- 协议白名单,仅允许 HTTP/HTTPS
update_config 负责将前端传入的配置变更合并到运行配置中,并通过 Mihomo 的 PATCH API 实现热重载,无需重启内核。
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'labelTextColor': '#999', 'labelBoxBkgColor': '#333', 'labelBoxBorderColor': '#555', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'actorTextColor': '#ccc', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant FE as 前端 (ui.js)
participant TI as Tauri IPC
participant CFG as config_manager
participant FILE as run_config.yaml
participant API as Mihomo API
FE->>TI: invoke("update_config", {patch})
TI->>CFG: update_config(patch)
CFG->>FILE: 读取当前运行配置
FILE-->>CFG: YAML 内容
Note over CFG: JSON → YAML 转换<br/>serde_yaml::from_value(patch)
CFG->>CFG: 保存安全键<br/>备份 external-controller<br/>备份 secret
CFG->>CFG: merge_yaml()<br/>递归合并 (深度限制 50 层)<br/>null 值表示删除键
CFG->>CFG: 恢复安全键<br/>写回 external-controller<br/>写回 secret
CFG->>FILE: 写入更新后的配置
FILE-->>CFG: 写入成功
CFG->>CFG: 同步到订阅配置文件
CFG->>API: PATCH /configs?force=true
API-->>CFG: 200 OK
CFG-->>TI: Ok(ConfigUpdateResult)
TI-->>FE: resolve()
fn merge_yaml(base: &mut YamlValue, overlay: &YamlValue, depth: usize) -> Result<(), String> {
// 深度限制:防止栈溢出攻击
if depth > 50 {
return Err("Maximum merge depth exceeded".to_string());
}
if let (YamlValue::Mapping(base_map), YamlValue::Mapping(overlay_map)) = (base, overlay) {
for (key, value) in overlay_map {
if value.is_null() {
// null 值表示删除该键
base_map.remove(key);
} else if let Some(existing) = base_map.get_mut(key) {
// 递归合并嵌套结构
merge_yaml(existing, value, depth + 1)?;
} else {
// 新增键值对
base_map.insert(key.clone(), value.clone());
}
}
} else {
// 非映射类型:直接覆盖
*base = overlay.clone();
}
Ok(())
}sys_proxy.rs 负责在各平台上配置和清除系统代理设置。所有实现都包含严格的安全验证,仅允许 loopback 地址作为代理服务器。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["enable_sysproxy 调用"] --> B["验证代理服务器地址<br/>仅允许 127.0.0.1 / localhost / ::1"]
B --> C{"地址合法?"}
C -->|"不合法"| ERR["返回错误<br/>Suspicious Proxy Host"]
C -->|"合法"| D{"平台检测"}
D -->|"Windows"| E["Windows 分支"]
D -->|"macOS"| F["macOS 分支"]
D -->|"Linux"| G["Linux 分支"]
subgraph Win["Windows -- 注册表原子写入"]
E1["读取当前注册表值<br/>HKCU\\...\\Internet Settings<br/>备份原始值"]
E2["原子写入新值<br/>ProxyEnable = 1<br/>ProxyServer = 127.0.0.1:7890<br/>ProxyOverride = localhost;127.*;..."]
E3["InternetSetOptionW 刷新<br/>SETTINGS_CHANGED<br/>+ REFRESH"]
E4{"写入成功?"}
E5["失败时回滚到备份值"]
E1 --> E2 --> E3 --> E4
E4 -->|"失败"| E5
end
subgraph Mac["macOS -- networksetup 逐服务"]
F1["获取所有网络服务列表<br/>networksetup -listallnetworkservices"]
F2["遍历每个服务"]
F3["跳过 Disabled 状态的服务"]
F4["设置 Web Proxy<br/>networksetup -setwebproxy"]
F5["设置 Secure Web Proxy<br/>networksetup -setsecurewebproxy"]
F6["设置 SOCKS Proxy<br/>networksetup -setsocksfirewallproxy"]
F7["启用代理开关<br/>-setwebproxystate on<br/>-setsecurewebproxystate on<br/>-setsocksfirewallproxystate on"]
F1 --> F2 --> F3 --> F4 --> F5 --> F6 --> F7
end
subgraph Lin["Linux -- 多桌面环境适配"]
G1{"检测桌面环境"}
G2["GNOME (gsettings)<br/>mode = manual<br/>http/https/socks host + port"]
G3["KDE Plasma<br/>org.kde.KIODaemon.update + org.kde.KWin.reconfigure"]
G4["XFCE (xfce4-session channel)<br/>mode = manual<br/>http/host + port"]
G1 -->|"GNOME"| G2
G1 -->|"KDE"| G3
G1 -->|"XFCE"| G4
end
E --> Win
F --> Mac
G --> Lin
E4 -->|"成功"| DONE["返回 Ok"]
E5 --> DONE
F7 --> DONE
G2 --> DONE
G3 --> DONE
G4 --> DONE
style ERR fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style DONE fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
style Win fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style Mac fill:#fff3e0,stroke:#e65100,color:#bf360c
style Lin fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
fn validate_proxy_server(server: &str) -> Result<(), String> {
// 1. 检查空地址
if server.is_empty() {
return Err("Proxy server address cannot be empty".to_string());
}
// 2. 检查长度上限
if server.len() > 512 {
return Err("Proxy server address too long".to_string());
}
// 3. 检查非法字符
if server.contains('\n') || server.contains('\r') || server.contains('\0') {
return Err("Proxy server address contains invalid characters".to_string());
}
// 4. 解析 host 和 port
let (host, _port) = parse_host_port(server)?;
// 5. 如果 host 可解析为 IP 地址:检查是否为 loopback
if let Ok(ip) = host.parse::<IpAddr>() {
if !ip.is_loopback() {
return Err("Only loopback addresses (127.0.0.1, ::1) are allowed".to_string());
}
return Ok(());
}
// 6. 如果 host 为 "localhost":允许
if host.to_lowercase() == "localhost" {
return Ok(());
}
Err(format!("Only loopback addresses are allowed, got: {}", host))
}main.js 中的 initApp() 函数编排了整个前端应用的初始化序列,步骤严格有序,确保每个模块在依赖就绪后才启动。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["initApp()"] --> B["Step 1: 禁用右键菜单<br/>contextmenu → preventDefault"]
B --> C["Step 2: 应用翻译<br/>i18n.applyTranslations()"]
C --> D["Step 3: 窗口控制绑定<br/>最小化 / 最大化 / 关闭"]
D --> E["Step 4: 显示窗口<br/>延迟 50ms 确保渲染完成"]
E --> F["Step 5: 启动 Mihomo 核心<br/>invoke('start_core')<br/>获取 secret + port"]
F --> G{"核心启动<br/>成功?"}
G -->|"失败"| ERR["显示错误提示<br/>应用进入降级模式"]
G -->|"成功"| H["Step 6: 设置 API 基地址<br/>API_BASE = http://127.0.0.1:port<br/>API_SECRET = secret"]
H --> I["Step 7: 初始化各模块<br/>initProxyToggle() + initProxyControls() 代理节点面板<br/>initNavigation() 订阅管理面板<br/>initSettings() 设置面板<br/>initChart() 流量图表<br/>initRulesPage() 规则编辑器"]
I --> J["Step 8: 检查密钥持久化<br/>invoke('is_machine_key_persisted')<br/>未持久化则提示用户"]
J --> K["Step 9: 同步配置与托盘<br/>syncCoreConfig() → 同步配置到 UI<br/>updateTrayStatus() + updateTrayMenu() → 同步托盘菜单状态"]
K --> L["Step 10: 启动周期同步<br/>startUnifiedSync()<br/>syncCoreConfig() + updateTrayStatus() + updateTrayMenu()"]
L --> M["Step 11: 监听后端事件<br/>config-parse-error<br/>profiles-imported<br/>tray-sysproxy-changed<br/>tray-tun-changed<br/>tray-mode-changed<br/>tray-proxy-changed<br/>core-download-status"]
M --> N["Step 12: 连接流量监控<br/>connectTraffic()<br/>HTTP Streaming → 滑动窗口<br/>→ Canvas 2D 渲染"]
style ERR fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style N fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
Zephyr 前端采用模块化架构,将 UI 逻辑拆分为独立的 ES Module 文件。以下列出核心模块:
| 模块 | 文件 | 行数 | 职责 |
|---|---|---|---|
| main.js | 入口 | ~225 行 | 应用初始化编排,协调各模块启动顺序,懒加载日志页面 |
| api.js | API 层 | ~504 行 | Tauri IPC 桥接(__TAURI_INTERNALS__)+ Mihomo REST API 封装 |
| websocket.js | 流量监控 | ~406 行 | HTTP Streaming 连接管理、数据解析、指数退避重连 |
| lifecycle.js | 生命周期 | ~124 行 | 统一配置切换逻辑,解决 api.js 与 UI 模块循环依赖 |
| prism.js | Prism IPC | ~136 行 | Prism Engine IPC 工具模块,为所有 Prism 命令提供类型化包装器 |
| settings-helpers.js | 设置辅助 | ~74 行 | settings.json 原子部分更新,串行队列保证写入顺序 |
| i18n.js | 国际化 | ~1019 行 | 英文/中文翻译,运行时语言切换 |
| rules.js | 规则转换 | ~98 行 | Shadowrocket 规则转 Clash 规则格式转换 |
| 模块 | 文件 | 行数 | 职责 |
|---|---|---|---|
| settings.js | 设置管理 | ~1100 行 | 设置页面主逻辑(已拆分子模块:subscriptions, theme, tunnels) |
| proxies.js | 代理管理 | ~827 行 | 代理组、节点选择、延迟测试 |
| logs.js | 日志查看 | ~692 行 | Mihomo 内核日志实时查看、搜索、自动滚动 |
| advanced.js | 高级设置 | ~506 行 | 自定义参数、动态配置引擎 |
| rule-library.js | 规则库 | ~1675 行 | Prism 规则库 CRUD、订阅导入、YAML 清洗 |
| plugins-smart.js | 智能插件 | ~595 行 | Prism 智能节点选择插件 |
| plugins.js | 插件管理 | ~437 行 | Prism 插件加载与管理 |
| prism.js | Prism 引擎 | ~484 行 | Prism Engine 前端控制层 |
| state.js | 状态管理 | ~352 行 | 基于 Proxy 的响应式状态管理,localStorage 持久化 |
| events.js | 事件总线 | ~214 行 | EventBus 事件系统,通配符监听,内存泄漏防护 |
| navigation.js | 导航控制 | — | 页面路由与 Tab 切换 |
| global-shortcut.js | 全局快捷键 | ~580 行 | 快捷键管理、平台感知显示、快捷键录制 |
| deep-link.js | Deep Link | ~60 行 |
clash:// URL 协议事件处理 |
| client-updater.js | 客户端更新 | ~82 行 | 自动更新检查、下载、Markdown Release Notes |
| os-notification.js | 系统通知 | ~25 行 | 原生 OS 通知封装 |
| bind.js | 响应式绑定 | ~61 行 |
bind() + bindAll() 高性能响应式 DOM 绑定(~100 bytes/binding) |
| tun.js | TUN 控制 | — | TUN 模式切换逻辑 |
| theme.js | 主题管理 | — | 主题色、透明度、暗色模式(已拆分至 settings/theme.js) |
| modes.js | 运行模式 | — | Global/Rule/Direct 模式切换 |
| dns.js | DNS 管理 | — | DNS 重写规则管理 |
| tray.js | 托盘交互 | — | 系统托盘事件处理 |
| 其他 12 个模块 | — | — | dropdown, proxy-groups, notifications, collapsible, dns-shared, sysproxy, 3d-effect, icons, rules, node-wheel, window-controls, cache |
| 模块 | 文件 | 行数 | 职责 |
|---|---|---|---|
| connections.js | 连接监控 | ~1134 行 | 实时连接列表、排序、搜索、关闭连接、delta 流量累积追踪 |
| traffic-chart.js | 图表渲染 | ~411 行 | Canvas 2D 贝塞尔曲线面积图 |
| 模块 | 文件 | 行数 | 职责 |
|---|---|---|---|
| sanitize.js | 安全防护 | ~231 行 |
escapeHtml() + escapeAttr() + sanitizeHtml() 白名单 HTML 清理器 |
| logger.js | 日志工具 | ~237 行 | 前端 console 日志分级输出 |
| format.js | 格式化 | — | 文件大小、时间、流量数据格式化 |
| debounce.js | 防抖 | — | 函数防抖 |
| throttle.js | 节流 | — | 函数节流 |
| cleanup-registry.js | 资源清理 | — | 统一资源清理注册表(定时器、WebSocket 等) |
| focus-trap.js | 焦点陷阱 | ~115 行 | 模态框 Tab 循环、Escape 关闭 |
| page-visibility.js | 页面可见性 | ~135 行 | 不可见时暂停轮询/动画,可见时恢复 |
| roving-tabindex.js | 键盘导航 | ~221 行 | 代理列表 Arrow keys + Enter 激活 |
| sequential.js | 顺序执行 | ~78 行 | 同一 key 的请求串行执行,防止竞态 |
| markdown.js | Markdown 渲染 | ~113 行 | 轻量 Markdown→HTML 转换,XSS 安全 |
| color.js | 颜色工具 | — | 颜色转换与处理 |
| intl-cache.js | 国际化缓存 | — | Intl 格式化缓存 |
| array.js | 数组工具 | — | 数组操作辅助函数 |
| virtual-scroll.js | 虚拟滚动 | ~438 行 | 大列表虚拟滚动渲染(Prism 规则库等场景) |
| context-menu.js | 右键菜单 | ~145 行 | 自定义上下文菜单组件 |
| rule-utils.js | 规则工具 | — | 规则格式转换与处理辅助函数 |
| 模块 | 文件 | 职责 |
|---|---|---|
| index.js | 命令名常量 |
COMMANDS 对象(12 个分类),Tauri IPC 命令名的单一来源(SSOT),为未来 tauri-specta 自动生成 IPC 类型做准备 |
前端实现了 LRU 淘汰 + Stale-While-Revalidate 缓存机制,减少不必要的 IPC 调用和 API 请求:
- apiCache:API 响应缓存,LRU 上限 50 条,TTL 2 秒 + SWR 窗口 5 秒,覆盖 config、proxies、settings 等端点
- Stale-While-Revalidate:缓存过期后先返回旧数据,后台静默刷新,用户无感知
- In-flight 去重:同一 key 的并发请求共享同一个 Promise,防止重复调用
- 顺序执行器:确保同一 key 的请求串行执行,防止竞态条件导致数据不一致
- trayMenuCache:托盘菜单缓存,TTL 2 秒,避免频繁重建菜单数据
Zephyr 的加密体系用于保护订阅元数据(metadata.json 中的订阅 URL、流量信息)的存储安全,采用机器指纹派生密钥 + AES-256-GCM 加密方案。
⚠️ 注意:代理配置文件(.yaml)以明文存储,仅依赖文件系统权限(0o600 / DACL)保护。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
subgraph Fingerprint["机器指纹采集"]
FP1["Windows: MachineGuid + C 盘卷序列号"]
FP2["macOS: IOPlatformUUID + IOPlatformSerialNumber"]
FP3["Linux: /etc/machine-id + board_serial"]
end
FP1 --> COMBINE["指纹拼接为单一字符串"]
FP2 --> COMBINE
FP3 --> COMBINE
FP4 --> COMBINE
COMBINE --> SHA["SHA-256 哈希"]
SHA --> PBKDF2["PBKDF2-HMAC-SHA256 密钥派生<br/>盐值: Zephyr_AES256_Key_Derivation<br/>迭代次数: 100,000 次<br/>输出: 32 字节密钥"]
PBKDF2 --> HEX["Hex 编码<br/>64 字符字符串"]
HEX --> PERSIST["持久化密钥<br/>存储到应用数据目录"]
PERSIST --> AES["AES-256-GCM 加密<br/>obfuscate_string()"]
subgraph AESDetail["AES-256-GCM 加密细节"]
NONCE["12 字节随机 Nonce"]
PLAIN["明文数据"]
ENC["加密运算"]
OUTPUT["v2 格式输出"]
end
NONCE --> ENC
PLAIN --> ENC
ENC --> OUTPUT
subgraph V2Format["v2 存储格式"]
V2PREFIX["v2:"]
B64DATA["Base64 编码"]
NONCE_BYTES["Nonce (12 bytes)"]
CIPHER_BYTES["Ciphertext (N bytes)"]
TAG_BYTES["GCM Tag (16 bytes)"]
end
V2PREFIX --> FORMAT["v2: + Base64(Nonce + Ciphertext + Tag)"]
B64DATA --> FORMAT
NONCE_BYTES --> FORMAT
CIPHER_BYTES --> FORMAT
TAG_BYTES --> FORMAT
style Fingerprint fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style PBKDF2 fill:#fff3e0,stroke:#e65100,color:#bf360c
style AES fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style V2Format fill:#fce4ec,stroke:#c62828,color:#b71c1c
v2:<Base64 编码数据>
Base64 数据结构:
+----------+--------------+----------+
| Nonce | Ciphertext | Tag |
| 12 bytes | N bytes | 16 bytes |
+----------+--------------+----------+
|
AES-256-GCM 自动附加 Tag
fn obfuscate_string(plaintext: &str) -> String {
let key = get_machine_key();
let key_bytes = hex::decode(key).expect("Invalid hex key");
let nonce = rand::thread_rng().gen::<[u8; 12]>();
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_bytes));
let nonce = Nonce::from_slice(&nonce);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.expect("Encryption failed");
// v2 格式: "v2:" + base64(nonce || ciphertext_with_tag)
let mut output = nonce.to_vec();
output.extend_from_slice(&ciphertext);
format!("v2:{}", base64::encode(&output))
}以下列出 Zephyr 注册的所有 Tauri Commands(145 个),按功能分为 21 组。其中第 1~12 组为原有命令(64 个),第 13~21 组为 Prism Engine 命令(81 个)。
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
start_core |
config_path, test, custom_args, secret |
Result<CoreStartResult, String> |
启动 Mihomo 内核 |
stop_core |
-- | Result<String, String> |
停止 Mihomo 内核 |
restart_core_as_root_cmd |
-- | Result<(), String> |
以 root 权限重启内核(macOS TUN) |
kill_all_mihomo_as_root_cmd |
-- | Result<(), String> |
以 root 权限清理所有 Mihomo 进程 |
get_core_version |
-- | Result<String, String> |
获取 Mihomo 内核版本 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
list_configs |
-- | Result<Vec<ConfigInfo>, String> |
列出所有订阅配置(含 last_updated、auto_update_interval) |
download_sub |
name, url?, user_agent?, overwrite? |
Result<String, String> |
下载订阅(url 为空时从 metadata 解析) |
download_sub_batch |
items: Vec<BatchUpdateItem>, user_agent? |
Result<Vec<BatchUpdateResult>, String> |
批量下载订阅(绕过单次速率限制) |
delete_config |
name |
Result<String, String> |
删除配置 |
rename_config |
old_name, new_name |
Result<String, String> |
重命名配置文件(自动迁移 last_proxy_selection) |
update_config_url |
name, new_url |
Result<(), String> |
更新配置的订阅 URL(仅 http/https) |
update_subscription_interval |
name, interval |
Result<(), String> |
更新订阅自动更新间隔(0 = 禁用) |
update_proxy_selection |
profile_name, node_name |
Result<(), String> |
原子更新代理节点选择记忆 |
read_config_file |
name |
Result<String, String> |
读取配置文件内容 |
write_config_file |
name, content |
Result<String, String> |
写入配置文件 |
open_config_folder |
-- | Result<(), String> |
打开配置文件夹 |
read_config |
-- | Result<Value, String> |
读取运行配置(移除 secret) |
update_config |
patch |
Result<ConfigUpdateResult, String> |
更新配置并热重载(ConfigUpdateResult = { files_saved, hot_reload_success, message }) |
fetch_text |
url |
Result<String, String> |
通用文本下载 |
read_core_log |
offset?, limit? |
Result<CoreLogResult, String> |
读取 Mihomo 内核日志(增量,上限 2000 行) |
get_scheduler_status |
-- | Result<Value, String> |
获取订阅自动更新调度器状态 |
trigger_auto_update |
-- | Result<usize, String> |
手动触发全部订阅更新,返回成功数量 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
enable_sysproxy |
server, bypass |
Result<String, String> |
启用系统代理 |
disable_sysproxy |
-- | Result<String, String> |
禁用系统代理 |
get_sys_proxy |
-- | Result<bool, String> |
获取当前系统代理状态 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
set_tun_enabled |
enabled |
Result<(), String> |
启用/禁用 TUN 模式 |
release_tun_toggle |
-- | Result<(), String> |
释放 TUN 切换锁 |
disable_tun_cmd |
-- | Result<(), String> |
强制禁用 TUN 模式 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
show_main_window |
-- | () |
显示主窗口 |
change_tray_icon |
status |
Result<(), String> |
更改托盘图标状态 |
get_tray_status |
-- | Result<String, String> |
获取托盘状态 |
update_tray_full_menu |
-- | Result<(), String> |
重建完整托盘菜单 |
get_tray_menu_state |
-- | Result<TrayMenuState, String> |
获取托盘菜单状态 |
set_tray_menu_state |
state |
Result<(), String> |
设置托盘菜单状态 |
get_tray_proxy_status |
-- | Result<String, String> |
获取托盘代理状态 |
update_tray_toggle_states |
-- | Result<(), String> |
更新托盘开关状态 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
get_latest_version |
-- | Result<UpdateInfo, String> |
获取最新内核版本 |
update_core |
url |
Result<CoreStartResult, String> |
执行内核更新 |
update_geo_data |
-- | Result<(), String> |
更新 GeoIP 数据 |
get_latest_client_versions |
-- | Result<ClientVersions, String> |
获取各客户端最新版本 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
get_settings |
-- | Settings |
获取应用设置 |
save_settings |
settings |
Result<(), String> |
保存应用设置 |
exempt_uwp_apps |
-- | Result<(), String> |
UWP 环回免除(仅 Windows) |
is_machine_key_persisted |
-- | bool |
检查机器密钥是否已持久化 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
handle_deep_link |
url |
Result<(), String> |
处理 clash:// URL 协议导入订阅 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
rate_limited_register_shortcut |
action, accelerator |
Result<(), String> |
注册全局快捷键(500ms 冷却) |
rate_limited_unregister_shortcut |
action |
Result<(), String> |
注销全局快捷键(500ms 冷却) |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
rate_limited_send_notification |
title, body |
Result<(), String> |
发送原生 OS 通知(1s 冷却) |
get_latest_client_version |
-- | Result<VersionInfo, String> |
查询 Zephyr 最新版本 |
update_client |
version, asset_url, sha256 |
Result<(), String> |
下载并打开安装包 |
get_app_version |
-- | String |
返回当前版本号 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
update_last_config |
config_name |
Result<(), String> |
原子更新 last_config 设置,避免 Read-Modify-Write 竞态条件 |
patch_settings |
patch |
Result<(), String> |
原子部分更新设置字段,使用 patch_field! 宏逐字段合并 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
override_list |
-- | Result<Vec<OverrideItem>, String> |
列出所有覆写 |
override_create |
item |
Result<OverrideItem, String> |
创建覆写(JS 或 Prism YAML) |
override_update |
id, meta |
Result<(), String> |
更新覆写元数据 |
override_delete |
id |
Result<(), String> |
删除覆写 |
override_get_content |
id |
Result<String, String> |
获取覆写内容 |
override_set_content |
id, content |
Result<(), String> |
设置覆写内容(async) |
override_reorder |
ordered_ids |
Result<(), String> |
重排序覆写 |
override_toggle |
id, enabled |
Result<(), String> |
启用/禁用覆写 |
override_test |
id |
Result<TestResult, String> |
测试单个覆写 |
override_refresh_remote |
id |
Result<String, String> |
刷新远程覆写(async) |
override_apply_all |
-- | Result<ApplyAllResult, String> |
应用所有启用的覆写(async) |
override_export |
ids |
Result<String, String> |
导出覆写为 JSON |
override_import |
data |
Result<Vec<OverrideItem>, String> |
导入覆写 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
prism_apply |
config |
Result<ApplyResult, String> |
应用 Prism 配置 |
prism_validate_config |
config |
Result<bool, String> |
验证 Prism 配置有效性 |
prism_get_status |
-- | Result<PrismStatus, String> |
获取 Prism Engine 状态 |
prism_get_config |
-- | Result<Value, String> |
获取当前 Prism 配置 |
prism_save_config |
config |
Result<(), String> |
保存 Prism 配置到磁盘 |
prism_reset |
-- | Result<(), String> |
重置 Prism Engine |
prism_get_last_trace |
-- | Result<Value, String> |
获取上次 apply 的 trace |
prism_resolve_variables |
config |
Result<Value, String> |
解析模板变量 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
rule_list |
-- | Result<Vec<RuleFileInfo>, String> |
列出所有规则文件 |
rule_read |
name |
Result<String, String> |
读取规则文件内容 |
rule_create |
name, content |
Result<(), String> |
创建规则文件 |
rule_update |
filename, content |
Result<(), String> |
更新规则文件 |
rule_delete |
filename |
Result<(), String> |
删除规则文件 |
rule_rename |
old_filename, new_filename |
Result<(), String> |
重命名规则文件 |
rule_import_text |
name, content, format |
Result<ImportResult, String> |
从文本导入规则 |
rule_import_from_url |
name, url |
Result<ImportResult, String> |
从 URL 导入规则 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
plugin_list |
-- | Result<Vec<PluginInfo>, String> |
列出已加载插件 |
plugin_load |
plugin_id, script, config |
Result<PluginInfo, String> |
加载插件 |
plugin_unload |
plugin_id |
Result<(), String> |
卸载插件 |
plugin_reload |
plugin_id |
Result<PluginInfo, String> |
重新加载插件 |
plugin_get_info |
plugin_id |
Result<PluginInfo, String> |
获取插件信息 |
plugin_call |
plugin_id, method, args |
Result<Value, String> |
调用插件方法 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
script_execute |
script, script_name |
Result<Value, String> |
执行脚本(沙箱内) |
script_validate |
script |
Result<bool, String> |
验证脚本安全性 |
script_get_sandbox |
-- | Result<Value, String> |
获取沙箱配置 |
script_set_sandbox |
allow_network, allow_filesystem, allow_child_process, allow_workers |
Result<(), String> |
设置沙箱配置 |
script_get_limits |
-- | Result<Value, String> |
获取脚本资源限制 |
script_set_limits |
max_execution_time_ms?, max_memory_bytes?, max_loop_iterations?, max_recursion_depth? |
Result<(), String> |
设置脚本资源限制 |
script_grant_plugin |
plugin_id, allow_network, allow_filesystem |
Result<(), String> |
授予插件沙箱权限 |
script_revoke_plugin |
plugin_id |
Result<(), String> |
撤销插件沙箱权限 |
script_check_plugin_permission |
plugin_id, permission |
Result<bool, String> |
检查插件权限 |
script_is_sandbox_safe |
-- | Result<bool, String> |
检查沙箱安全状态 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
smart_get_config |
-- | Result<Value, String> |
获取智能路由配置 |
smart_set_config |
config |
Result<(), String> |
设置智能路由配置 |
smart_get_history |
-- | Result<Value, String> |
获取节点测试历史 |
smart_clear_history |
-- | Result<(), String> |
清除节点测试历史 |
smart_validate_config |
-- | Result<Vec<String>, String> |
验证智能路由配置 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
failover_get_status |
-- | Result<Value, String> |
获取 failover 状态 |
failover_reset |
-- | Result<(), String> |
重置 failover 计数器 |
failover_get_config |
-- | Result<Value, String> |
获取 failover 配置 |
failover_set_config |
config |
Result<(), String> |
设置 failover 配置 |
| 命令 | 用户参数 | 返回类型 | 说明 |
|---|---|---|---|
kv_get |
key |
Result<Option<String>, String> |
读取 KV 值 |
kv_set |
key, value |
Result<(), String> |
写入 KV 值 |
kv_delete |
key |
Result<(), String> |
删除 KV 值 |
kv_list |
-- | Result<Vec<String>, String> |
列出所有 key |
kv_clear |
-- | Result<(), String> |
清空 KV 存储 |
prism_get_trace |
config |
Result<Value, String> |
获取配置 trace(不应用) |
prism_get_rule_groups |
config |
Result<Value, String> |
获取规则分组 |
| 命令 | 冷却时间 | 防护目标 |
|---|---|---|
start_core |
3 秒 | 防止内核频繁启停导致端口冲突 |
download_sub |
5 秒 | 防止订阅下载过于频繁 |
exempt_uwp_apps |
5 分钟 | UWP 操作涉及系统级修改,需较长冷却 |
rate_limited_register_shortcut |
500ms | 防止快捷键注册过于频繁 |
rate_limited_unregister_shortcut |
500ms | 防止快捷键注销过于频繁 |
rate_limited_send_notification |
1 秒 | 防止 OS 通知轰炸 |
Zephyr 使用 Tauri 事件系统实现后端到前端的通知推送,支持双向状态同步。
%%{init: {'themeVariables': {'fontSize': '10px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '10px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 15, 'rankSpacing': 25}}}%%
graph LR
subgraph Backend["Rust 后端"]
E1["config-parse-error"]
E2["profiles-imported"]
E3["tray-sysproxy-changed"]
E4["tray-tun-changed"]
E5["tray-mode-changed"]
E6["tray-subscription-changed"]
E7["tray-proxy-changed"]
E8["core-download-status"]
end
subgraph Frontend["前端事件监听"]
H1["显示错误通知"]
H2["刷新订阅列表"]
H3["同步系统代理开关"]
H4["同步 TUN 开关"]
H5["同步运行模式"]
H6["刷新配置和代理列表"]
H7["更新当前节点显示"]
H8["显示下载状态消息"]
end
E1 -->|"emit"| H1
E2 -->|"emit"| H2
E3 -->|"emit"| H3
E4 -->|"emit"| H4
E5 -->|"emit"| H5
E6 -->|"emit"| H6
E7 -->|"emit"| H7
E8 -->|"emit"| H8
style Backend fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style Frontend fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
| # | 事件名 | 触发场景 | 前端响应 | Payload |
|---|---|---|---|---|
| 1 | config-parse-error |
配置文件解析失败 | 显示错误通知 |
String (错误信息) |
| 2 | profiles-imported |
拖拽导入 YAML 配置完成 | 刷新订阅列表 |
number (导入数量) |
| 3 | tray-sysproxy-changed |
托盘菜单切换系统代理 | 同步 UI 开关状态 | bool |
| 4 | tray-tun-changed |
托盘菜单切换 TUN 模式 | 同步 UI 开关状态 | bool |
| 5 | tray-mode-changed |
托盘菜单切换运行模式 | 同步 UI 模式选择器 |
String (mode) |
| 6 | tray-subscription-changed |
托盘菜单切换订阅 | 刷新配置和代理列表 |
String (配置名) |
| 7 | tray-proxy-changed |
托盘菜单切换代理节点 | 更新当前节点显示 | {group, proxy} |
| 8 | core-download-status |
内核/GeoIP 下载状态变化 | 显示下载状态消息 |
String (状态文本) |
import { listen } from './api.js';
// 监听托盘代理切换
listen("tray-proxy-changed", (event) => {
const { group, proxy } = event.payload;
updateActiveProxy(group, proxy);
});
// 监听内核下载状态(统一事件,覆盖内核和 GeoIP 下载)
listen("core-download-status", (event) => {
showDownloadStatus(event.payload);
});
// 监听配置解析错误
listen("config-parse-error", (event) => {
showNotification("error", `配置解析失败: ${event.payload}`);
});以下是 Zephyr 后端的核心数据结构定义。
/// Mihomo 进程运行时状态,通过 Tauri State 注入,全局共享
pub struct CoreData {
pub process: Option<Child>, // 子进程句柄
pub last_secret: String, // 上次生成的 API Secret (32字符字母数字)
pub last_config_path: Option<String>, // 上次使用的配置文件路径
pub last_custom_args: Option<Vec<String>>, // 上次使用的自定义参数
pub last_port: Option<u16>, // 上次使用的 API 端口
}/// 应用运行时所需的各类路径
pub struct AppPaths {
pub app_data_dir: PathBuf, // 应用数据目录 (配置、缓存等)
pub core_dir: PathBuf, // 内核可执行文件目录
pub profiles_dir: PathBuf, // 订阅配置文件目录
}/// 订阅配置的元信息,用于列表展示和管理
pub struct ConfigInfo {
pub name: String, // 配置名称
#[serde(skip_serializing)]
pub url: Option<String>, // 订阅 URL (AES-256-GCM 加密存储)
pub sub_info: Option<String>, // 订阅流量信息 (原始字符串)
}/// 应用全局设置,序列化为 JSON 持久化存储
pub struct Settings {
pub close_to_tray: bool, // 关闭时最小化到托盘
pub auto_update: bool, // 自动更新内核
pub autostart: bool, // 开机自启
pub theme: Option<String>, // 主题标识
pub last_config: Option<String>, // 上次使用的配置名
#[serde(default)]
pub custom_args: Vec<String>, // 自定义启动参数
#[serde(default)]
pub dns_nameservers: Option<Vec<String>>, // 自定义 DNS 服务器
#[serde(default)]
pub dns_fallbacks: Option<Vec<String>>, // DNS 回退服务器
}/// 系统托盘菜单的动态状态,用于前后端双向同步
pub struct TrayMenuState {
pub sys_proxy_enabled: bool, // 系统代理是否启用
pub tun_enabled: bool, // TUN 模式是否启用
pub current_mode: String, // 当前运行模式 (rule/global/direct)
pub active_config: Option<String>, // 当前激活的配置名称
pub active_proxy: Option<String>, // 当前选中的代理节点
}/// 内核启动结果
pub struct CoreStartResult {
pub secret: String, // API Secret
pub port: u16, // API 端口
}
/// 版本更新信息
pub struct UpdateInfo {
pub version: String, // 最新版本号
pub download_url: String, // 下载地址
}%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TD
subgraph TauriState["Tauri State (全局注入)"]
MS["MihomoState"]
TS["TrayState"]
RL["RateLimiter"]
end
subgraph CoreStructs["核心数据结构"]
CD["CoreData<br/>process / secret / port<br/>config_path / custom_args"]
AP["AppPaths<br/>app_data_dir / core_dir<br/>profiles_dir"]
CI["ConfigInfo<br/>name / url<br/>sub_info"]
ST["Settings<br/>close_to_tray / auto_update<br/>theme / dns..."]
TMS["TrayMenuState<br/>sys_proxy / tun<br/>mode / config / proxy"]
end
subgraph ResultStructs["返回值结构"]
CSR["CoreStartResult<br/>secret + port"]
UI["UpdateInfo<br/>version + download_url"]
end
MS --> CD
MS --> AP
TS --> TMS
CD --> CSR
CI --> UI
style TauriState fill:#fff3e0,stroke:#e65100,color:#bf360c
style CoreStructs fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style ResultStructs fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
Zephyr 中存在三种核心数据流,分别对应命令调用、配置写入和流量监控场景。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TD
subgraph CommandFlow["命令通路 (请求-响应模式)"]
direction LR
CF1["用户操作<br/>UI 事件"] --> CF2["api.js<br/>Tauri invoke()"]
CF2 --> CF3["Tauri IPC<br/>命令分发"]
CF3 --> CF4["Rust Handler<br/>业务逻辑"]
CF4 --> CF5["Mihomo Core<br/>/ 文件系统"]
CF5 --> CF6["Result 返回<br/>原路回传"]
CF6 --> CF7["ViewModel<br/>更新状态"]
CF7 --> CF8["View<br/>DOM 重渲染"]
end
subgraph ConfigFlow["配置写入通路 (热重载模式)"]
direction LR
CG1["用户修改配置<br/>UI 表单"] --> CG2["update_config()<br/>api.js 调用"]
CG2 --> CG3["JSON → YAML<br/>serde_yaml 转换"]
CG3 --> CG4["merge_yaml()<br/>递归深度合并"]
CG4 --> CG5["写入文件<br/>run_config.yaml"]
CG5 --> CG6["PATCH /configs<br/>Mihomo 热重载 API"]
CG6 --> CG7["200 OK<br/>配置生效"]
end
subgraph TrafficFlow["流量监控通路 (推送流模式)"]
direction LR
TF1["Mihomo Core<br/>代理流量"] --> TF2["GET /traffic<br/>HTTP Streaming"]
TF2 --> TF3["websocket.js<br/>ReadableStream"]
TF3 --> TF4["滑动窗口<br/>60 数据点缓存"]
TF4 --> TF5["modules/traffic-chart.js<br/>Canvas 2D 渲染"]
TF5 --> TF6["requestAnimationFrame<br/>节流 60fps"]
end
style CommandFlow fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style ConfigFlow fill:#fff3e0,stroke:#e65100,color:#bf360c
style TrafficFlow fill:#fce4ec,stroke:#c62828,color:#b71c1c
Rust 后端由 8 个模块组成,各司其职,共同构成 Zephyr 的核心引擎。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TB
LIB["lib.rs<br/>应用入口<br/>命令注册<br/>状态管理"] --> CM["apps/desktop/src-tauri/src/core_manager.rs<br/>→ core/ 模块<br/>(9 个子模块)"]
LIB --> PRISM["apps/desktop/src-tauri/src/prism.rs<br/>→ prism/ 模块<br/>(19 个子文件,4700+ 行)<br/>Prism Engine 集成"]
BE["apps/desktop/src-tauri/src/backend_event.rs<br/>后端事件系统<br/>结构化日志 + 错误码<br/>路径脱敏 + 前端推送"]
DL["apps/desktop/src-tauri/src/deep_link.rs<br/>Deep Link<br/>URL 协议"]
GS["apps/desktop/src-tauri/src/global_shortcut.rs<br/>全局快捷键"]
ON["apps/desktop/src-tauri/src/os_notification.rs<br/>系统通知"]
UP["apps/desktop/src-tauri/src/updater.rs<br/>内核更新 + 客户端更新<br/>GeoIP 更新<br/>版本检查"]
LIB --> TR["apps/desktop/src-tauri/src/tray.rs<br/>系统托盘<br/>菜单构建<br/>事件分发"]
CM --> CFG["config_manager.rs<br/>配置读写<br/>YAML 合并<br/>热重载"]
CM --> SP["sys_proxy.rs<br/>系统代理<br/>跨平台适配"]
TR --> SP
subgraph PlatformSpecific["平台专属模块"]
UWP["uwp_loopback.rs<br/>UWP 环回免除<br/>仅 Windows"]
end
LIB --> UWP
subgraph TauriPlugins["Tauri 系统插件"]
AS["autostart<br/>开机自启"]
DL["dialog<br/>对话框"]
OP["opener<br/>外部链接"]
end
LIB --> AS
LIB --> DL
LIB --> OP
style LIB fill:#fff3e0,stroke:#e65100,color:#bf360c
style CM fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style CFG fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style SP fill:#fce4ec,stroke:#c62828,color:#b71c1c
style TR fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c
style UP fill:#e0f7fa,stroke:#00695c,color:#004d40
style UWP fill:#efebe9,stroke:#4e342e,color:#3e2723
style PRISM fill:#e8eaf6,stroke:#283593,color:#1a237e
lib.rs 是整个 Rust 后端的入口文件,负责应用初始化、命令注册、状态管理和生命周期控制。
| 职责 | 说明 |
|---|---|
| 应用入口 |
run() 函数构建 Tauri 应用并启动事件循环 |
| 命令注册 | 注册 145 个 Tauri Commands 到 Tauri IPC |
| 状态管理 | 初始化并注入 MihomoState、TrayState、RateLimiter
|
| 窗口事件 | 处理关闭到托盘、拖拽导入等窗口级事件 |
| Panic Hook | 全局 panic 捕获,确保异常时清理 Mihomo 进程 |
| 插件集成 | 加载 autostart、dialog、opener 等 Tauri 插件 |
以下流程图展示了 lib.rs 中 run() 函数的完整初始化过程。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["run() 调用"] --> B["加载 Tauri 插件<br/>autostart / dialog / opener"]
B --> C["设置 Panic Hook<br/>捕获全局异常<br/>异常时清理 Mihomo 进程"]
C --> D["初始化 MihomoState<br/>CoreData (进程状态)<br/>+ AppPaths (路径集合)"]
D --> E["初始化 TrayState<br/>TrayMenuState (托盘菜单状态)"]
E --> F["初始化 RateLimiter<br/>命令限流器"]
F --> G["注册 145 个 Tauri Commands<br/>核心管理 / 配置管理<br/>系统代理 / TUN 控制<br/>系统托盘 / Deep Link<br/>全局快捷键 / 系统通知<br/>客户端更新 / 设置<br/>覆写系统 / Prism Engine(81 个命令)"]
G --> H["设置窗口事件<br/>CloseRequested → 隐藏到托盘<br/>DragDrop → 导入 YAML 配置"]
H --> I{"平台检测"}
I -->|"Windows / macOS"| J["创建无边框窗口<br/>自定义标题栏 + 拖拽区域"]
I -->|"Linux"| K["创建原生窗口<br/>系统窗口装饰"]
J --> L["启动事件循环<br/>窗口显示 + 前端加载"]
K --> L
style A fill:#fff3e0,stroke:#e65100,color:#bf360c
style B fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style C fill:#fce4ec,stroke:#c62828,color:#b71c1c
style D fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style E fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style F fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c
style G fill:#e0f7fa,stroke:#00695c,color:#004d40
style H fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style J fill:#fff3e0,stroke:#e65100,color:#bf360c
style K fill:#efebe9,stroke:#4e342e,color:#3e2723
style L fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
core_manager.rs 原为 3090 行的单体文件,已拆分为 core/ 模块(9 个子模块),自身缩减为 67 行的纯 re-export 门面:
| 子模块 | 行数 | 职责 |
|---|---|---|
core_process.rs |
~1200 | 进程管理(启动/停止/版本检测/二进制替换/健康检查/端口等待) |
crypto.rs |
~515 | AES-256-GCM 加密、PBKDF2 密钥派生、机器指纹 |
subscription.rs |
~472 | 订阅下载、SSRF 防护、DNS pinning |
tun_manager.rs |
~461 | TUN 模式管理、权限提升、进程清理 |
config_manager.rs |
~262 | 配置文件 CRUD、列表、URL 获取 |
config_sanitizer.rs |
~191 | 文件名清理、危险 YAML 键移除 |
secure_io.rs |
~178 | 安全文件写入(Unix 0o600 + Windows DACL) |
core_log.rs |
~64 | Mihomo 内核日志增量读取 |
mod.rs |
~96 | 模块门面、类型定义、全局原子变量、re-export |
// core_manager.rs — 67 行门面
pub mod core;
pub use core::*;prism.rs(290 行)是 Prism Engine 的入口模块,桥接 clash-prism-* crate 到 Zephyr。prism/ 子目录包含 19 个子文件(共 4,700+ 行),实现完整的规则引擎、插件系统、脚本沙箱、智能路由、覆写系统和速率限制。
| 子模块 | 行数 | 职责 |
|---|---|---|
commands_core.rs |
~298 | Prism 核心命令(apply/validate/status/config/watch/trace) |
rule_library.rs |
~422 | 规则库 CRUD、订阅导入、YAML 清洗 |
smart_commands.rs |
~258 | 智能节点选择(配置/历史/验证/EMA 评分) |
smart_state.rs |
~411 | Smart State 异步持久化(WAL + DashMap + mpsc) |
smart_state_tests.rs |
~375 | Smart State 单元测试 |
plugin_commands.rs |
~247 | 插件加载/卸载/调用/权限检查 |
script_commands.rs |
~216 | 脚本沙箱(执行/验证/权限/资源限制) |
overrides.rs |
~30 | 覆写系统入口模块 |
overrides/overrides_model.rs |
~129 | 覆写数据模型(OverrideItem/OverrideMeta/OverrideLog) |
overrides/overrides_store.rs |
~333 | 覆写存储层(meta.json 序列化、CRUD) |
overrides_commands.rs |
~786 | 覆写系统 IPC 命令(14 个命令) |
pipeline.rs |
~368 | 覆写执行管道(批量应用、热重载、测试) |
types.rs |
~335 | 类型定义、输入验证(sanitize_filename/validate_plugin_id/check_input_size) |
host.rs |
~297 | PrismHost trait 实现(桥接 Zephyr 状态到 Prism Engine) |
rate_limiter.rs |
~130 | 滑动窗口速率限制器(script_execute 10次/10s、rule_import_url 5次/10s) |
rule_groups.rs |
~122 | 规则分组解析 |
failover_commands.rs |
~87 | 自动故障转移 |
kv_commands.rs |
~47 | 键值存储 |
trace_commands.rs |
~25 | 配置 trace |
此外,prism_tests.rs(1,361 行,119 个测试)提供完整的单元测试覆盖。
config_manager.rs 负责 Mihomo 配置文件的读取、合并、更新与热重载。
sys_proxy.rs 负责在各平台上配置和清除系统代理设置,确保代理流量正确路由。
tray.rs 管理系统托盘图标和菜单,实现后端与前端的双向状态同步。
updater.rs 负责检查和执行 Mihomo 内核、GeoIP 数据和客户端版本的更新。
以下流程图展示了 Mihomo 内核自动更新的完整流程,从版本检查到二进制替换的全链路。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["get_latest_version()"] --> B["请求 GitHub API<br/>获取最新 Release 信息"]
B --> C["解析 GitHub API 响应<br/>提取 version / download_url"]
C --> D{"与当前版本比较<br/>semver 对比"}
D -->|"已是最新"| DONE1["返回无需更新"]
D -->|"有新版本"| E["解析下载 URL<br/>匹配平台与架构"]
E --> F{"URL 域名验证<br/>可信域名白名单"}
F -->|"不可信域名"| ERR1["返回错误<br/>Untrusted Download URL"]
F -->|"验证通过"| G["download_release_asset()<br/>HTTP 流式下载"]
G --> H["emit 下载状态事件<br/>core-download-status"]
H --> I{"下载完成?"}
I -->|"失败"| ERR2["返回错误<br/>Download Failed"]
I -->|"成功"| J["verify_sha256()<br/>校验文件完整性"]
J --> K{"SHA256 校验通过?"}
K -->|"失败"| ERR3["返回错误<br/>Checksum Mismatch"]
K -->|"通过"| L["extract_core_binary()<br/>解压 ZIP/TAR 包"]
L --> M{"路径遍历检查<br/>Zip Slip 防护"}
M -->|"检测到恶意路径"| ERR4["返回错误<br/>Path Traversal Detected"]
M -->|"安全"| N["stop_core()<br/>停止当前 Mihomo 进程"]
N --> O["替换二进制文件<br/>rename() 原子替换<br/>最多重试 5 次"]
O --> P{"替换成功?"}
P -->|"失败"| ERR5["返回错误<br/>Binary Replace Failed"]
P -->|"成功"| Q["restart_core()<br/>重启 Mihomo 内核"]
Q --> R["emit 完成事件<br/>core-download-status"]
R --> DONE2["更新完成"]
style ERR1 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR2 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR3 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR4 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style ERR5 fill:#ffcdd2,stroke:#c62828,color:#b71c1c
style DONE1 fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
style DONE2 fill:#c8e6c9,stroke:#2e7d32,color:#1b5e20
style A fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style G fill:#fff3e0,stroke:#e65100,color:#bf360c
style L fill:#e0f7fa,stroke:#00695c,color:#004d40
style Q fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
uwp_loopback.rs 是 Windows 平台专属模块,用于为 UWP 应用添加环回网络免除规则。
Zephyr 前端采用原生 JavaScript 构建,无任何前端框架依赖,采用模块化架构,项目结构为 pnpm monorepo(apps/desktop + packages/shared),由 67 个 ES Module 文件组成(5 个核心模块 + 33 个 UI 模块 + 2 个功能模块 + 17 个工具模块 + 1 个共享包)。
api.js 是前端与后端通信的桥梁,封装了所有 Tauri invoke 调用和 Mihomo REST API 请求。
// api.js — Tauri IPC 桥接层(基于 __TAURI_INTERNALS__)
const _t = window.__TAURI_INTERNALS;
export const invoke = (cmd, args) => _t?.invoke(cmd, args);
export const listen = (event, handler) => { /* ... */ };
let API_BASE = "http://127.0.0.1:9090";
let API_SECRET = "";
// Tauri Command 调用
export async function startCore() {
return invoke("start_core");
}
// Mihomo REST API 调用
export async function getProxies() {
const res = await fetch(`${API_BASE}/proxies`, {
headers: { Authorization: `Bearer ${API_SECRET}` }
});
return res.json();
}以下展示了前端核心模块之间的导入依赖关系。connections.js 和 logs.js 采用懒加载策略——不在 initApp() 中直接初始化,而是通过动态 import() 按需加载,离开页面时通过 destroy 函数清理定时器和状态。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TD
MAIN["main.js<br/>应用入口<br/>~225 行"] --> API["api.js<br/>IPC 桥接 + API<br/>~504 行"]
MAIN --> I18N["i18n.js<br/>国际化<br/>~1019 行"]
MAIN --> WS["websocket.js<br/>流量监控<br/>~406 行"]
MAIN --> UI["apps/desktop/src/ui/<br/>33 个 UI 模块"]
MAIN --> CONN["modules/connections.js<br/>连接监控(懒加载)<br/>~1134 行"]
MAIN --> LOGS["ui/logs.js<br/>日志查看(懒加载)<br/>~692 行"]
UI -->|"import"| API
UI -->|"import"| RULES["rules.js<br/>规则转换<br/>~98 行"]
UI -->|"import"| UTILS["apps/desktop/src/utils/<br/>17 个工具模块"]
CONN -->|"import"| API
LOGS -->|"import"| API
style MAIN fill:#fff3e0,stroke:#e65100,color:#bf360c
style API fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style I18N fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c
style WS fill:#fce4ec,stroke:#c62828,color:#b71c1c
style UI fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style CONN fill:#e0f2f1,stroke:#00695c,color:#004d40
style LOGS fill:#e0f2f1,stroke:#00695c,color:#004d40
style RULES fill:#efebe9,stroke:#4e342e,color:#3e2723
style UTILS fill:#fff8e1,stroke:#f57f17,color:#e65100
Zephyr 使用 Rust 的原子类型和互斥锁确保多线程安全。
static OPERATION_LOCK: AtomicBool = AtomicBool::new(false);
async fn guarded_operation() -> Result<(), String> {
// 尝试获取锁
if OPERATION_LOCK
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return Err("Operation already in progress".to_string());
}
// 设置超时自动释放(防止死锁)
let timeout_handle = tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(10)).await;
OPERATION_LOCK.store(false, Ordering::Release);
});
let result = do_operation().await;
// 释放锁
OPERATION_LOCK.store(false, Ordering::Release);
timeout_handle.abort();
result
}Zephyr 的完整生命周期从进程启动到退出清理,涵盖初始化、运行和退出三个阶段。
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TD
subgraph Startup["启动阶段"]
S1["Rust 初始化<br/>加载插件 + 注册 State<br/>注册 Commands + Panic Hook"]
S2["窗口创建<br/>无边框 / Linux 原生装饰"]
S3["前端初始化 (initApp)<br/>UI 设置 → 启动核心<br/>模块初始化 → 事件监听"]
S4["系统托盘初始化<br/>Show + Quit"]
S1 --> S2 --> S3 --> S4
end
subgraph Running["运行阶段"]
R1["命令处理<br/>invoke → Command → 返回"]
R2["事件推送<br/>emit → listen → UI 更新"]
R3["周期同步 (10s)<br/>syncCoreConfig + updateTrayStatus + updateTrayMenu"]
R4["流量监控<br/>HTTP Streaming → Canvas"]
R5["限流清理 (60s)<br/>RateLimiter 过期记录"]
R6["窗口事件<br/>CloseRequested / DragDrop"]
end
subgraph Shutdown["退出阶段"]
Q1{"close_to_tray?"}
Q2["window.hide()<br/>后台继续运行"]
Q3["kill_mihomo()<br/>终止管理进程"]
Q4["smart_kill_all_mihomo_as_root()<br/>清理残留进程"]
Q5["进程退出"]
Q1 -->|"是"| Q2
Q1 -->|"否"| Q3 --> Q4 --> Q5
end
Startup --> Running --> Shutdown
style Startup fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
style Running fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
style Shutdown fill:#fce4ec,stroke:#c62828,color:#b71c1c
返回 Home