ByteNoteByteNote

字节笔记本

2026年7月20日

Tauri 开发实战:API 能力全景与 Mac 应用打包上架

API中转
¥120

Tauri 开发实战:API 能力全景与 Mac 应用打包上架

Tauri 是一个用 Rust 构建桌面应用的框架,相比 Electron,包体积更小、性能更好、安全性更高。本文梳理 Tauri 提供的核心 API、C++ 互操作、截图功能实现,以及 Mac 应用打包上架的完整流程。

Tauri 核心 API 一览

Tauri 通过 Rust 后端 + Web 前端的架构,提供了丰富的系统级 API:

窗口管理

rust
use tauri::WindowBuilder;

// 创建新窗口
let window = WindowBuilder::new(app, "label")
    .title("My Window")
    .inner_size(800.0, 600.0)
    .build()?;

// 控制窗口
window.maximize();
window.minimize();
window.close();

系统对话框

rust
use tauri::api::dialog;

// 打开文件选择
let file_path = dialog::FileDialogBuilder::new().pick_file();

// 消息提示
dialog::MessageDialogBuilder::new("提示", "操作成功!").show(|_| {});

文件系统

rust
use std::fs;

let contents = fs::read_to_string("config.txt")?;
fs::write("data.txt", "content")?;

HTTP 客户端

Tauri 的 HTTP 客户端通过 tauri::http 模块提供,支持在 Rust 后端发起网络请求:

rust
use tauri::http::{Request, Response};

系统托盘

rust
use tauri::SystemTray;
use tauri::SystemTrayEvent;
use tauri::CustomMenuItem;

let tray = SystemTray::new()
    .with_menu(menu)
    .on_tray_event(|app, event| match event {
        SystemTrayEvent::LeftClick { .. } => {
            // 点击托盘图标
        }
        SystemTrayEvent::MenuItemClick { id, .. } => {
            // 菜单项点击
        }
        _ => {}
    });

事件系统

前后端通信的核心机制:

rust
// 后端监听事件
app.listen("custom-event", |event| {
    println!("收到事件: {:?}", event.payload());
});

// 后端发送事件
app.emit("frontend-event", payload)?;
javascript
// 前端监听
import { listen } from '@tauri-apps/api/event';

const unlisten = await listen('frontend-event', (event) => {
    console.log(event.payload);
});

全局快捷键

rust
use tauri::GlobalShortcutManager;

app.global_shortcut_manager()
    .register("CommandOrControl+Shift+S", || {
        println!("快捷键触发");
    })?;

剪贴板

rust
use tauri::api::clipboard;

clipboard::write_text("复制的文本")?;
let text = clipboard::read_text()?;

通过 FFI 调用 C++ 代码

Tauri 后端是 Rust,可以通过 FFI(Foreign Function Interface)调用 C++ 编写的库。这对于复用现有的 C++ 库非常有用。

步骤 1:编写 C++ 代码

cpp
// src-tauri/cpp/calculator.h
#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);
double multiply(double a, double b);
const char* get_version();

#ifdef __cplusplus
}
#endif
cpp
// src-tauri/cpp/calculator.cpp
#include "calculator.h"
#include <string>

int add(int a, int b) {
    return a + b;
}

double multiply(double a, double b) {
    return a * b;
}

const char* get_version() {
    static std::string version = "1.0.0";
    return version.c_str();
}

步骤 2:配置 build.rs 编译 C++

rust
// src-tauri/build.rs
fn main() {
    cc::Build::new()
        .cpp(true)
        .file("cpp/calculator.cpp")
        .compile("calculator");
}

步骤 3:Rust 端 FFI 绑定

rust
// src-tauri/src/lib.rs
use std::os::raw::c_char;
use std::ffi::CStr;

#[link(name = "calculator", kind = "static")]
extern "C" {
    fn add(a: i32, b: i32) -> i32;
    fn multiply(a: f64, b: f64) -> f64;
    fn get_version() -> *const c_char;
}

// 包装为 Tauri Command,供前端调用
#[tauri::command]
pub fn cpp_add(a: i32, b: i32) -> i32 {
    unsafe { add(a, b) }
}

#[tauri::command]
pub fn cpp_get_version() -> String {
    unsafe {
        CStr::from_ptr(get_version())
            .to_string_lossy()
            .into_owned()
    }
}

步骤 4:前端调用

javascript
import { invoke } from '@tauri-apps/api/tauri';

const result = await invoke('cpp_add', { a: 1, b: 2 });
console.log(result); // 3

注意事项:

  • C++ 函数必须用 extern "C" 包裹,避免 C++ 的名称修饰(name mangling)
  • 字符串用 *const c_char,Rust 端用 CStr::from_ptr 转换
  • 内存管理需要特别小心,避免内存泄漏或悬垂指针
  • Cargo.toml 中需要添加 cc 作为 build-dependency

屏幕截图实现

Tauri 可以通过 screenshots-rs 库实现屏幕截图功能。

安装依赖

toml
# Cargo.toml
[dependencies]
screenshots = "0.8"
image = "0.24"
base64 = "0.21"

Rust 后端实现

rust
use screenshots::Screen;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};

#[derive(serde::Serialize)]
pub struct ScreenInfo {
    pub id: i32,
    pub name: String,
    pub width: u32,
    pub height: u32,
}

#[tauri::command]
pub async fn get_screens() -> Result<Vec<ScreenInfo>, String> {
    let screens = Screen::all().map_err(|e| e.to_string())?;
    Ok(screens.iter().map(|s| ScreenInfo {
        id: s.display_info.id,
        name: s.display_info.name.clone(),
        width: s.display_info.width,
        height: s.display_info.height,
    }).collect())
}

#[tauri::command]
pub async fn capture_screen(screen_id: i32) -> Result<String, String> {
    let screens = Screen::all().map_err(|e| e.to_string())?;
    let screen = screens.iter()
        .find(|s| s.display_info.id == screen_id)
        .ok_or("Screen not found")?;

    let image = screen.capture().map_err(|e| e.to_string())?;
    let mut buffer = Vec::new();
    image.save_to(&mut std::io::Cursor::new(&mut buffer), image::ImageFormat::Png)
        .map_err(|e| e.to_string())?;

    Ok(BASE64.encode(&buffer))
}

前端调用

javascript
import { invoke } from '@tauri-apps/api/tauri';

// 获取屏幕列表
const screens = await invoke('get_screens');

// 截取主屏幕
const base64 = await invoke('capture_screen', { screenId: screens[0].id });

// 显示截图
const img = document.createElement('img');
img.src = `data:image/png;base64,${base64}`;
document.body.appendChild(img);

Mac 应用打包与上架

1. 配置 tauri.conf.json

json
{
  "package": {
    "productName": "YourApp",
    "version": "1.0.0"
  },
  "tauri": {
    "bundle": {
      "identifier": "com.yourcompany.yourapp",
      "icon": ["icons/32x32.png", "icons/128x128.png", "icons/[email protected]"],
      "active": true,
      "category": "public.app-category.productivity",
      "macOS": {
        "minimumSystemVersion": "10.13",
        "signingIdentity": "Apple Distribution: Your Company (TEAMID)",
        "providerShortName": "TEAMID",
        "entitlements": "entitlements.plist"
      }
    }
  }
}

2. 配置权限文件

xml
<!-- entitlements.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.screen-capture</key>
    <true/>
</dict>
</plist>

3. 构建与签名

bash
# 构建 Universal Binary(同时支持 Intel 和 Apple Silicon)
npm run tauri build -- --target universal-apple-darwin

# 验证签名
codesign --verify -vvvv ./src-tauri/target/release/bundle/macos/YourApp.app

4. 应用公证(Notarization)

苹果要求所有应用必须经过公证才能在 macOS 上正常运行:

bash
xcrun altool --notarize-app \
  --primary-bundle-id "com.yourcompany.yourapp" \
  --username "[email protected]" \
  --password "app-specific-password" \
  --file "./src-tauri/target/release/bundle/macos/YourApp.app"

公证密码需要在 Apple ID 账户中生成应用专用密码,不能用普通登录密码。

5. 上传到 App Store Connect

bash
# 使用 Transporter 应用或命令行上传
xcrun altool --upload-app \
  --type macos \
  --file "./src-tauri/target/release/bundle/macos/YourApp.pkg" \
  --username "[email protected]" \
  --password "app-specific-password"

上架前检查清单

  • 隐私政策 URL
  • 所有权限请求都有使用说明
  • 应用描述和关键词
  • 各尺寸截图(根据 Apple 要求)
  • 测试账号(如果应用需要登录)
  • 确保没有使用私有 API
  • 确保没有违反 App Store 审核指南

Tauri vs Electron 对比

维度TauriElectron
后端语言RustNode.js
打包体积最小约 600KB通常 100MB+
内存占用较低较高
安全模型默认沙箱 + 权限控制完全系统访问
跨平台Windows / macOS / LinuxWindows / macOS / Linux
学习曲线需要 Rust 基础JavaScript 开发者友好

Tauri 适合对包体积和性能有要求的应用,Electron 则在生态成熟度和开发门槛上有优势,根据项目需求选择即可。

分享: