官方插件清单
| 插件 | 功能 | npm 包 |
|---|---|---|
| plugin-fs | 文件系统操作 | @tauri-apps/plugin-fs |
| plugin-dialog | 系统对话框 | @tauri-apps/plugin-dialog |
| plugin-notification | 系统通知 | @tauri-apps/plugin-notification |
| plugin-store | 持久化键值存储 | @tauri-apps/plugin-store |
| plugin-sql | SQLite 数据库 | @tauri-apps/plugin-sql |
| plugin-http | HTTP 客户端 | @tauri-apps/plugin-http |
| plugin-shell | 运行外部命令 | @tauri-apps/plugin-shell |
| plugin-updater | 自动更新 | @tauri-apps/plugin-updater |
| plugin-global-shortcut | 全局快捷键 | @tauri-apps/plugin-global-shortcut |
| plugin-clipboard-manager | 剪贴板 | @tauri-apps/plugin-clipboard-manager |
plugin-store:持久化键值存储
plugin-store 提供简单的持久化键值存储,数据自动保存到应用数据目录,适合存储用户偏好设置:
npm install @tauri-apps/plugin-store
import { Store } from '@tauri-apps/plugin-store';
// 创建/打开 store(文件名即存储标识)
const store = await Store.load('settings.json', {
autoSave: true, // 每次修改后自动保存
});
// 写入值
await store.set('theme', 'dark');
await store.set('windowSize', { width: 1200, height: 800 });
await store.set('recentFiles', ['/tmp/a.txt', '/tmp/b.txt']);
// 读取值(带默认值)
const theme = await store.get<string>('theme') ?? 'light';
const windowSize = await store.get<{ width: number; height: number }>('windowSize');
// 检查 key 是否存在
const hasTheme = await store.has('theme');
// 删除
await store.delete('recentFiles');
// 手动保存(autoSave 为 false 时需要)
await store.save();
// 获取所有 key
const keys = await store.keys();
console.log(keys); // ['theme', 'windowSize']
plugin-sql:SQLite 数据库
npm install @tauri-apps/plugin-sql
# Cargo.toml
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
# 也支持 "mysql" 和 "postgres"
import Database from '@tauri-apps/plugin-sql';
// 连接 SQLite 数据库(文件位于 appDataDir)
const db = await Database.load('sqlite:app.db');
// 创建表
await db.execute(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// 插入数据
const result = await db.execute(
'INSERT INTO todos (title) VALUES ($1)',
['Learn Tauri']
);
console.log('Inserted ID:', result.lastInsertId);
// 查询数据
interface Todo {
id: number;
title: string;
completed: boolean;
created_at: string;
}
const todos = await db.select<Todo[]>(
'SELECT * FROM todos WHERE completed = $1 ORDER BY created_at DESC',
[false]
);
// 更新
await db.execute(
'UPDATE todos SET completed = $1 WHERE id = $2',
[true, 1]
);
// 事务
await db.execute('BEGIN');
try {
await db.execute('INSERT INTO todos (title) VALUES ($1)', ['Task A']);
await db.execute('INSERT INTO todos (title) VALUES ($1)', ['Task B']);
await db.execute('COMMIT');
} catch {
await db.execute('ROLLBACK');
}
plugin-http:突破 CORS 限制
WebView 中直接发 fetch 请求受 CORS 限制,plugin-http 使用 Rust 的 reqwest 库发出请求,绕过浏览器的 CORS 检查:
import { fetch } from '@tauri-apps/plugin-http';
// 与浏览器 fetch API 兼容,但从 Rust 后端发出请求
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token',
},
body: JSON.stringify({ query: 'search term' }),
});
const data = await response.json();
plugin-global-shortcut:全局快捷键
import { register, unregister, isRegistered } from '@tauri-apps/plugin-global-shortcut';
// 注册全局快捷键(应用不在前台也有效)
await register('CommandOrControl+Shift+Space', () => {
console.log('Global shortcut triggered!');
});
// 注册多个快捷键
await register(['CommandOrControl+K', 'F1'], (shortcut) => {
console.log('Pressed:', shortcut);
});
// 检查是否已注册
const registered = await isRegistered('CommandOrControl+K');
// 注销
await unregister('CommandOrControl+K');
自定义 Rust 插件
# 使用 tauri-plugin 模板创建插件
cargo generate --git https://github.com/tauri-apps/tauri-plugin --name tauri-plugin-myplugin
# 或手动创建
cargo new --lib tauri-plugin-myplugin
// tauri-plugin-myplugin/src/lib.rs
use tauri::{
plugin::{Builder, TauriPlugin},
Runtime,
};
// 插件暴露的 Command
#[tauri::command]
fn my_custom_function(input: String) -> String {
format!("Processed: {}", input.to_uppercase())
}
// 插件初始化函数
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("myplugin")
.invoke_handler(tauri::generate_handler![my_custom_function])
.setup(|app, _api| {
// 插件初始化逻辑(可以注册状态等)
println!("MyPlugin initialized");
Ok(())
})
.build()
}
// 在主应用中使用
// .plugin(tauri_plugin_myplugin::init())
社区插件资源
除官方插件外,社区提供了大量实用插件:tauri-plugin-log(结构化日志)、tauri-plugin-autostart(开机自启)、tauri-plugin-window-state(记住窗口大小/位置)等。访问 tauri-apps/plugins-workspace 查看全部官方插件,或在 crates.io 搜索 "tauri-plugin" 发现社区贡献。
Tauri 插件系统的工作原理
插件的架构层次
Tauri 插件是 Tauri 功能扩展的标准机制,理解其架构有助于正确选择是使用插件还是直接写 Command:
插件与 Command 的区别
Command(
#[tauri::command])是应用内部的功能,直接写在 src-tauri/src/ 中。插件是可复用的、可分发的 Tauri 扩展模块,有独立的 Cargo 包,可以发布到 crates.io 让其他 Tauri 应用使用。如果你只是给自己的应用加功能,用 Command;如果要复用或分享,用 Plugin。插件的生命周期钩子
插件 Builder 提供了多个生命周期钩子:
setup(应用启动时初始化,注册状态、启动后台线程)、on_page_load(每次 WebView 加载页面时)、on_event(应用事件,如窗口创建/销毁)。这允许插件在应用的关键时机执行初始化和清理逻辑。permissions 系统(Tauri 2.0 特性)
Tauri 2.0 引入了细粒度的权限系统(Capabilities):每个插件声明自己的权限集,应用通过
capabilities/*.json 配置文件显式授予前端哪些权限。这是 Tauri 2.0 安全模型的核心——前端只能调用被显式允许的 API,即使前端被 XSS 攻击,攻击者也无法访问未授权的系统 API。什么时候应该写自定义插件?
用 Command(最常见):
✓ 功能只供当前应用使用
✓ 需要访问应用特定的状态(数据库连接、配置)
✓ 快速原型或一次性功能
用 Plugin(可复用模块):
✓ 功能多个应用都需要(如统一的认证模块)
✓ 封装复杂的 Rust 库(如密码学、图像处理)
✓ 需要生命周期管理(setup/teardown)
✓ 有独立的 npm 包提供 TypeScript 类型
Plugin 的典型结构:
tauri-plugin-myplugin/
├── src/ ← Rust 代码
│ ├── lib.rs ← Plugin Builder + Commands
│ └── models.rs ← 类型定义
├── guest-js/ ← TypeScript 封装(可选)
│ └── index.ts ← JS API,供前端 import 使用
├── Cargo.toml
└── package.json ← npm 包配置
plugin-shell:运行外部命令
plugin-shell 允许 Tauri 应用运行外部命令(如调用 FFmpeg、打开终端、运行脚本),支持捕获输出和状态码:
import { Command } from '@tauri-apps/plugin-shell';
// 运行命令并等待结果
const result = await Command.create('ffmpeg', [
'-i', '/path/to/input.mp4',
'-vcodec', 'h264',
'/path/to/output.mp4',
]).execute();
console.log('Exit code:', result.code);
console.log('stdout:', result.stdout);
console.log('stderr:', result.stderr);
// 流式读取输出(实时日志)
const command = Command.create('git', ['clone', 'https://github.com/tauri-apps/tauri.git']);
command.stdout.on('data', line => {
console.log('stdout:', line);
});
command.stderr.on('data', line => {
console.log('stderr:', line);
});
command.on('close', data => {
console.log('Finished with code:', data.code);
});
await command.spawn(); // 异步运行,不阻塞
// capabilities/default.json — shell 权限
{
"permissions": [
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "ffmpeg", // 允许执行的命令名(白名单)
"cmd": "ffmpeg",
"args": true // 允许任意参数(或用数组限制参数)
},
{
"name": "git",
"cmd": "git",
"args": ["clone", "pull", "push"] // 只允许指定子命令
}
]
}
]
}
plugin-updater:自动更新
plugin-updater 提供应用自动更新功能,检查更新服务器并下载安装新版本:
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
async function checkAndUpdate() {
const update = await check();
if (!update) {
console.log('Already on the latest version.');
return;
}
console.log(`Update available: ${update.version}`);
console.log(`Release notes: ${update.body}`);
// 下载并安装更新(带进度回调)
let downloaded = 0;
let total = 0;
await update.downloadAndInstall((event) => {
if (event.event === 'Started') {
total = event.data.contentLength ?? 0;
} else if (event.event === 'Progress') {
downloaded += event.data.chunkLength;
const percent = total > 0 ? (downloaded / total * 100).toFixed(1) : '?';
console.log(`Downloading... ${percent}%`);
} else if (event.event === 'Finished') {
console.log('Download complete.');
}
});
// 重启应用以完成更新
await relaunch();
}
// tauri.conf.json — 配置更新服务器
{
"bundle": {
"updater": {
"active": true,
"endpoints": [
"https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"
],
"dialog": true, // 显示内置更新对话框
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6..." // 用 tauri signer generate 生成
}
}
}
更新必须签名
plugin-updater 要求更新包必须使用 tauri signer 工具生成的密钥签名。公钥存在 tauri.conf.json 中,私钥存在 CI 的 secrets 里。这防止中间人攻击——即使攻击者控制了更新服务器 URL,没有私钥签名的包也无法被安装。
本章小结
本章核心要点
- 官方插件覆盖常见需求:SQL(SQLite/MySQL/Postgres)、HTTP(绕过 CORS)、FS(文件系统)、Store(键值存储)、Shell(外部命令)、Updater(自动更新)等,优先用官方插件。
- plugin-sql 支持参数化查询:使用
$1/$2占位符防止 SQL 注入;支持事务(BEGIN/COMMIT/ROLLBACK);数据库文件存储在 appDataDir()。 - plugin-shell 需要命令白名单:在 capabilities 中声明允许执行的命令名和参数限制,防止前端注入执行任意系统命令。
- plugin-updater 更新包必须签名:防止中间人攻击;pubkey 在配置文件中,privkey 存 CI secrets;下载进度通过回调函数报告。
- 插件与 Command 的选择:仅当前应用使用 → Command;需要复用或分发 → Plugin;自定义插件有独立 Cargo 包和 guest-js TypeScript 封装。
- Capabilities 权限系统是安全关键:每个插件的每个 API 需要在 capabilities 中显式授权;防止 XSS 攻击升级为系统权限滥用。