- 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.
409 lines
12 KiB
Markdown
409 lines
12 KiB
Markdown
# Tauri v2 客户端自动更新指南
|
||
|
||
> **方案版本**: v1.0(2026-04-02)
|
||
> **适用范围**: Tauri v2 + 任意更新源(阿里云 OSS / GitHub Releases / 自建服务器)
|
||
> **验证状态**: 已在 dev-manager-tauri 项目实际跑通
|
||
>
|
||
> 本文档聚焦客户端侧的自动更新功能实现,涵盖从插件安装到 UI 交互的全链路。
|
||
> 服务端构建与发布流程请参考 [tauri-cicd-guide.md](./tauri-cicd-guide.md)。
|
||
|
||
---
|
||
|
||
## 整体流程
|
||
|
||
```
|
||
客户端启动 / 用户点击「检查更新」
|
||
│
|
||
▼
|
||
请求 endpoint 上的 latest.json
|
||
│
|
||
▼
|
||
比较 latest.json.version vs 本地版本
|
||
│
|
||
┌────┴────┐
|
||
│ 无更新 │ 有新版本
|
||
│ 结束 │
|
||
│ ▼
|
||
│ 展示版本号 + 更新说明
|
||
│ 用户点击「立即更新」
|
||
│ │
|
||
│ ▼
|
||
│ 下载 .exe(带进度)
|
||
│ │
|
||
│ ▼
|
||
│ 验证签名 → 安装 → relaunch
|
||
└─────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 一、安装依赖
|
||
|
||
### 1.1 Rust 端(src-tauri/Cargo.toml)
|
||
|
||
```toml
|
||
[dependencies]
|
||
tauri-plugin-updater = "2"
|
||
tauri-plugin-process = "2"
|
||
```
|
||
|
||
### 1.2 前端(package.json)
|
||
|
||
```bash
|
||
pnpm add @tauri-apps/plugin-updater @tauri-apps/plugin-process
|
||
```
|
||
|
||
---
|
||
|
||
## 二、注册插件
|
||
|
||
`src-tauri/src/lib.rs` 中注册两个插件:
|
||
|
||
```rust
|
||
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
|
||
|
||
```jsonc
|
||
{
|
||
"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 生成方法
|
||
|
||
```powershell
|
||
# 生成密钥对
|
||
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 调用会被拦截:
|
||
|
||
```jsonc
|
||
{
|
||
"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 allowed`
|
||
- `process:default` — 否则 `relaunch()` 无法调用
|
||
- http allow 列表包含更新源域名 — 否则报 `Could not fetch a valid release JSON`
|
||
|
||
---
|
||
|
||
## 五、latest.json 格式
|
||
|
||
更新源需要提供一个 `latest.json`,格式如下:
|
||
|
||
```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` | 安装包下载地址,必须可公开访问 |
|
||
|
||
### 多平台支持
|
||
|
||
```json
|
||
{
|
||
"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 状态机设计
|
||
|
||
更新流程有明确的状态流转,用联合类型建模最清晰:
|
||
|
||
```typescript
|
||
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 完整组件参考
|
||
|
||
```typescript
|
||
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"` | 前端构建元数据 |
|
||
|
||
### 发版步骤
|
||
|
||
```bash
|
||
# 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,可以配置主备:
|
||
|
||
```json
|
||
"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 可访问
|
||
|
||
```powershell
|
||
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()
|
||
```
|