dev-manager-tauri/docs/tauri-updater-guide.md
lanrtop 0cdaa3678c feat: add Blueprint feature to ProjectCard and commands
- 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.
2026-04-02 19:12:53 +09:00

12 KiB
Raw Blame History

Tauri v2 客户端自动更新指南

方案版本: v1.02026-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 = 用代码控制更新 UItrue = 系统原生弹窗
    }
  }
}

关键说明

字段 作用 注意事项
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 allowed
  • process: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 + pushPowerShell 不支持 &&,分开执行)
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.jsonbundle.createUpdaterArtifacts: true
6 pubkey 填了私钥 failed to decode pubkey 用公钥文件的 base64
7 版本号三处不一致 更新后仍提示有新版本 tauri.conf.jsonCargo.tomlpackage.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()