بنقرة واحدة
api-development
// Tauri Command (IPC API) 开发技能,指导如何设计和实现 Rust Command 供前端调用。 触发场景: - 需要创建新的 Tauri Command - 需要设计前后端通信接口 - 需要处理 Command 的参数和返回值 - 需要实现异步 Command 触发词:Command、API、invoke、IPC、接口、通信、前后端
// Tauri Command (IPC API) 开发技能,指导如何设计和实现 Rust Command 供前端调用。 触发场景: - 需要创建新的 Tauri Command - 需要设计前后端通信接口 - 需要处理 Command 的参数和返回值 - 需要实现异步 Command 触发词:Command、API、invoke、IPC、接口、通信、前后端
Tauri 打包与分发技能,指导桌面端(Windows/macOS/Linux)与移动端(Android/iOS)的构建、签名和分发。 触发场景: - 需要构建生产安装包(exe/dmg/deb/apk/aab/ipa) - 需要配置各平台打包参数 - 需要代码签名(Windows 证书 / Android keystore / iOS 描述文件) - 需要减小安装包体积 - 需要设置应用图标和元数据 - 需要打包 Android / iOS App 触发词: 打包、构建、build、发布、安装包、exe、dmg、deb、apk、aab、ipa、签名、分发、release、android build、ios build、移动端打包
Git 工作流与版本管理技能,规范分支策略、提交信息和发布流程。 触发场景: - 用户需要创建分支或合并代码 - 用户需要规范提交信息格式 - 用户需要管理版本发布流程 触发词:Git、分支、提交、合并、版本发布
发布 Tauri 桌面应用新版本,处理版本号同步、Git tag、GitHub Actions 构建、Release 仓库产物同步、Cloudflare R2 上传、update.json 生成、自动更新发布和文档站重建。 触发场景: - 需要发布新版本或 release - 需要打 tag、推送发布分支或触发 CI 构建 - 需要生成或更新 update.json - 需要处理 Tauri updater 签名产物 - 需要同步 R2 CDN、GitHub/Gitee release 仓库或发布安装包 触发词:发布、release、版本发布、推送、打Tag、update.json、签名构建、安装包、自动更新、R2、CDN
排查已发生的问题、定位 Bug 原因。 触发场景: - 代码运行报错,需要定位原因 - 功能不正常,需要排查 - Tauri Command 返回错误,需要分析 - 日志分析、调试代码 触发词:Bug、报错、不工作、调试、排查、为什么、出问题、失败、不生效、无效、找不到原因、定位问题
排查已发生的问题、定位 Bug 原因。 触发场景: - 代码运行报错,需要定位原因 - 功能不正常,需要排查 - Tauri Command 返回错误,需要分析 - 日志分析、调试代码 触发词:Bug、报错、不工作、调试、排查、为什么、出问题、失败、不生效、无效、找不到原因、定位问题
Git 工作流与版本管理技能,规范分支策略、提交信息和发布流程。 触发场景: - 用户需要创建分支或合并代码 - 用户需要规范提交信息格式 - 用户需要管理版本发布流程 触发词:Git、分支、提交、合并、版本发布
| name | api-development |
| description | Tauri Command (IPC API) 开发技能,指导如何设计和实现 Rust Command 供前端调用。 触发场景: - 需要创建新的 Tauri Command - 需要设计前后端通信接口 - 需要处理 Command 的参数和返回值 - 需要实现异步 Command 触发词:Command、API、invoke、IPC、接口、通信、前后端 |
在 Tauri 中,前后端通信通过 Command 实现,替代传统 Web 应用的 HTTP REST API。
传统 Web: GET /api/users → Controller → Service → DAO
Tauri: invoke("get_users") → Command → Service → Database
1. 定义数据模型 (models.rs: struct + Serialize/Deserialize)
2. 实现 Database 层 (database/mod.rs: CRUD 方法)
3. 实现 Service 层(可选) (services/*.rs: 业务逻辑)
4. 实现 Command 层 (commands/*.rs: #[tauri::command])
5. 注册到 Builder (lib.rs: generate_handler![])
6. 定义 TypeScript 接口 (src/types/index.ts)
7. 封装前端 API (src/lib/api/index.ts)
8. 组件中调用 (src/pages/*.tsx)
// src-tauri/src/models.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub key: String,
pub value: String,
}
#[derive(Debug, Deserialize)]
pub struct SetConfigInput {
pub key: String,
pub value: String,
}
// src-tauri/src/database/mod.rs
use crate::error::AppError;
use crate::models::AppConfig;
impl Database {
/// 获取所有配置
pub fn get_all_config(&self) -> Result<Vec<AppConfig>, AppError> {
let conn = self.conn.lock()
.map_err(|e| AppError::Custom(e.to_string()))?;
let mut stmt = conn.prepare("SELECT key, value FROM app_config ORDER BY key")?;
let configs = stmt
.query_map([], |row| {
Ok(AppConfig {
key: row.get(0)?,
value: row.get(1)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(configs)
}
/// 获取单个配置
pub fn get_config(&self, key: &str) -> Result<Option<String>, AppError> {
let conn = self.conn.lock()
.map_err(|e| AppError::Custom(e.to_string()))?;
let mut stmt = conn.prepare("SELECT value FROM app_config WHERE key = ?1")?;
Ok(stmt.query_row([key], |row| row.get(0)).ok())
}
/// 设置配置(Upsert)
pub fn set_config(&self, key: &str, value: &str) -> Result<(), AppError> {
let conn = self.conn.lock()
.map_err(|e| AppError::Custom(e.to_string()))?;
conn.execute(
"INSERT INTO app_config (key, value, updated_at)
VALUES (?1, ?2, datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at",
[key, value],
)?;
Ok(())
}
/// 删除配置
pub fn delete_config(&self, key: &str) -> Result<bool, AppError> {
let conn = self.conn.lock()
.map_err(|e| AppError::Custom(e.to_string()))?;
let affected = conn.execute("DELETE FROM app_config WHERE key = ?1", [key])?;
Ok(affected > 0)
}
}
// src-tauri/src/services/config_service.rs
use crate::database::Database;
use crate::error::AppError;
pub struct ConfigService;
impl ConfigService {
pub fn new() -> Self {
Self
}
/// 业务逻辑:获取必需配置(不存在则报错)
pub fn get_required(&self, db: &Database, key: &str) -> Result<String, AppError> {
db.get_config(key)?
.ok_or_else(|| AppError::NotFound(format!("配置 {} 不存在", key)))
}
/// 业务逻辑:验证配置值格式
pub fn set_with_validation(
&self,
db: &Database,
key: &str,
value: &str,
) -> Result<(), AppError> {
// 示例:验证主题值
if key == "theme" && !["light", "dark"].contains(&value) {
return Err(AppError::InvalidInput(
"主题只能是 light 或 dark".into()
));
}
db.set_config(key, value)
}
}
// src-tauri/src/commands/config.rs
use tauri::State;
use crate::database::Database;
use crate::models::AppConfig;
/// 获取所有配置
#[tauri::command]
pub fn get_all_config(db: State<'_, Database>) -> Result<Vec<AppConfig>, String> {
db.get_all_config()
.map_err(|e| e.to_string())
}
/// 获取单个配置
#[tauri::command]
pub fn get_config(db: State<'_, Database>, key: String) -> Result<String, String> {
db.get_config(&key)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("配置 {} 不存在", key))
}
/// 设置配置
#[tauri::command]
pub fn set_config(
db: State<'_, Database>,
key: String,
value: String,
) -> Result<(), String> {
// 可以在这里调用 Service 层做业务验证
db.set_config(&key, &value)
.map_err(|e| e.to_string())
}
/// 删除配置
#[tauri::command]
pub fn delete_config(db: State<'_, Database>, key: String) -> Result<bool, String> {
db.delete_config(&key)
.map_err(|e| e.to_string())
}
// src-tauri/src/commands/mod.rs
pub mod config;
pub mod system;
// src-tauri/src/lib.rs
mod commands;
mod database;
mod error;
mod models;
mod services;
use database::Database;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.setup(|app| {
let data_dir = app.path().app_data_dir()?;
std::fs::create_dir_all(&data_dir)?;
let db_path = data_dir.join("app.db");
let db = Database::init(db_path.to_str().unwrap())?;
app.manage(db);
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::config::get_all_config,
commands::config::get_config,
commands::config::set_config,
commands::config::delete_config,
commands::system::get_system_info,
commands::system::greet,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
// src/types/index.ts
export interface AppConfig {
key: string;
value: string;
}
export interface SystemInfo {
os: string;
arch: string;
app_version: string;
data_dir: string;
}
// src/lib/api/index.ts
import { invoke } from "@tauri-apps/api/core";
import type { AppConfig, SystemInfo } from "@/types";
/** 配置管理 API */
export const configApi = {
getAll: () => invoke<AppConfig[]>("get_all_config"),
get: (key: string) => invoke<string>("get_config", { key }),
set: (key: string, value: string) =>
invoke<void>("set_config", { key, value }),
delete: (key: string) => invoke<boolean>("delete_config", { key }),
};
/** 系统相关 API */
export const systemApi = {
greet: (name: string) => invoke<string>("greet", { name }),
getSystemInfo: () => invoke<SystemInfo>("get_system_info"),
};
// src/pages/ConfigPage.tsx
import { useState, useEffect } from "react";
import { Button, Table, message } from "antd";
import { configApi } from "@/lib/api";
import type { AppConfig } from "@/types";
export default function ConfigPage() {
const [configs, setConfigs] = useState<AppConfig[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadConfigs();
}, []);
async function loadConfigs() {
setLoading(true);
try {
const data = await configApi.getAll();
setConfigs(data);
} catch (error) {
message.error(`加载失败: ${error}`);
} finally {
setLoading(false);
}
}
async function handleDelete(key: string) {
try {
const deleted = await configApi.delete(key);
if (deleted) {
message.success("删除成功");
loadConfigs();
}
} catch (error) {
message.error(`删除失败: ${error}`);
}
}
return (
<div className="p-6">
<Table
dataSource={configs}
rowKey="key"
loading={loading}
columns={[
{ title: "键", dataIndex: "key" },
{ title: "值", dataIndex: "value" },
{
title: "操作",
render: (_, record) => (
<Button danger size="small" onClick={() => handleDelete(record.key)}>
删除
</Button>
),
},
]}
/>
</div>
);
}
| 操作 | Rust 函数名 | invoke 调用 | HTTP 类比 |
|---|---|---|---|
| 查询列表 | get_all_config | invoke("get_all_config") | GET /api/config |
| 查询单个 | get_config | invoke("get_config", { key }) | GET /api/config/:key |
| 创建 | create_user | invoke("create_user", { input }) | POST /api/users |
| 更新 | update_user | invoke("update_user", { id, input }) | PUT /api/users/:id |
| 删除 | delete_config | invoke("delete_config", { key }) | DELETE /api/config/:key |
// ✅ 正确:Rust 参数名用 snake_case
#[tauri::command]
fn create_user(user_name: String, user_email: String) -> Result<User, String> {
// ...
}
// 前端调用:TypeScript 参数名用 camelCase(Tauri 自动转换)
await invoke("create_user", {
userName: "Alice",
userEmail: "alice@example.com"
});
// ✅ 无返回值
#[tauri::command]
fn do_action() -> Result<(), String>
// ✅ 返回单个对象
#[tauri::command]
fn get_item(id: u32) -> Result<Item, String>
// ✅ 返回列表
#[tauri::command]
fn list_items() -> Result<Vec<Item>, String>
// ✅ 返回简单值
#[tauri::command]
fn get_count() -> Result<u32, String>
// ✅ 返回 Option(前端判断 null)
#[tauri::command]
fn find_item(id: u32) -> Result<Option<Item>, String>
// 异步 Command(不阻塞主线程)
#[tauri::command]
async fn fetch_url(url: String) -> Result<String, String> {
reqwest::get(&url)
.await
.map_err(|e| e.to_string())?
.text()
.await
.map_err(|e| e.to_string())
}
// 前端调用(无需改变)
const result = await invoke<string>("fetch_url", { url: "https://example.com" });
// 注入 AppHandle
#[tauri::command]
fn with_app(app: tauri::AppHandle) -> Result<String, String> {
let data_dir = app.path().app_data_dir()
.map_err(|e| e.to_string())?;
Ok(data_dir.to_string_lossy().into())
}
// 注入 Window
#[tauri::command]
fn with_window(window: tauri::Window) -> Result<(), String> {
window.set_title("新标题")
.map_err(|e| e.to_string())?;
Ok(())
}
// 注入 State
#[tauri::command]
fn with_state(state: tauri::State<'_, Database>) -> Result<String, String> {
// 使用 state...
}
// 组合注入
#[tauri::command]
async fn complex_cmd(
app: tauri::AppHandle,
window: tauri::Window,
state: tauri::State<'_, Database>,
user_id: u32, // 前端参数
) -> Result<String, String> {
// 使用所有注入对象...
Ok("done".into())
}
| 错误做法 | 正确做法 |
|---|---|
| 把所有逻辑放在 Command | 使用三层架构(Command → Service → Database) |
| Command 不返回 Result | 始终返回 Result<T, String> |
| 忘记注册新 Command | 添加到 generate_handler![] |
| 前端不处理 invoke 错误 | 每次 invoke 都 try-catch |
| struct 忘记 derive Serialize | 添加 #[derive(Serialize, Deserialize)] |
| 前端直接 invoke 不封装 | 在 lib/api/ 中封装 API |
| Rust 参数名用 camelCase | Rust 用 snake_case,TypeScript 用 camelCase |
不使用 @/ 路径别名 | 统一使用 @/types, @/lib/api |
models.rs 定义database/mod.rs 实现commands/*.rs 实现lib.rs 注册src/types/index.ts 定义src/lib/api/index.tsResult<T, String>try-catch 处理错误