个人工具型桌面应用:Tauri 和 Electron 怎么选
个人工具型桌面应用:Tauri 和 Electron 怎么选
July 15, 2026
做个人 AI 工具时,桌面端常常要:读本地目录、管 API Key、跑后台任务,同时 UI 还想继续用 React/Vue。Electron 和 Tauri 都会进短名单——别只比「谁更新」,要比威胁模型和交付约束。
1. 先看一张决策表
| 约束 | 更偏向 Electron | 更偏向 Tauri |
|---|---|---|
| 团队只会 JS,要两周出第一版 | ✅ | |
| 安装包 / 内存是体验一部分 | ✅ | |
| 要强一致的 Chromium 渲染 | ✅ | |
| 本地密钥、文件权限要默认收紧 | ✅ | |
| 重度 Node 原生模块 / 现成生态 | ✅ | |
| 长期自用小工具 | ✅ |
2. Tauri:把危险能力收进 Rust command
前端不要直接「全能 Node」。Tauri 的健康模式是:UI 只调白名单 command。
2.1 Rust 侧(示意)
// src-tauri/src/lib.rs(Tauri 2 风格示意)
use serde::Serialize;
use std::fs;
use std::path::PathBuf;
#[derive(Serialize)]
struct SafeFile {
name: String,
size: u64,
}
#[tauri::command]
fn list_notes_dir(base: String) -> Result<Vec<SafeFile>, String> {
let root = PathBuf::from(&base);
// 只允许固定目录,拒绝 .. 逃逸
let canon = root.canonicalize().map_err(|e| e.to_string())?;
let allowed = PathBuf::from("/home/user/notes").canonicalize().unwrap();
if !canon.starts_with(&allowed) {
return Err("path not allowed".into());
}
let mut out = vec![];
for ent in fs::read_dir(&canon).map_err(|e| e.to_string())? {
let ent = ent.map_err(|e| e.to_string())?;
let meta = ent.metadata().map_err(|e| e.to_string())?;
if meta.is_file() {
out.push(SafeFile {
name: ent.file_name().to_string_lossy().into(),
size: meta.len(),
});
}
}
Ok(out)
}
#[tauri::command]
fn read_secret_slot(slot: String) -> Result<String, String> {
// 真实项目:走系统钥匙串 / 加密文件,不要明文写进 localStorage
if slot != "openai" && slot != "anthropic" {
return Err("unknown slot".into());
}
keyring_get(&slot) // 伪代码
}2.2 权限配置(关键)
{
"permissions": [
"core:default",
{
"identifier": "allow-list-notes",
"commands": {
"allow": ["list_notes_dir", "read_secret_slot"]
}
}
]
}2.3 前端调用
import { invoke } from "@tauri-apps/api/core";
type SafeFile = { name: string; size: number };
export async function loadNotes() {
const files = await invoke<SafeFile[]>("list_notes_dir", {
base: "/home/user/notes",
});
return files;
}
export async function getApiKey() {
return invoke<string>("read_secret_slot", { slot: "openai" });
}实践要点: 文件系统、Shell、任意 HTTP 代理能力,全部走 command + 权限表;前端 XSS 也不等于「整机沦陷」。
3. Electron:用 preload 做桥,别开 nodeIntegration
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("api", {
listNotes: (base) => ipcRenderer.invoke("list-notes", base),
getSecret: (slot) => ipcRenderer.invoke("get-secret", slot),
});// main.js
const { app, BrowserWindow, ipcMain } = require("electron");
const fs = require("fs");
const path = require("path");
function createWindow() {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
win.loadFile("index.html");
}
ipcMain.handle("list-notes", async (_e, base) => {
const allowed = path.resolve("/home/user/notes");
const target = path.resolve(base);
if (!target.starts_with(allowed)) throw new Error("path not allowed");
return fs.readdirSync(target).map((name) => ({
name,
size: fs.statSync(path.join(target, name)).size,
}));
});Electron 完全可以做得安全,但默认模板若打开 nodeIntegration,前端一处 XSS 就等于本机 RCE。个人工具尤其别省这两行隔离。
4. 案例:做一个「本地 Prompt 柜 + 网关客户端」
需求:
- 桌面端保存常用 prompt 模板
- API Key 不进渲染进程明文
- 实际补全请求打到自己的 LLM Gateway(
http://127.0.0.1:4000/v1)
推荐拆分:
- UI:模板列表、流式输出展示(Web 技术)
- command / ipc:读写加密密钥、读模板目录
- 网络:主进程或 Rust 侧发 HTTPS(可钉死只允许网关域名)
伪代码(Tauri 侧发请求):
#[tauri::command]
async fn chat_via_gateway(prompt: String) -> Result<String, String> {
let key = keyring_get("gateway").map_err(|e| e.to_string())?;
let client = reqwest::Client::new();
let body = serde_json::json!({
"model": "auto",
"messages": [{"role":"user","content": prompt}]
});
let resp = client
.post("http://127.0.0.1:4000/v1/chat/completions")
.bearer_auth(key)
.json(&body)
.send()
.await
.map_err(|e| e.to_string())?;
let v: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
Ok(v["choices"][0]["message"]["content"].as_str().unwrap_or("").into())
}这样 UI 进程拿不到长生命周期云厂商 Key;网关虚拟 Key 也只存在安全存储里。
5. 我怎么选(个人项目经验)
- 验证产品、堆功能快:Electron,但必须
contextIsolation + preload - 长期自用、在意体积、密钥敏感:Tauri 2,把能力做成少量 command
- 核心逻辑尽量与壳无关:本地小服务或共享库,壳只是皮肤——日后换壳成本才可控
桌面框架是载体;个人 AI 工具真正要守住的是:密钥位置、文件边界、网络出口白名单。