自动更新架构
应用启动 / 用户触发检查
│
▼
检查更新服务器
GET /latest.json
│
┌────┴────┐
│ 有新版 │ 没有新版本
│ 本可用 │ ─────────────→ 结束
└────┬────┘
│
下载更新包
(显示进度条)
│
▼
验证数字签名
(防止中间人篡改)
│
▼
安装并重启应用
plugin-updater 安装与配置
npm install @tauri-apps/plugin-updater
# Cargo.toml
tauri-plugin-updater = "2"
// src-tauri/src/lib.rs
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.run(tauri::generate_context!())
.unwrap();
}
// tauri.conf.json
{
"plugins": {
"updater": {
"pubkey": "YOUR_UPDATER_PUBLIC_KEY",
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
],
"dialog": false
}
}
}
{{target}}
目标平台:
linux、windows、darwin(macOS){{arch}}
CPU 架构:
x86_64、aarch64{{current_version}}
当前版本号(如
1.2.3),服务器可据此返回是否有更高版本pubkey
更新签名验证公钥。Tauri 要求每个更新包必须有对应私钥的签名,客户端用公钥验证,防止供应链攻击。
生成签名密钥对
# 生成 updater 密钥对
npm run tauri signer generate -- -w ~/.tauri/myapp.key
# 输出:
# Private key: ~/.tauri/myapp.key (保密!用于 CI/CD 签名)
# Public key: dW50cnVzdGVkIGNvbW1lbnQ6... (填入 tauri.conf.json pubkey)
# 查看公钥
cat ~/.tauri/myapp.key.pub
更新服务器响应格式
// 有更新时返回 200 + 以下 JSON
{
"version": "1.3.0",
"notes": "## 更新内容\n- 修复若干 Bug\n- 新增深色主题",
"pub_date": "2024-11-15T08:00:00Z",
"platforms": {
"darwin-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://cdn.myapp.com/v1.3.0/MyApp_x64.dmg"
},
"darwin-aarch64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://cdn.myapp.com/v1.3.0/MyApp_aarch64.dmg"
},
"windows-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://cdn.myapp.com/v1.3.0/MyApp_x64-setup.nsis.zip"
},
"linux-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
"url": "https://cdn.myapp.com/v1.3.0/MyApp_amd64.AppImage.tar.gz"
}
}
}
// 没有更新时返回 204 No Content
前端更新 UI 实现
import { check, Update } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { useState } from 'react';
function UpdateChecker() {
const [status, setStatus] = useState('idle');
const [update, setUpdate] = useState<Update | null>(null);
const [progress, setProgress] = useState(0);
async function checkForUpdates() {
setStatus('checking');
const result = await check();
if (!result?.available) {
setStatus('up-to-date');
return;
}
setUpdate(result);
setStatus('available');
}
async function installUpdate() {
if (!update) return;
setStatus('downloading');
let downloaded = 0;
let contentLength = 0;
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
contentLength = event.data.contentLength ?? 0;
break;
case 'Progress':
downloaded += event.data.chunkLength;
if (contentLength > 0) {
setProgress(Math.round((downloaded / contentLength) * 100));
}
break;
case 'Finished':
setStatus('installing');
break;
}
});
// 安装完成,重启应用
await relaunch();
}
return (
<div>
{status === 'idle' && (
<button onClick={checkForUpdates}>检查更新</button>
)}
{status === 'checking' && <p>正在检查更新...</p>}
{status === 'up-to-date' && <p>已是最新版本</p>}
{status === 'available' && (
<div>
<p>发现新版本: {update?.version}</p>
<p>{update?.body}</p>
<button onClick={installUpdate}>立即更新</button>
</div>
)}
{status === 'downloading' && (
<div>
<p>下载中... {progress}%</p>
<progress value={progress} max="100" />
</div>
)}
{status === 'installing' && <p>正在安装,即将重启...</p>}
</div>
);
}
版本号管理
# 版本号在两个地方定义,需要保持同步:
# 1. src-tauri/tauri.conf.json: "version": "1.2.3"
# 2. src-tauri/Cargo.toml: version = "1.2.3"
# 使用 tauri-version 工具同步更新
npm install -g @tauri-apps/cli
tauri version 1.3.0 # 同时更新 tauri.conf.json 和 Cargo.toml
# 或手动更新后创建 git tag 触发 CI
git add src-tauri/tauri.conf.json src-tauri/Cargo.toml
git commit -m "chore: bump version to v1.3.0"
git tag v1.3.0
git push origin main --tags
GitHub Actions 自动发布与更新 JSON
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v[0-9]+.[0-9]+.[0-9]+']
jobs:
create-release:
runs-on: ubuntu-latest
outputs:
release-id: ${{ steps.create-release.outputs.result }}
steps:
- id: create-release
uses: actions/github-script@v7
with:
script: |
const { data } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: context.ref.replace('refs/tags/', ''),
name: context.ref.replace('refs/tags/', ''),
draft: true,
prerelease: false,
});
return data.id;
build-tauri:
needs: create-release
strategy:
matrix:
include:
- platform: macos-latest
args: '--target aarch64-apple-darwin'
- platform: macos-latest
args: '--target x86_64-apple-darwin'
- platform: ubuntu-22.04
args: ''
- platform: windows-latest
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- uses: dtolnay/rust-toolchain@stable
- run: npm install
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
releaseId: ${{ needs.create-release.outputs.release-id }}
args: ${{ matrix.args }}
publish-release:
runs-on: ubuntu-latest
needs: [create-release, build-tauri]
steps:
- uses: actions/github-script@v7
with:
script: |
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: ${{ needs.create-release.outputs.release-id }},
draft: false,
});
使用 GitHub Releases 作为更新服务器
无需自建服务器,可以直接使用 GitHub Releases 作为更新源。tauri-action 会自动生成 latest.json 并上传到 Release:
// tauri.conf.json — 使用 GitHub Releases
{
"plugins": {
"updater": {
"pubkey": "YOUR_PUBLIC_KEY",
"endpoints": [
"https://github.com/username/repo/releases/latest/download/latest.json"
]
}
}
}
使用 Tauri Update Server 简化部署
开源项目 tauri-update-server 提供了一个简单的 Cloudflare Worker,可部署为代理层:根据请求的 target/arch/version 从 GitHub Releases 找到合适的更新包并返回标准格式的 JSON。这样既利用了 GitHub 的免费托管,又满足了 Tauri updater 的 API 格式要求,是小型项目的理想选择。
应用启动时自动检查更新
最常见的更新模式是应用启动后在后台静默检查,有更新时通知用户而不强制更新:
use tauri::{AppHandle, Manager};
use tauri_plugin_updater::UpdaterExt;
// 在 setup 中异步检查更新(不阻塞应用启动)
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {
let handle = app.handle().clone();
// 使用 spawn 在后台检查,不阻塞主线程
tauri::async_runtime::spawn(async move {
check_for_update(handle).await;
});
Ok(())
})
.run(tauri::generate_context!())
.unwrap();
}
async fn check_for_update(app: AppHandle) {
// 等待 5 秒,确保应用完全启动后再检查
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
match app.updater_builder().build() {
Ok(updater) => {
match updater.check().await {
Ok(Some(_update)) => {
// 有更新:通知前端
app.emit("update-available", ()).unwrap();
}
Ok(None) => {
// 已是最新版本
}
Err(e) => {
// 检查失败(网络问题等),静默处理
eprintln!("Update check failed: {}", e);
}
}
}
Err(e) => eprintln!("Updater build failed: {}", e),
}
}
import { listen } from '@tauri-apps/api/event';
import { useEffect, useState } from 'react';
// 在 App 根组件中监听更新通知
function App() {
const [showUpdateBanner, setShowUpdateBanner] = useState(false);
useEffect(() => {
const unlisten = listen('update-available', () => {
setShowUpdateBanner(true);
});
return () => { unlisten.then(f => f()); };
}, []);
return (
<div>
{showUpdateBanner && (
<div className="update-banner">
有新版本可用!
<button onClick={() => navigate('/settings/update')}>
查看更新
</button>
<button onClick={() => setShowUpdateBanner(false)}>
稍后提醒
</button>
</div>
)}
{/* 其他应用内容 */}
</div>
);
}
更新前数据迁移
当应用版本更新涉及数据格式变化(数据库 schema 升级、配置格式改变)时,需要在应用启动时执行迁移:
use semver::Version;
#[tauri::command]
pub async fn run_migrations(app: AppHandle) -> Result<(), String> {
let current_version = Version::parse(env!("CARGO_PKG_VERSION"))
.map_err(|e| e.to_string())?;
// 读取上次运行的版本号
let data_dir = app.path().app_data_dir().unwrap();
let version_file = data_dir.join("version.txt");
let last_version = std::fs::read_to_string(&version_file)
.ok()
.and_then(|s| Version::parse(s.trim()).ok());
if let Some(last) = last_version {
// 从旧版本迁移到新版本
if last < Version::parse("1.3.0").unwrap() {
migrate_to_1_3_0(&app).await?;
}
}
// 更新版本文件
std::fs::write(&version_file, current_version.to_string())
.map_err(|e| e.to_string())?;
Ok(())
}
async fn migrate_to_1_3_0(app: &AppHandle) -> Result<(), String> {
// 执行数据库 schema 升级
println!("Migrating to v1.3.0...");
// ALTER TABLE todos ADD COLUMN priority INTEGER DEFAULT 0;
Ok(())
}
本章小结
本章核心要点(也是整个教程的收尾)
- plugin-updater 架构:检查 → 比较版本 → 下载(带进度)→ 验证签名 → 安装 → 重启;pubkey/privkey 对保证更新安全性。
- 更新 JSON 格式:endpoint 返回 200+JSON(有更新)或 204(无更新);JSON 包含版本号、release notes、各平台的 URL + 签名。
- 后台静默检查:在 Rust setup() 中 spawn 异步任务,延迟 5 秒检查;有更新时 emit 事件给前端显示 banner,不强制更新。
- 版本号同步:tauri.conf.json 和 Cargo.toml 必须版本一致;使用
tauri version X.Y.Z或提交前脚本统一更新。 - 数据迁移:应用启动时检查上次运行版本,执行必要的 schema 升级或配置格式转换,确保跨版本数据兼容性。
- GitHub Releases 作为更新服务器:无需自建服务器,tauri-action 自动生成 latest.json 并上传到 Release Assets;通过 Cloudflare Worker 代理满足 Tauri updater 的 API 格式要求。
完结:你已掌握 Tauri 2.0 全栈开发
恭喜完成 Tauri 教程!你现在能够:构建 WebView + Rust 双进程桌面应用、实现前后端双向通信(Commands + Events)、使用系统 API(文件、托盘、通知、快捷键)、集成 SQLite 数据库、完成跨平台打包签名,以及实现安全的自动更新。Tauri 2.0 更进一步支持 iOS 和 Android,让一套代码覆盖所有主流平台成为可能。