使用 Tonic 构建 gRPC 服务:从入门到实践

什么是 Tonic?

Tonic 是一个基于 Rust 的高性能 gRPC 实现,它提供了构建 gRPC 客户端和服务器的全套工具。Tonic 使用 HTTP/2 作为传输协议,并支持异步/等待模式,非常适合构建高效的分布式系统。

为什么选择 Tonic?

  • 高性能:基于 Tokio 异步运行时和 Hyper HTTP/2 实现
  • 类型安全:Rust 的强类型系统保证接口安全
  • 易用性:简洁的 API 和良好的开发者体验
  • 生态系统:与 Rust 生态无缝集成

快速开始

1. 创建项目并添加依赖

首先创建一个新的 Rust 项目,然后在 Cargo.toml 中添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[package]
name = "helloworld-tonic"
version = "0.1.0"
edition = "2024"

[[bin]] # 用于运行 gRPC 服务端
name = "helloworld-server"
path = "src/server.rs"

[[bin]] # 用于运行 gRPC 客户端
name = "helloworld-client"
path = "src/client.rs"

[dependencies]
tonic = "*"
prost = "0.13"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }

[build-dependencies]
tonic-build = "*"

2. 定义 Protocol Buffers 接口

在项目根目录下创建 proto/helloworld.proto 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
syntax = "proto3";
package helloworld;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

3. 构建 Protocol Buffers 代码

创建 build.rs 文件来自动生成 Rust 代码:

1
2
3
4
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}

4. 实现 gRPC 服务端

src/server.rs 中实现服务端逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use tonic::{transport::Server, Request, Response, Status};

use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
println!("Got a request: {:?}", request);

let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};

Ok(Response::new(reply))
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:50051".parse()?;
let greeter = MyGreeter::default();

println!("Server listening on {}", addr);

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}

5. 实现 gRPC 客户端

src/client.rs 中创建客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use hello_world::{HelloRequest};
use hello_world::greeter_client::GreeterClient;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://127.0.0.1:50051").await?;

let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response);

Ok(())
}

测试服务

1. 启动服务端

1
cargo run --bin helloworld-server

2. 运行客户端测试

1
cargo run --bin helloworld-client

3. 使用 grpcurl 测试

1
2
grpcurl -plaintext -import-path ./proto -proto helloworld.proto \
-d '{"name": "Tonic"}' '127.0.0.1:50051' helloworld.Greeter/SayHello

常见问题解决

1. 版本兼容性问题

确保 tonictonic-buildprost 版本兼容。推荐使用固定版本而非通配符(*)。

2. Protocol Buffers 编译器

确保已安装 protoc 编译器,可以从 Protocol Buffers 发布页 下载。

3. 连接问题

如果遇到连接问题,尝试:

  • 检查服务是否正在运行
  • 确认端口是否正确
  • 尝试使用 127.0.0.1 替代 localhost

总结

Tonic 提供了一个强大而简洁的方式来构建 gRPC 服务。通过本文的指南,你应该能够快速搭建起一个基本的 gRPC 服务,并理解其核心概念。随着对 Tonic 的深入使用,你可以探索更多高级特性,如流式 RPC、拦截器和自定义错误处理等。

Happy coding with Tonic!

Rust Cargo封面图

作为Rust生态的核心工具,Cargo远不止是一个包管理器。本文将系统介绍其核心概念、目录结构和实用技巧,助你高效管理Rust项目。

一、核心概念解析

1. Crate:Rust的模块化单元

  • 定义:Rust中的依赖包或编译单元(官方文档
  • 特点:
    • 可以生成库(lib)或可执行文件(bin)
    • 通过crates.io集中分发

2. Feature:灵活的编译选项

1
2
[dependencies]
serde = { version = "1.0", features = ["derive"] }

📌 关键点

  • 允许选择性启用依赖项的功能
  • 减少最终二进制体积
  • 官方推荐Feature规范

3. 项目类型对比

类型 入口文件 用途 编译命令
lib src/lib.rs 作为库被其他项目引用 cargo build
bin src/main.rs 生成可执行程序 cargo run

二、项目目录结构详解

1
2
3
4
5
6
7
8
.
├── Cargo.lock # 精确锁定的依赖版本([作用说明](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html))
├── Cargo.toml # 项目元数据和依赖声明
├── examples/ # 示例代码(每个文件都是独立可执行)
├── src/ # 主代码目录
│ ├── lib.rs # 库项目入口
│ └── main.rs # 二进制项目入口
└── tests/ # 集成测试(自动被`cargo test`加载)

💡 测试小技巧

1
2
3
4
5
6
7
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
  • 单元测试:直接写在模块中
  • 集成测试:tests/目录下的独立文件

三、依赖管理实战

1. 三种依赖声明方式

1
2
3
4
[dependencies]
rand = "0.8.5" # crates.io版本
tokio = { git = "https://github.com/tokio-rs/tokio" } # Git仓库
local_lib = { path = "../my-local-lib" } # 本地路径

2. 版本控制语义

符号 示例 含义
^ ^1.2.3 允许1.x.x但不包括2.0.0
~ ~1.2.3 允许1.2.x但不包括1.3.0
* * 任意版本(不推荐生产环境使用)

⚠️ 安全建议:生产项目应使用Cargo.lock固定精确版本

3. 紧急补丁方案

1
2
[patch.crates-io]
some-crate = { git = "https://github.com/user/some-crate", branch = "fix-bug" }

官方文档参考:Overriding Dependencies


四、高级功能:构建脚本

build.rs的典型用途:

  1. 链接系统库
  2. 代码生成
  3. 编译时环境检查

示例(查找OpenSSL):

1
2
3
4
5
// build.rs
fn main() {
println!("cargo:rerun-if-changed=build.rs");
pkg_config::Config::new().probe("openssl").unwrap();
}

更多案例见构建脚本指南


五、国内开发者必备:更换Cargo源

~/.cargo/config中添加:

1
2
3
4
5
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

可用镜像源:

  • 中科大:ustc
  • 清华大学:tuna
  • 上海交大:sjtu

延伸阅读

  1. The Cargo Book(官方手册)
  2. Rust模块系统详解
  3. Cargo高级特性

Rust 核心概念解析:宏、借用、Move 和生命周期

目标读者:Rust 初学者
技术栈:Rust(Macro、Ownership、Borrowing、Lifetime)


1. 引言

Rust 以其内存安全和零成本抽象著称,但初学者常常对某些核心概念感到困惑。本文将介绍 宏(Macro)借用与引用(Borrowing & References)Move 语义'static 生命周期,帮助新手快速理解 Rust 的核心机制。


2. 宏(Macro)

是什么?

宏是 Rust 的元编程工具,允许在编译时生成或修改代码,减少重复逻辑。

两种主要宏

  1. 声明宏(macro_rules!
    类似于模式匹配,如 println!
    1
    2
    3
    4
    5
    6
    7
    8
    macro_rules! say_hello {
    () => {
    println!("Hello!");
    };
    }
    fn main() {
    say_hello!(); // 输出: Hello!
    }
  2. 过程宏(Procedural Macros)
    更灵活,可操作 AST(抽象语法树),如 #[derive(Debug)]

适用场景

  • 减少重复代码(如批量实现 trait)。
  • 构建 DSL(领域特定语言)。

官方文档Rust Macros


3. 借用与引用(Borrowing & References)

是什么?

Rust 通过 所有权(Ownership) 管理内存,而 借用(Borrowing) 允许临时访问数据而不获取所有权。

两种引用

  1. 不可变引用(&T
    允许多个读取,但不能修改:
    1
    2
    let s = String::from("hello");
    let len = calculate_length(&s); // 借用 s
  2. 可变引用(&mut T
    唯一且独占,可修改数据:
    1
    2
    let mut s = String::from("hello");
    change(&mut s); // 可变借用

借用规则

  1. 同一时间,要么只能有一个可变引用,要么只能有多个不可变引用。
  2. 引用必须始终有效(无悬垂指针)。

示例验证

1
2
3
4
5
6
7
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 允许
let r2 = &s; // 允许
// let r3 = &mut s; // 错误!已有不可变引用
println!("{}, {}", r1, r2);
}

官方文档Rust Ownership


4. Move 语义

是什么?

Rust 中赋值或传参默认是 移动(Move),所有权转移后,原变量失效。

示例

1
2
3
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2
// println!("{}", s1); // 错误!s1 已失效

例外:实现了 Copy trait 的类型(如 i32bool)会复制而非移动:

1
2
3
let x = 5;
let y = x; // 复制,x 仍然有效
println!("x = {}, y = {}", x, y); // 正确

官方文档Rust Move Semantics


5. 'static 生命周期

是什么?

'static 是 Rust 中最长的生命周期,表示数据在整个程序运行期间有效。

常见场景

  • 字符串字面量(存储在二进制中):
    1
    let s: &'static str = "I live forever!";
  • 全局静态变量
    1
    2
    static VALUE: i32 = 42;
    let r: &'static i32 = &VALUE;

注意:不能随意标记 'static,必须确保数据确实不会释放。

官方文档Rust Lifetimes


6. 总结

概念 核心要点
编译时代码生成,减少重复逻辑。
借用/引用 &T 允许多读,&mut T 允许唯一写,遵守所有权规则。
Move 默认转移所有权,Copy 类型例外。
'static 生命周期标记,表示数据永远有效(如字符串字面量)。

7. 进阶学习

希望这篇博客能帮助你理解 Rust 的核心概念! 🚀

为什么选择Tauri?

在当今多平台应用需求旺盛的时代,前端开发者经常面临这样的需求:如何快速将现有的Web应用打包为桌面程序?传统方案如Electron虽然流行,但存在包体积大、性能开销高等问题。而Tauri作为一个新兴的Rust框架,能生成极小的二进制文件(最小仅几百KB),完美解决了这些问题。

下面我将通过实战演示,只需3步即可完成转换,最后还会以it-tools项目为例展示完整流程。


核心三步曲

第一步:安装Tauri CLI

在项目根目录执行:

1
pnpm add -D @tauri-apps/cli@latest

(也支持npm/yarn安装)

第二步:初始化配置

运行初始化命令并填写基本信息:

1
pnpm tauri init

典型配置示例:

1
2
3
4
5
6
✔ 应用名称? my-app  
✔ 窗口标题? My App
✔ 静态资源路径? ../dist
✔ 开发服务器地址? http://localhost:5173
✔ 开发启动命令? pnpm dev
✔ 构建命令? pnpm build

第三步:启动开发模式

1
pnpm tauri dev

此时会同时启动:

  1. 前端开发服务器
  2. 桌面应用窗口

实战案例:it-tools项目改造

让我们用一个真实项目(it-tools)演示完整流程:

1. 克隆项目

1
2
git clone https://github.com/CorentinTh/it-tools.git
cd it-tools

2. 安装依赖

1
pnpm install

3. 常规启动(验证项目)

1
pnpm dev

4. 集成Tauri

1
2
3
pnpm add -D @tauri-apps/cli@latest
pnpm tauri init
# 按提示填写配置(参考上文)

5. 启动桌面版

1
pnpm tauri dev

启动后可以看应用界面
it-tools


常见问题解答

Q:构建后的应用体积有多大?
A:基础应用约2MB,相比Electron的100MB+有巨大优势。

Q:支持哪些前端框架?
A:所有主流框架(React/Vue/Svelte等)均可使用,Tauri与框架无关。

Q:如何自定义窗口样式?
修改tauri.conf.json中的:

1
2
3
4
5
6
7
8
9
"windows": [
{
"title": "My App",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
]

进阶技巧

  1. 添加系统托盘图标:通过Rust代码实现
  2. 调用原生API:如文件系统操作
  3. 打包发布pnpm tauri build生成各平台安装包

提示:Tauri的Rust后端能力可以逐步探索,初期只需关注前端整合即可。


结语

通过这个教程,我们仅用几条命令就实现了:

  • 保留原有前端开发体验
  • 获得原生桌面应用的所有特性
  • 最终产物极致轻量

如果你正在寻找Electron的替代方案,Tauri绝对是2025年最值得尝试的技术之一。

(完整代码示例可在GitHub获取)
https://github.com/tuyu79/it-tools

引言

系统托盘图标是桌面应用程序中常见的功能,它允许应用在窗口关闭后继续在后台运行,并通过托盘图标提供快捷操作。本文将详细介绍如何为Tauri应用添加这一实用功能。

准备工作

在开始之前,请确保你已经:

  1. 安装了最新版本的Rust和Tauri
  2. 创建了一个基础的Tauri项目

实现步骤

第一步:启用Tauri的托盘功能特性

首先,我们需要在Cargo.toml文件中启用 tray-icon 和 unstable 特性:

1
2
[dependencies]
tauri = { version = "2", features = ["tray-icon","unstable"] }

这个配置会告诉Tauri我们需要使用系统托盘功能。

第二步:编写系统托盘实现代码

以下是完整的实现代码,我们将其放在main.rs或适当的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use tauri::{
menu::{Menu, MenuItem},
tray::TrayIconBuilder,
AppHandle, Manager,
};

fn setup_tray(app: &AppHandle) -> tauri::Result<()> {
let main_window = app.get_window("main").unwrap();
let main_window_clone = main_window.clone();

// 监听窗口关闭事件
main_window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
api.prevent_close();
main_window_clone.hide().unwrap(); // 隐藏窗口而不是关闭
}
});

// 创建托盘菜单项
let show_main = MenuItem::with_id(app, "show_main", "显示主窗口", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show_main, &quit])?;

let app_handle = app.clone();

// 构建并配置托盘图标
TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.show_menu_on_left_click(true) // 左键点击显示菜单
.on_menu_event(move |app: &AppHandle, event| match event.id.as_ref() {
"show_main" => {
app_handle.get_window("main").unwrap().show().unwrap();
}
"quit" => {
app.exit(0);
}
_ => {
println!("未处理的菜单项: {:?}", event.id);
}
})
.build(app)?;

Ok(())
}

代码解析

  1. 窗口关闭处理

    • 我们拦截了窗口的关闭事件,改为隐藏窗口
    • 这确保了应用不会真正退出,而是继续在后台运行
  2. 托盘菜单创建

    • 创建了两个菜单项:”显示主窗口”和”退出”
    • 使用Menu::with_items将它们组合成菜单
  3. 托盘图标配置

    • 使用应用的默认图标作为托盘图标
    • 设置了左键点击显示菜单
    • 为每个菜单项添加了事件处理逻辑

实际应用

要在你的Tauri应用中使用这个功能,只需在应用初始化时调用我们的setup_tray函数:

1
2
3
4
5
6
7
8
9
fn main() {
tauri::Builder::default()
.setup(|app| {
let _ = setup_tray(app.handle());
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

启动后就可以看到效果了

1
pnpm tauri dev

tray icon

注意事项

  1. 跨平台兼容性

    • 不同操作系统对托盘图标的支持可能略有不同
    • 在Linux上可能需要额外的依赖
  2. 图标设计

    • 建议使用清晰简洁的图标
    • 考虑为不同分辨率提供多个尺寸的图标
  3. 用户体验

    • 提供明显的退出选项
    • 考虑添加状态指示功能(如未读消息数)

进阶学习

想要了解更多高级功能,可以参考官方文档:

总结

通过本文的指导,你已经学会了如何为Tauri应用添加系统托盘功能。这个功能可以显著提升你的桌面应用的用户体验,让应用更加专业和实用。


引言

在前端开发中,我们经常需要为桌面应用添加快捷键功能来提升用户体验。本文将介绍如何使用Tauri框架实现通过全局快捷键(如Alt+A)快速创建新窗口的功能。这个功能非常适合需要快速调出辅助窗口的应用场景。

技术栈

  • Tauri - 构建小型、快速、安全的桌面应用框架
  • tauri-plugin-global-shortcut - Tauri全局快捷键插件
  • Vue.js - 前端框架(也可以使用React或其他框架)

基本概念

1. Tauri Webview窗口

Tauri中的WebviewWindow允许我们创建和管理多个浏览器窗口。每个窗口可以加载不同的HTML内容或路由。

2. 全局快捷键

全局快捷键是指在整个操作系统范围内都能响应的快捷键组合,即使应用没有获得焦点也能触发。

3. 进程间通信

Tauri前端(Web)和后端(Rust)通过事件系统进行通信,这使得我们可以在Rust中监听快捷键,然后通知前端创建窗口。

实现步骤

1. 安装必要依赖

首先确保你已经创建了Tauri项目,然后安装全局快捷键插件:

1
pnpm tauri add global-shortcut

依赖安装完后,会自动在 Rust 启动代码里引用插件
add global shortcut plugin

2. 添加创建窗口逻辑

在前端(如Vue组件)中添加窗口创建逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';

const createOrShowShortcutWindow = async () => {
// 检查窗口是否已存在
const existingWindow = await WebviewWindow.getByLabel('shortcut-window');
if (existingWindow) {
await existingWindow.setFocus(); // 存在则聚焦
return;
}

// 创建新窗口
const webview = new WebviewWindow('shortcut-window', {
url: '/', // 加载的路由
center: true, // 居中显示
width: 300, // 宽度
height: 600, // 高度
alwaysOnTop: true // 置顶
});

// 处理窗口创建事件
webview.once('tauri://created', () => webview.show());
webview.once('tauri://error', (e) => {
console.error('窗口创建失败:', e);
});
};

3. 根组件挂载时注册快捷键

1
2
3
4
5
6
7
8
9
10
import {register} from '@tauri-apps/plugin-global-shortcut';

onMounted(async () => {
await register('Alt+A', (event) => {
if (event.state === "Pressed") {
console.log('Shortcut triggered');
createOrShowShortcutWindow()
}
});
});

4. 配置权限

src-tauri/capabilities/desktop.json中添加必要的权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"windows": [
"main"
],
"permissions": [
"global-shortcut:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister",
"core:webview:allow-create-webview",
"core:webview:allow-create-webview-window",
"core:window:allow-show",
"core:window:allow-set-focus",
"core:window:allow-destroy"
]
}

config permission

5. 启动查看效果

1
pnpm tauri dev

按下快捷键 Alt+A, 应该会看到新窗口弹出。
multiple window

示例验证

  1. 运行应用后,按下Alt+A应该会弹出新窗口
  2. 再次按下组合键,如果窗口已存在则会聚焦到该窗口
  3. 可以尝试修改窗口大小、位置等参数查看效果

常见问题

Q: 快捷键没有生效?
A: 检查:

  • 是否正确安装了插件
  • 权限配置是否正确
  • 快捷键是否被其他应用占用

Q: 窗口创建失败?
A: 检查:

  • 新窗口配置的路由是否存在
  • 窗口label是否唯一
  • 控制台错误信息

进阶学习

想要了解更多高级功能,可以参考官方文档:

结语

通过本文,我们学习了如何在Tauri应用中实现全局快捷键创建窗口的功能。这个功能可以大大提升应用的易用性。Tauri的强大之处在于它让前端开发者也能轻松实现原生功能,而无需深入了解系统级编程。

前言

在现代应用开发中,经常需要将高性能的Rust代码与灵活的前端界面结合。Tauri框架为此提供了优雅的解决方案。本文将带你了解如何使用Tauri实现Rust与前端之间的双向通信。

技术栈介绍

我们使用以下技术:

  • Tauri:一个构建小型、快速、安全的桌面应用程序的框架
  • Rust:系统级编程语言,提供高性能和内存安全
  • Vue/React(示例中使用Vue):现代前端框架

基本原理

Tauri提供了一个安全的桥梁,允许:

  1. 前端JavaScript调用Rust函数(命令)
  2. Rust代码触发前端JavaScript事件

这种通信基于进程间通信(IPC)机制,既安全又高效。


第一部分:前端调用Rust函数

第一步:Rust端定义命令

在Rust中,我们需要定义一个可以被前端调用的”命令”:

1
2
3
4
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}

关键点说明

  • #[tauri::command]宏标记这个函数为可从前端调用的命令
  • 函数接收一个String参数,返回一个String
  • 命令名默认与函数名相同(这里是greet

第二步:注册命令

main.rs中注册这个命令:

1
2
3
4
5
6
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

第三步:前端调用命令

在前端代码中(以Vue 3为例):

1
2
3
4
5
6
7
8
9
10
11
import { invoke } from '@tauri-apps/api/core';

async function greet() {
try {
const result = await invoke('greet', { name: 'gulou' });
console.log("收到Rust的回复: ", result);
// 输出: "收到Rust的回复: Hello, gulou!"
} catch (error) {
console.error("调用失败:", error);
}
}

最佳实践

  1. 总是使用try/catch处理可能的错误
  2. 参数需要与Rust函数签名匹配
  3. 使用TypeScript可以获得更好的类型提示

第二部分:Rust调用前端

第一步:前端设置事件监听

在前端组件中(如Vue的onMounted钩子):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { listen } from '@tauri-apps/api/event';

const unlisteners = ref(null);

onMounted(async () => {
try {
const unlisten = await listen('done', (event) => {
console.log("收到Rust的消息: ", event.payload);
});
unlisteners.value = unlisten;
} catch (error) {
console.error("监听失败:", error);
}
});

onUnmounted(() => {
unlisteners.value();
});

第二步:Rust发送事件

在Rust代码中:

1
2
3
4
use tauri::{Emitter};

// 假设app是你的Tauri应用实例
app.emit("done", "hello").unwrap();

高级用法

  • emit发送所有 listener,这里还包括 rust 的 listener
  • emit_to发送给指定的 webview
  • 可以发送任何实现了Serialize的结构体

完整示例:计数器应用

Rust部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use tauri::{Emitter};

#[tauri::command]
fn increment(count: i32) -> i32 {
count + 1
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![increment])
.setup(|app| {
// 启动后2秒发送通知
let app_handle = app.handle().clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(2));
app_handle.emit("ready", ()).unwrap();
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

前端部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script setup>
import {ref, onMounted, onUnmounted} from 'vue';
import {invoke} from '@tauri-apps/api/core';
import {listen} from '@tauri-apps/api/event';

const count = ref(0);
const unlisten = ref(null);

onMounted(async () => {
unlisten.value = await listen('ready', () => {
console.log('应用已准备好');
});
});

onUnmounted(() => {
unlisten.value()
})

const increment = async () => {
count.value = await invoke('increment', {count: count.value});
};
</script>

<template>
<button @click="increment">计数: {{ count }}</button>
</template>

最终效果

counter


调试技巧

  1. 前端调试

    • 使用浏览器开发者工具
    • 添加console.log检查通信数据
  2. Rust调试

    • 使用println!宏输出日志
    • 配置RUST_LOG=debug环境变量获取详细日志
  3. 常见问题

    • 确保命令已正确注册
    • 检查事件名称拼写是否一致
    • 验证数据格式是否符合预期

进阶学习

  1. Tauri官方文档(Rust 调用前端)
  2. Tauri官方文档(前端调用 Rust)
  3. 流式数据处理

通过实现前后端的双向通信,你可以构建复杂的Rust-前端交互应用,充分发挥两者的优势!

引言

你是否想过用前端技术开发桌面应用?Tauri 是一个创新的框架,让你能够使用熟悉的 Web 技术(HTML, CSS, JavaScript)构建小巧、快速的跨平台桌面应用。本文将带你从零开始创建你的第一个 Tauri 应用,并解释其中的关键概念。

技术栈介绍

  • Tauri: 一个基于 Rust 的框架,用于构建轻量级桌面应用
  • Rust: 系统级编程语言,提供高性能和安全性
  • Node.js: JavaScript 运行时环境
  • pnpm: 快速、节省磁盘空间的包管理工具

为什么选择 Tauri?

  1. 体积小巧:生成的应用程序比 Electron 应用小很多
  2. 性能优异:基于 Rust 构建,运行效率高
  3. 跨平台:支持 Windows、macOS 和 Linux
  4. 熟悉的开发体验:使用前端技术栈开发
  5. 安全性强:内置安全最佳实践

环境搭建

1. 安装系统依赖

根据你的操作系统,可能需要安装一些基础工具链。请参考 Tauri 官方文档 获取详细要求。

2. 安装 Rust

Rust 是 Tauri 的核心依赖。打开终端运行以下命令:

1
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

安装完成后,运行以下命令确保 Rust 安装正确:

1
rustc --version

3. 安装 Node.js

推荐使用 nvm 管理 Node.js 版本:

1
2
3
4
5
# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# 安装最新 LTS 版本的 Node.js
nvm install --lts

或者直接从 Node.js 官网 下载安装包。

4. 安装 pnpm

1
npm install -g pnpm 

创建你的第一个 Tauri 应用

1. 初始化项目

使用 pnpm 创建新项目(也可以使用 npm 或 yarn):

1
pnpm create tauri-app

按照提示操作:

  • 输入项目名称(如 my-tauri-app
  • 选择包管理器(pnpm/npm/yarn)
  • 选择前端框架(Vanilla/React/Vue/Svelte 等)或纯 HTML/CSS/JS

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · pnpm
✔ Choose your UI template · Vue - (https://vuejs.org/)
✔ Choose your UI flavor · TypeScript

2. 安装依赖

进入项目目录并安装依赖:

1
2
cd tauri-app
pnpm install

3. 启动开发服务器

运行开发模式:

1
pnpm tauri dev

这将启动两个进程:

  1. 前端开发服务器(通常是 Vite)
  2. Tauri 应用窗口

你会看到一个桌面窗口,里面渲染着你的前端应用!
Tauri Demo

项目结构简介

1
2
3
4
5
6
7
8
my-tauri-app/
├── src/ # 前端源码
├── src-tauri/ # Tauri 相关配置和 Rust 代码
│ ├── Cargo.toml # Rust 项目配置
│ ├── tauri.conf.json # Tauri 应用配置
│ └── src/ # Rust 源码
├── index.html # 主 HTML 文件
└── package.json # 前端项目配置

打包应用

当你的应用准备好发布时,运行:

1
pnpm tauri build

这会在 src-tauri/target/release 目录下生成可执行文件:

  • Windows: .exe 文件
  • macOS: .dmg 文件
  • Linux: .AppImage.deb 文件

Mac Build Image

如果要修改打包选项,可以编辑 src-tauri/tauri.conf.json 后重新打包
Tauri Config

进阶学习

总结

通过本文,你已经学会了:

  1. Tauri 的基本概念和优势
  2. 如何搭建开发环境
  3. 创建和运行 Tauri 应用
  4. 打包应用

Tauri 为前端开发者打开了桌面应用开发的大门,结合了 Web 开发的便利性和原生应用的性能。现在就开始你的 Tauri 开发之旅吧!

技术文档整理


Gradle 配置优先级(从高到低)

来源 示例 覆盖关系
命令行参数 -PmyProp=value 最高优先级
项目级 gradle.properties ./gradle.properties 覆盖用户级配置
用户级 gradle.properties ~/.gradle/gradle.properties 覆盖环境变量
环境变量 ORG_GRADLE_PROJECT_myProp=value 覆盖脚本默认值
settings.gradle gradle.ext.sharedVersion 影响多项目构建
build.gradle ext { myProp = ... } 最低优先级

Gradle 官方文档


Gradle 依赖缓存位置

1
~/.gradle/caches/modules-2/files-2.1/

Maven 配置

1. 配置文件路径

  • 全局配置${maven.home}/conf/settings.xml
  • 用户配置${user.home}/.m2/settings.xml

    ⚠️ 用户配置优先级更高(合并时覆盖相同配置)。

2. 仓库类型

类型 路径/协议 说明
本地仓库 ${user.home}/.m2/repository 缓存下载的依赖或本地安装
远程仓库 file://https:// 协议 从远程服务器获取依赖

3. 常用插件

  • maven-compiler-plugin:编译 Java 文件(官方提供)。
  • kotlin-maven-plugin:编译 Kotlin 文件(Kotlin 官方提供)。
  • spring-boot-maven-plugin:打包 Spring Boot 为可执行 JAR。

PNPM 全局安装配置

1. 设置全局路径

1
2
pnpm config set store-dir ~/.pnpm-store
pnpm config set global-bin-dir ~/.pnpm-global
  • ~/.pnpm-global 添加到 PATH
    1
    export PATH="$HOME/.pnpm-global:$PATH"

    Windows: $env:PATH += ";$env:USERPROFILE\.pnpm-global"

2. 配置环境变量

  • Linux/macOS(添加到 ~/.bashrc~/.zshrc):
    1
    2
    export PNPM_HOME="$HOME/.pnpm-global"
    export PATH="$PNPM_HOME:$PATH"
  • Windows:通过系统设置添加 PNPM_HOME 变量。

3. 验证配置

1
2
pnpm config list  # 检查路径是否正确
pnpm add -g your-package-name # 重新安装全局包

Shell 配置文件加载时机

文件 加载场景 典型用途
~/.bash_profile 仅登录 Shell(如 SSH、su - 设置环境变量(PATHJAVA_HOME
~/.zshrc 所有交互式 Shell(非脚本运行) 配置别名、主题、插件(如 Oh My Zsh)

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%