Skip to content

Architecture

Juwan-Hwang edited this page Apr 17, 2026 · 27 revisions

系统架构

Zephyr 采用 Tauri v2 混合架构,以 Rust 后端为核心引擎、原生 JavaScript 前端为交互层、Mihomo 内核为代理执行层,三层协同工作,实现高性能、低资源占用的代理管理体验。


目录


架构总览

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["src/ui/<br/>24 个 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/>核心进程管理"]
        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
Loading

设计原则

原则 实现方式
零前端框架 原生 JavaScript + DOM 操作,无 React/Vue/Svelte 依赖
零图表库 原生 Canvas 2D API 绘制流量面积图
内存安全 Rust 所有权系统 + 编译期检查,无 GC 停顿
纵深防御 SSRF 防护、命令限流、危险配置清除、AES-256-GCM 加密
跨平台一致 单一代码库覆盖 Windows / macOS / Linux,差异化能力精准适配

MVVM 架构交互时序图

以下时序图展示了 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: 界面重新渲染
Loading

整体数据流

%%{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&lt;T, String&gt;<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
Loading

核心启动流程

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()"]
    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/>最多 20 次 x 1000ms"]
    S --> T{"健康检查<br/>是否通过?"}
    T -->|"失败"| U["kill_mihomo()<br/>返回错误"]
    T -->|"成功"| V["更新 CoreData 状态<br/>返回 secret + port"]

    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
Loading

全局原子变量

变量 类型 用途
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})
    TI->>CM: download_sub(url, name, user_agent)

    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)
Loading

三重下载策略

Zephyr 实现了三重下载策略,确保在各种网络环境下都能成功获取订阅配置:

  1. 第一优先级:直连下载 -- 适用于无代理环境
  2. 第二优先级:通过 Mihomo 代理下载 -- 适用于已启动代理的环境
  3. 第三优先级:通过系统代理下载 -- 作为最后的回退方案

SSRF 防护

  • 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()
Loading

merge_yaml 递归合并算法

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
Loading

安全验证逻辑

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
Loading

前端模块职责

Zephyr 前端采用模块化架构,将 UI 逻辑拆分为独立的 ES Module 文件。以下列出核心模块:

核心模块(src/ 根目录)

模块 文件 行数 职责
main.js 入口 ~225 行 应用初始化编排,协调各模块启动顺序,懒加载日志页面
api.js API 层 ~504 行 Tauri IPC 桥接(__TAURI_INTERNALS__)+ Mihomo REST API 封装
websocket.js 流量监控 ~406 行 HTTP Streaming 连接管理、数据解析、指数退避重连
i18n.js 国际化 ~1019 行 英文/中文翻译,运行时语言切换
rules.js 规则转换 ~98 行 Shadowrocket 规则转 Clash 规则格式转换

UI 模块(src/ui/)

模块 文件 行数 职责
settings.js 设置管理 ~1497 行 设置页面所有交互逻辑
proxies.js 代理管理 ~827 行 代理组、节点选择、延迟测试
logs.js 日志查看 ~692 行 Mihomo 内核日志实时查看、搜索、自动滚动
advanced.js 高级设置 ~506 行 自定义参数、动态配置引擎
state.js 状态管理 ~352 行 基于 Proxy 的响应式状态管理,localStorage 持久化
events.js 事件总线 ~214 行 EventBus 事件系统,通配符监听,内存泄漏防护
navigation.js 导航控制 页面路由与 Tab 切换
tun.js TUN 控制 TUN 模式切换逻辑
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

功能模块(src/modules/)

模块 文件 行数 职责
connections.js 连接监控 ~1134 行 实时连接列表、排序、搜索、关闭连接、delta 流量累积追踪
traffic-chart.js 图表渲染 ~411 行 Canvas 2D 贝塞尔曲线面积图

工具模块(src/utils/)

模块 文件 行数 职责
sanitize.js 安全防护 ~231 行 escapeHtml() + sanitizeHtml() 白名单 HTML 清理器
logger.js 日志工具 ~237 行 前端 console 日志分级输出
format.js 格式化 文件大小、时间、流量数据格式化
debounce.js 防抖 函数防抖
throttle.js 节流 函数节流
cleanup-registry.js 资源清理 统一资源清理注册表(定时器、WebSocket 等)
color.js 颜色工具 颜色转换与处理
intl-cache.js 国际化缓存 Intl 格式化缓存
array.js 数组工具 数组操作辅助函数

缓存系统

前端实现了两级缓存机制,减少不必要的 IPC 调用和 API 请求:

  • apiCache:API 响应缓存,TTL 2 秒,覆盖 config、proxies、settings 等端点
  • trayMenuCache:托盘菜单缓存,TTL 2 秒,避免频繁重建菜单数据

加密体系

Zephyr 的加密体系用于保护敏感数据(订阅 URL、认证信息等)的存储安全,采用机器指纹派生密钥 + AES-256-GCM 加密方案。

%%{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
Loading

加密数据格式

v2:<Base64 编码数据>

Base64 数据结构:
+----------+--------------+----------+
|  Nonce   |  Ciphertext  |   Tag    |
| 12 bytes |  N bytes     | 16 bytes |
+----------+--------------+----------+
           |
    AES-256-GCM 自动附加 Tag

obfuscate_string 实现

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))
}

Tauri Commands 分类表

以下列出 Zephyr 注册的所有 Tauri Commands(38 个),按功能分为 8 组。

1. 核心管理 (Core Management)

命令 用户参数 返回类型 说明
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 内核版本

2. 配置管理 (Configuration)

命令 用户参数 返回类型 说明
list_configs -- Result<Vec<ConfigInfo>, String> 列出所有订阅配置
download_sub url, name, user_agent Result<String, String> 下载订阅
get_config_url name Result<String, String> 获取配置的订阅 URL
delete_config name Result<String, 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 行)

3. 系统代理 (System Proxy)

命令 用户参数 返回类型 说明
enable_sysproxy server, bypass Result<String, String> 启用系统代理
disable_sysproxy -- Result<String, String> 禁用系统代理
get_sys_proxy -- Result<bool, String> 获取当前系统代理状态

4. TUN 控制 (TUN Control)

命令 用户参数 返回类型 说明
set_tun_enabled enabled Result<(), String> 启用/禁用 TUN 模式
release_tun_toggle -- Result<(), String> 释放 TUN 切换锁
disable_tun_cmd -- Result<(), String> 强制禁用 TUN 模式

5. 系统托盘 (System Tray)

命令 用户参数 返回类型 说明
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> 更新托盘开关状态

6. 更新 (Updater)

命令 用户参数 返回类型 说明
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> 获取各客户端最新版本

7. 设置与安全 (Settings & Security)

命令 用户参数 返回类型 说明
get_settings -- Settings 获取应用设置
save_settings settings Result<(), String> 保存应用设置
exempt_uwp_apps -- Result<(), String> UWP 环回免除(仅 Windows)
is_machine_key_persisted -- bool 检查机器密钥是否已持久化

8. 命令限流矩阵

命令 冷却时间 防护目标
start_core 3 秒 防止内核频繁启停导致端口冲突
download_sub 5 秒 防止订阅下载过于频繁
exempt_uwp_apps 5 分钟 UWP 操作涉及系统级修改,需较长冷却

事件系统

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
Loading

事件详细说明

# 事件名 触发场景 前端响应 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 后端的核心数据结构定义。

CoreData -- Mihomo 进程运行时状态

/// 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 端口
}

AppPaths -- 应用路径集合

/// 应用运行时所需的各类路径
pub struct AppPaths {
    pub app_data_dir: PathBuf,  // 应用数据目录 (配置、缓存等)
    pub core_dir: PathBuf,      // 内核可执行文件目录
    pub profiles_dir: PathBuf,  // 订阅配置文件目录
}

ConfigInfo -- 配置元信息

/// 订阅配置的元信息,用于列表展示和管理
pub struct ConfigInfo {
    pub name: String,                       // 配置名称
    #[serde(skip_serializing)]
    pub url: Option<String>,                // 订阅 URL (AES-256-GCM 加密存储)
    pub sub_info: Option<String>,           // 订阅流量信息 (原始字符串)
}

Settings -- 持久化配置

/// 应用全局设置,序列化为 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 回退服务器
}

TrayMenuState -- 托盘菜单状态

/// 系统托盘菜单的动态状态,用于前后端双向同步
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
Loading

数据流设计

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
Loading

Rust 后端模块详解

Rust 后端由 7 个模块组成,各司其职,共同构成 Zephyr 的核心引擎。

模块依赖关系

%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TB
    LIB["lib.rs<br/>应用入口<br/>命令注册<br/>状态管理"] --> CM["core_manager.rs<br/>核心进程管理<br/>订阅下载<br/>加密存储"]
    LIB --> TR["tray.rs<br/>系统托盘<br/>菜单构建<br/>事件分发"]
    LIB --> UP["updater.rs<br/>内核更新<br/>GeoIP 更新<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
Loading

lib.rs -- 应用入口与生命周期

lib.rs 是整个 Rust 后端的入口文件,负责应用初始化、命令注册、状态管理和生命周期控制。

职责 说明
应用入口 run() 函数构建 Tauri 应用并启动事件循环
命令注册 注册 38 个 Tauri Commands 到 Tauri IPC
状态管理 初始化并注入 MihomoStateTrayStateRateLimiter
窗口事件 处理关闭到托盘、拖拽导入等窗口级事件
Panic Hook 全局 panic 捕获,确保异常时清理 Mihomo 进程
插件集成 加载 autostart、dialog、opener 等 Tauri 插件

Tauri 应用入口详细流程图

以下流程图展示了 lib.rsrun() 函数的完整初始化过程。

%%{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["注册 38 个 Tauri Commands<br/>核心管理 / 配置管理<br/>系统代理 / TUN 控制<br/>系统托盘 / 更新 / 设置"]
    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
Loading

core_manager.rs -- 核心进程管理

core_manager.rs 是整个后端中最大、最核心的模块(约 3084 行),负责 Mihomo 进程的完整生命周期管理。

config_manager.rs -- 配置管理

config_manager.rs 负责 Mihomo 配置文件的读取、合并、更新与热重载。

sys_proxy.rs -- 系统代理

sys_proxy.rs 负责在各平台上配置和清除系统代理设置,确保代理流量正确路由。

tray.rs -- 系统托盘

tray.rs 管理系统托盘图标和菜单,实现后端与前端的双向状态同步。

updater.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
Loading

uwp_loopback.rs -- UWP 环回免除

uwp_loopback.rs 是 Windows 平台专属模块,用于为 UWP 应用添加环回网络免除规则。


前端架构

Zephyr 前端采用原生 JavaScript 构建,无任何前端框架依赖,采用模块化架构,由 40 个 ES Module 文件组成(5 个核心模块 + 24 个 UI 模块 + 2 个功能模块 + 9 个工具模块)。

api.js -- API 调用层

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.jslogs.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["src/ui/<br/>24 个 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["src/utils/<br/>9 个工具模块"]
    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
Loading

并发安全与原子变量

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
Loading

返回 Home

Clone this wiki locally