# 《WaLiAPI - 本地 LLM API 网关》第2-5节:设置中心与打包部署

作者:小傅哥
博客:https://bugstack.cn (opens new window)

沉淀、分享、成长,让自己和他人都能有所收获!😄

大家好,我是技术UP主小傅哥。

这是本课程的最后一节。我们要把 WaLiAPI 打磨成一个"真正的桌面软件"——有系统托盘、能开机自启、配置持久化,最后打包成各平台的安装包发布出去。

# 一、本章诉求

  1. 实现基于 Tauri Store 的配置管理系统
  2. 实现系统托盘(托盘菜单、点击恢复窗口、关闭到托盘)
  3. 实现开机自启
  4. 实现主题切换
  5. 打包发布各平台安装包

# 二、配置管理系统

# 2.1 存储方案选型

WaLiAPI 中有两种配置存储,分别适用不同场景:

方案 存储位置 适用数据
SQLite app_data_dir/waliapi.db 结构化业务数据(渠道、密钥、日志、规则)
Tauri Store app_data_dir/settings.json 键值对应用设置(端口、主题、开关)

为什么设置不用 SQLite?因为设置是扁平键值对语义(server.portui.theme),用 SQL 表存 KV 反而别扭(要设计 key-value 表、做类型转换)。Tauri Store 插件就是为这个场景设计的:JSON 文件持久化 + 类型化读取 + 前后端都能访问。

# 2.2 后端读取配置

use tauri_plugin_store::StoreExt;

// 读取单个配置项的通用模式
fn get_server_port(app: &AppHandle) -> u16 {
    if let Ok(store) = app.store("settings.json") {
        if let Some(port) = store.get("server.port") {
            if let Some(value) = port.as_u64() {
                return value as u16;
            }
        }
    }
    8777  // 默认值
}
1
2
3
4
5
6
7
8
9
10
11
12
13

配置键的命名规范是 点分层级server.portsecurity.enabledretry.timesgeneral.close_to_tray

# 2.3 设置的命令层

src-tauri/src/commands/settings.rs 核心逻辑:

#[derive(Debug, Serialize, Deserialize)]
pub struct SettingsDto {
    pub server_port: u16,
    pub server_host: String,
    pub ui_theme: String,
    pub ui_language: String,
    pub minimize_to_tray: bool,
    pub close_to_tray: bool,
    pub auto_start: bool,
    pub retry_enabled: bool,
    pub retry_times: i64,
    pub security_enabled: bool,
    pub security_mode: String,
    // ... 安全相关开关
}

#[tauri::command]
pub async fn get_settings(app: AppHandle) -> Result<SettingsDto, String> {
    let store = app.store("settings.json").map_err(|e| e.to_string())?;
    Ok(SettingsDto {
        server_port: store.get("server.port").and_then(|v| v.as_u64()).unwrap_or(8777) as u16,
        server_host: store.get("server.host").and_then(|v| v.as_str().map(String::from))
            .unwrap_or_else(|| "127.0.0.1".to_string()),
        ui_theme: store.get("ui.theme").and_then(|v| v.as_str().map(String::from))
            .unwrap_or_else(|| "dark".to_string()),
        // ... 其余字段,全部带默认值
    })
}

#[tauri::command]
pub async fn save_settings(app: AppHandle, settings: SettingsDto) -> Result<(), String> {
    let store = app.store("settings.json").map_err(|e| e.to_string())?;
    store.set("server.port", serde_json::json!(settings.server_port));
    store.set("server.host", serde_json::json!(settings.server_host));
    store.set("ui.theme", serde_json::json!(settings.ui_theme));
    // ... 其余字段
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

设计要点:所有配置读取都有默认值兜底——settings.json 损坏、缺失、字段不存在时,应用依然能以合理默认配置运行。这是"防御性配置读取"的标准实践。