- Introduced BlueprintModal in ProjectCard for project blueprint visualization. - Added state management for displaying the BlueprintModal. - Implemented Blueprint-related interfaces and functions in commands.ts for handling blueprint data.
12 KiB
12 KiB
Tauri v2 客户端自动更新指南
方案版本: v1.0(2026-04-02) 适用范围: Tauri v2 + 任意更新源(阿里云 OSS / GitHub Releases / 自建服务器) 验证状态: 已在 dev-manager-tauri 项目实际跑通
本文档聚焦客户端侧的自动更新功能实现,涵盖从插件安装到 UI 交互的全链路。 服务端构建与发布流程请参考 tauri-cicd-guide.md。
整体流程
客户端启动 / 用户点击「检查更新」
│
▼
请求 endpoint 上的 latest.json
│
▼
比较 latest.json.version vs 本地版本
│
┌────┴────┐
│ 无更新 │ 有新版本
│ 结束 │
│ ▼
│ 展示版本号 + 更新说明
│ 用户点击「立即更新」
│ │
│ ▼
│ 下载 .exe(带进度)
│ │
│ ▼
│ 验证签名 → 安装 → relaunch
└─────────┘
一、安装依赖
1.1 Rust 端(src-tauri/Cargo.toml)
[dependencies]
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
1.2 前端(package.json)
pnpm add @tauri-apps/plugin-updater @tauri-apps/plugin-process
二、注册插件
src-tauri/src/lib.rs 中注册两个插件:
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
// ... 其他插件和配置
tauri_plugin_updater— 检查更新、下载、验证签名、安装tauri_plugin_process— 安装后调用relaunch()重启应用
三、配置 tauri.conf.json
{
"bundle": {
"createUpdaterArtifacts": true // ⚠️ 必须!否则构建不生成 .exe.sig 签名文件
},
"plugins": {
"updater": {
"pubkey": "<公钥 base64>",
"endpoints": [
"https://<bucket>.<endpoint>/latest.json"
],
"dialog": false // false = 用代码控制更新 UI;true = 系统原生弹窗
}
}
}
关键说明
| 字段 | 作用 | 注意事项 |
|---|---|---|
createUpdaterArtifacts |
构建时生成 .exe.sig 签名文件 |
Tauri v2 默认 false,不开启则签名静默跳过 |
pubkey |
客户端用来验证下载文件签名 | 是公钥文件的 base64,不是私钥 |
endpoints |
latest.json 的 URL 列表 |
可配多个,按顺序尝试 |
dialog |
是否使用原生弹窗 | 推荐 false,自定义 UI 体验更好 |
pubkey 生成方法
# 生成密钥对
pnpm tauri signer generate -w "$HOME\.tauri\your-app.key"
# 获取公钥 base64(填入 pubkey 字段)
$content = Get-Content "$HOME\.tauri\your-app.key.pub" -Raw
$content = $content -replace "`r`n", "`n"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($content)
[Convert]::ToBase64String($bytes)
四、配置 Capabilities 权限
src-tauri/capabilities/default.json 中必须声明权限,否则前端 API 调用会被拦截:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
// ... 其他权限 ...
{
"identifier": "http:default",
"allow": [
{ "url": "https://*.aliyuncs.com/**" } // ⚠️ 放行更新源域名
]
},
"updater:default", // ⚠️ 允许 check()、downloadAndInstall()
"process:default" // ⚠️ 允许 relaunch()
]
}
三项缺一不可:
updater:default— 否则报updater.check not allowedprocess:default— 否则relaunch()无法调用- http allow 列表包含更新源域名 — 否则报
Could not fetch a valid release JSON
五、latest.json 格式
更新源需要提供一个 latest.json,格式如下:
{
"version": "0.1.11",
"pub_date": "2026-04-02T05:29:44Z",
"notes": "版本 v0.1.11 已发布",
"platforms": {
"windows-x86_64": {
"signature": "<.exe.sig 文件内容>",
"url": "https://<bucket>.<endpoint>/releases/v0.1.11/<app>_0.1.11_x64-setup.exe"
}
}
}
| 字段 | 说明 |
|---|---|
version |
新版本号,updater 用它和本地版本比较 |
pub_date |
ISO 8601 格式,必须有 |
notes |
更新说明,前端通过 update.body 获取 |
platforms |
按平台提供下载链接和签名 |
signature |
.exe.sig 文件内容(base64),客户端用 pubkey 验证 |
url |
安装包下载地址,必须可公开访问 |
多平台支持
{
"platforms": {
"windows-x86_64": { "signature": "...", "url": "...setup.exe" },
"darwin-x86_64": { "signature": "...", "url": "...app.tar.gz" },
"darwin-aarch64": { "signature": "...", "url": "...app.tar.gz" },
"linux-x86_64": { "signature": "...", "url": "...AppImage.tar.gz" }
}
}
六、前端实现(React + TypeScript)
6.1 状态机设计
更新流程有明确的状态流转,用联合类型建模最清晰:
type UpdateState =
| { status: "idle" } // 初始状态
| { status: "checking" } // 正在检查
| { status: "latest" } // 已是最新
| { status: "available"; version: string; notes: string; downloadAndInstall: () => Promise<void> }
| { status: "downloading"; progress: number } // 下载中(0~100)
| { status: "error"; message: string } // 出错
6.2 完整组件参考
import { useState, useEffect } from "react";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";
import { getVersion } from "@tauri-apps/api/app";
function UpdateSection() {
const [appVersion, setAppVersion] = useState("");
const [state, setState] = useState<UpdateState>({ status: "idle" });
useEffect(() => {
getVersion().then(setAppVersion).catch(() => {});
}, []);
const handleCheck = async () => {
setState({ status: "checking" });
try {
const update = await checkUpdate();
if (!update?.available) {
setState({ status: "latest" });
return;
}
setState({
status: "available",
version: update.version,
notes: update.body ?? "",
downloadAndInstall: async () => {
let downloaded = 0;
let total = 0;
await update.downloadAndInstall((event) => {
if (event.event === "Started") {
total = event.data.contentLength ?? 0;
setState({ status: "downloading", progress: 0 });
} else if (event.event === "Progress") {
downloaded += event.data.chunkLength;
const progress = total > 0 ? Math.round((downloaded / total) * 100) : 0;
setState({ status: "downloading", progress });
} else if (event.event === "Finished") {
setState({ status: "idle" });
}
});
await relaunch();
},
});
} catch (e) {
setState({ status: "error", message: String(e) });
}
};
return (
<div>
<p>当前版本 v{appVersion}</p>
{state.status === "latest" && <span>已是最新版本</span>}
{state.status === "available" && (
<>
<span>v{state.version} 可用</span>
<button onClick={state.downloadAndInstall}>立即更新</button>
</>
)}
{state.status === "downloading" && (
<span>下载中 {state.progress}%</span>
)}
{state.status === "error" && (
<div>
<p>{state.message}</p>
<button onClick={() => navigator.clipboard.writeText(state.message)}>
复制错误
</button>
</div>
)}
{state.status !== "available" && (
<button
onClick={handleCheck}
disabled={state.status === "checking" || state.status === "downloading"}
>
{state.status === "checking" ? "检查中…" : "检查更新"}
</button>
)}
</div>
);
}
6.3 下载事件说明
downloadAndInstall 的回调事件顺序:
| 事件 | 数据 | 说明 |
|---|---|---|
Started |
{ contentLength?: number } |
开始下载,contentLength 可能为 0 |
Progress |
{ chunkLength: number } |
每个数据块,累加计算进度 |
Finished |
无 | 下载完成,随后自动安装 |
downloadAndInstall完成后立即调用relaunch()重启应用,新版本生效。
七、版本号管理
Tauri v2 项目有三处版本号必须保持一致:
| 文件 | 字段 | 作用 |
|---|---|---|
src-tauri/tauri.conf.json |
"version" |
updater 用来和 latest.json 比较 |
src-tauri/Cargo.toml |
version |
Rust 构建元数据 |
package.json |
"version" |
前端构建元数据 |
发版步骤
# 1. 三处改版本号(如 0.2.0)
# 2. 提交
git add -A
git commit -m "release: v0.2.0"
# 3. 打 tag + push(PowerShell 不支持 &&,分开执行)
git tag v0.2.0
git push
git push --tags
版本号不一致的后果:
latest.json中的 version 高于本地版本时才会提示更新。 如果tauri.conf.json的版本没改,每次启动都会提示"有新版本"即使已经更新过。
八、更新源选择
| 更新源 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 阿里云 OSS | 国内快,CDN 加速 | 需要额外配置 OSS | 面向国内用户 |
| GitHub Releases | 零配置,tauri-action 自动生成 | 国内访问慢/不稳定 | 面向海外用户 |
| 自建服务器 | 完全可控 | 需要运维 | 企业内网分发 |
Tauri v2 的 endpoints 支持多个 URL,可以配置主备:
"endpoints": [
"https://<oss-bucket>.oss-cn-hangzhou.aliyuncs.com/latest.json",
"https://github.com/<user>/<repo>/releases/latest/download/latest.json"
]
九、调试技巧
9.1 验证 latest.json 可访问
curl https://<your-endpoint>/latest.json
- 返回 JSON → 正常
- 返回
AccessDenied→ OSS Bucket 未设为公共读 - 返回 404 → CI/CD 未上传或路径不对
9.2 开发模式测试
pnpm tauri dev 模式下也能调用 check(),但注意:
- 开发模式的版本号来自
tauri.conf.json - 如果本地版本号和 latest.json 相同或更高,不会触发更新
9.3 强制触发更新(调试用)
临时把 tauri.conf.json 的版本号改低(如 0.0.1),再运行 pnpm tauri dev 点检查更新即可触发。
十、踩坑清单
| # | 问题 | 现象 | 解决方案 |
|---|---|---|---|
| 1 | 缺少 updater:default |
updater.check not allowed |
capabilities/default.json 添加 updater:default |
| 2 | 缺少 process:default |
relaunch() 无效 |
capabilities/default.json 添加 process:default |
| 3 | http 未放行更新源域名 | Could not fetch a valid release JSON |
capabilities http allow 加更新源域名 |
| 4 | OSS Bucket 为私有 | 同上,curl 返回 AccessDenied |
Bucket ACL 改为公共读 |
| 5 | createUpdaterArtifacts 未开启 |
构建成功但无 .exe.sig |
tauri.conf.json → bundle.createUpdaterArtifacts: true |
| 6 | pubkey 填了私钥 | failed to decode pubkey |
用公钥文件的 base64 |
| 7 | 版本号三处不一致 | 更新后仍提示有新版本 | tauri.conf.json、Cargo.toml、package.json 必须同步 |
| 8 | latest.json 缺少 pub_date |
updater 解析失败 | 必须包含 ISO 8601 格式的日期 |
| 9 | signature 字段为空 | 签名验证失败 | 检查 CI/CD 是否正确读取了 .exe.sig 文件内容 |
| 10 | dialog: true 时无法自定义 UI |
弹出系统原生对话框 | 设为 false,用代码实现自定义更新界面 |
配置文件速查
新项目最小配置清单:
src-tauri/Cargo.toml → 添加 tauri-plugin-updater、tauri-plugin-process 依赖
package.json → pnpm add @tauri-apps/plugin-updater @tauri-apps/plugin-process
src-tauri/src/lib.rs → 注册两个插件
src-tauri/tauri.conf.json → bundle.createUpdaterArtifacts + plugins.updater 配置
src-tauri/capabilities/*.json → updater:default + process:default + http allow
前端组件 → 调用 check() + downloadAndInstall() + relaunch()