【一起学Rust | Tauri2.0框架】基于 Rust 与 Tauri 2.0 框架实现全局状态管理


前言
在现代应用程序开发中,状态管理是构建复杂且可维护应用的关键。随着应用程序规模的增长,组件之间共享和同步状态变得越来越具有挑战性。如果处理不当,状态管理可能会导致代码混乱、难以调试,并最终影响应用程序的性能和可扩展性。
Tauri 2.0 作为一个基于 Rust 的跨平台应用程序开发框架,为我们提供了一个强大的工具集来构建高性能、安全且易于维护的桌面应用程序。结合 Rust 语言的优势,我们可以实现高效且可靠的全局状态管理。
本文将深入探讨如何在 Tauri 2.0 应用程序中实现全局状态管理。我们将从基本概念开始,逐步介绍不同的状态管理方法,并通过实际代码示例演示如何在 Tauri 2.0 项目中应用这些方法。无论你是 Rust 和 Tauri 的新手,还是有经验的开发者,相信都能从本文中获得有价值的知识和实践经验。
文章目录
- 前言
- 一、全局状态管理概述
- 1.1 全局状态管理的挑战
- 1.2 全局状态管理的重要性
- 二、Rust 与 Tauri 2.0 中的状态管理
- 2.1 Rust 的所有权和借用机制
- 2.2 Tauri 2.0 的架构
- 三、Tauri 2.0 内置状态管理
- 3.1 使用 `tauri::State`
- 3.2 状态更新与事件
- 3.3 使用异步互斥锁 (async mutex)
- 3.4 使用`Arc`
- 3.5 使用 Manager Trait来访问状态
- 3.6 修复`Mismatching Types`
- 四、使用第三方状态管理库
- 4.1 `Redux` 启发的状态管理:`Yewdux`
- 4.2 其他状态管理库
- 五、状态管理的最佳实践
- 六、实战案例:构建一个简单的计数器应用
- 6.1 项目设置
- 6.2 后端代码
- 6.3 前端代码
- 6.4 运行应用
- 总结
一、全局状态管理概述
全局状态管理是指在应用程序的多个组件之间共享和同步数据的一种机制。这些数据可以是用户界面状态、应用程序配置、用户数据等。全局状态管理的目标是确保应用程序中的所有组件都能访问和更新相同的状态,从而保持数据的一致性和应用程序的整体协调性。
1.1 全局状态管理的挑战
在大型应用程序中,全局状态管理面临着以下挑战:
- 数据一致性: 确保所有组件都能访问和更新相同的状态,避免数据不一致导致的问题。
- 组件通信: 在不同的组件之间传递状态更新,确保所有相关组件都能及时响应状态变化。
- 性能优化: 避免不必要的状态更新和渲染,提高应用程序的性能。
- 代码可维护性: 保持状态管理代码的清晰和简洁,便于理解和维护。
- 可扩展性: 随着应用程序的增长,状态管理方案能够适应新的需求和变化。
1.2 全局状态管理的重要性
良好的全局状态管理可以带来以下好处:
- 简化组件开发: 组件无需关心状态的来源和更新,只需专注于自身的渲染和逻辑。
- 提高代码可维护性: 将状态管理逻辑集中处理,减少代码重复和冗余。
- 增强应用程序可预测性: 状态变化可追踪、可预测,便于调试和问题排查。
- 提升用户体验: 确保应用程序在不同组件之间保持一致的状态,提供流畅的用户体验。
二、Rust 与 Tauri 2.0 中的状态管理
Rust 语言的特性和 Tauri 2.0 框架的架构为我们提供了多种实现全局状态管理的方式。
2.1 Rust 的所有权和借用机制
Rust 的所有权和借用机制是其内存安全和并发安全的基础。在状态管理中,我们可以利用这些机制来确保状态数据在不同组件之间的安全共享和访问。
- 所有权(Ownership): Rust 中的每个值都有一个被称为其所有者的变量。在任何给定时间,一个值只能有一个所有者。当所有者超出作用域时,该值将被丢弃。
- 借用(Borrowing): 我们可以通过引用(&)来借用一个值,而无需获取其所有权。引用可以是可变的(&mut)或不可变的(&)。
- 生命周期(Lifetime): 生命周期是 Rust 编译器用来确保引用始终有效的机制。
2.2 Tauri 2.0 的架构
Tauri 2.0 采用了一种基于 Web 技术(HTML、CSS、JavaScript)构建前端界面,并使用 Rust 编写后端逻辑的架构。这种架构使得我们可以利用 Web 生态系统中丰富的状态管理库,同时也能利用 Rust 的性能和安全性优势。
Tauri 2.0 提供了以下机制来实现前端和后端之间的通信和状态共享:
- 命令(Commands): 前端可以通过调用 Tauri 提供的命令来与后端进行交互。命令可以接收参数并返回结果。
- 事件(Events): 后端可以向前端发送事件,前端可以监听这些事件并做出响应。
- 状态(State): Tauri 2.0 提供了一个内置的状态管理机制,允许我们在后端管理全局状态,并在前端访问和更新这些状态。
三、Tauri 2.0 内置状态管理
Tauri 2.0 提供了一个简单而强大的内置状态管理机制,可以满足大多数应用程序的需求。
3.1 使用 tauri::State
tauri::State 是 Tauri 2.0 中用于管理全局状态的核心类型。它是一个泛型类型,可以存储任何实现了 Send 和 Sync trait 的类型。
-
定义状态类型:
#[derive(Default)] struct AppState {counter: std::sync::Mutex<i32>, }这里我们定义了一个名为
AppState的结构体,其中包含一个名为counter的字段。counter的类型是std::sync::Mutex<i32>,表示一个受互斥锁保护的 32 位整数。使用互斥锁可以确保多个线程安全地访问和修改counter的值。 -
初始化状态:
fn main() {tauri::Builder::default().manage(AppState::default()).invoke_handler(tauri::generate_handler![increment_counter, get_counter]).run(tauri::generate_context!()).expect("error while running tauri application"); }在
main函数中,我们使用tauri::Builder::manage方法将AppState的一个实例注册为全局状态。 -
在命令中访问状态:
#[tauri::command]fn increment_counter(state: tauri::State<AppState>) -> Result<(), String> {let mut counter = state.counter.lock().map_err(|e| e.to_string())?;*counter += 1;Ok(())}#[tauri::command]fn get_counter(state: tauri::State<AppState>) -> Result<i32, String> {let counter = state.counter.lock().map_err(|e| e.to_string())?;Ok(*counter)}
- 在异步命令中访问状态:
#[tauri::command]async fn increment_counter(state: tauri::State<AppState>) -> Result<(), String> {let mut counter = state.counter.await;*counter += 1;Ok(())}#[tauri::command]async fn get_counter(state: tauri::State<AppState>) -> Result<i32, String> {let counter = state.counter.await;Ok(*counter)}
我们定义了两个命令:increment_counter 和 get_counter。这两个命令都接收一个 tauri::State<AppState> 类型的参数,表示对全局状态的引用。
increment_counter命令获取counter的互斥锁,将其值加 1,然后释放锁。get_counter命令获取counter的互斥锁,读取其值,然后释放锁并返回该值。
- 在前端访问状态:
import { invoke } from '@tauri-apps/api/tauri';async function incrementCounter() {await invoke('increment_counter');updateCounter();}async function updateCounter() {const counter = await invoke('get_counter');document.getElementById('counter').textContent = counter;}// 在页面加载时更新计数器updateCounter();
在前端,我们使用 @tauri-apps/api/tauri 提供的 invoke 函数来调用后端命令。
incrementCounter函数调用increment_counter命令,然后在状态更新后调用updateCounter函数。updateCounter函数调用get_counter命令获取计数器的当前值,并将其显示在页面上。
3.2 状态更新与事件
在上面的示例中,我们通过调用 get_counter 命令来获取状态的更新。这种方式在状态更新不频繁的情况下是可行的。但是,如果状态更新非常频繁,或者我们需要在状态更新时立即通知前端,那么使用事件机制会更有效。
-
在后端发送事件:
#[tauri::command] fn increment_counter(state: tauri::State<AppState>, window: tauri::Window) -> Result<(), String> {let mut counter = state.counter.lock().map_err(|e| e.to_string())?;*counter += 1;window.emit("counter-updated", *counter).map_err(|e| e.to_string())?;Ok(()) }在
increment_counter命令中,我们在更新counter的值后,使用window.emit方法向前端发送一个名为"counter-updated"的事件,并将counter的当前值作为事件的负载。 -
在前端监听事件:
import { invoke } from '@tauri-apps/api/tauri'; import { listen } from '@tauri-apps/api/event';async function incrementCounter() {await invoke('increment_counter'); }// 监听 counter-updated 事件 listen('counter-updated', (event) => {document.getElementById('counter').textContent = event.payload; });// 在页面加载时更新计数器 updateCounter();在前端,我们使用
@tauri-apps/api/event提供的listen函数来监听"counter-updated"事件。当事件发生时,事件处理函数会被调用,并将事件的负载(即counter的当前值)显示在页面上。
3.3 使用异步互斥锁 (async mutex)
该功能必须tokio启用
sync特征。
引用Tokio的文档,使用标准库的互斥体而不是Tokio提供的异步互斥体通常是可以的:
与普遍看法相反,在异步代码中使用标准库中的普通互斥体是可以的,而且通常是首选的……异步互斥体的主要用例是提供对IO资源(如数据库连接)的共享可变访问。
你需要充分阅读Tokio的文档以了解两者之间的权衡。需要使用异步互斥的一个原因是,如果您需要在await之间保持MutexGuard。
这种类型的作用类似于 std::sync::Mutex,有两个主要区别:lock 是一个异步方法,因此不会阻塞,并且锁保护被设计为跨 .await 点持有。
Tokio的Mutex在保证FIFO的基础上运行。 这意味着任务调用锁方法的顺序就是它们获取锁的顺序。
也就是说,你通常使用标准库的mutex基本上就能实现你的需求,因为async mutex实现起来成本高,因此Tokio官方直接就推荐使用标准库的mutex了。tokio文档中推荐使用:
Arc<Mutex<...>>定义状态- 生成一个task线程来与主线程通信
并且给出了样例代码
use tokio::sync::Mutex;
use std::sync::Arc;#[tokio::main]
async fn main() {let count = Arc::new(Mutex::new(0));for i in 0..5 {let my_count = Arc::clone(&count);tokio::spawn(async move {for j in 0..10 {let mut lock = my_count.lock().await;*lock += 1;println!("{} {} {}", i, j, lock);}});}loop {if *count.lock().await >= 50 {break;}}println!("Count hit 50.");
}
在这个例子中,有几件事需要注意。
- 互斥体被包裹在Arc中,以允许在线程之间共享。
- 每个生成的任务都会获得一个锁,并在每次迭代时释放它
- Mutex保护的数据的突变是通过取消引用所获得的锁来完成的。
Tokio的Mutex采用简单的FIFO(先进先出)风格,所有锁定调用都按照执行顺序完成。这样,互斥体在如何将锁分配给内部数据方面是“公平的”和可预测的。每次迭代后都会释放并重新获取锁,因此基本上,每个线程在递增一次值后都会转到行的后面。请注意,线程启动之间的时间存在一些不可预测性,但一旦它们启动,它们就会可预测地交替。最后,由于在任何给定时间只有一个有效的锁,因此在改变内部值时不可能出现竞争条件。
请注意,与std::sync::Mutex相反,当持有MutexGuard的线程崩溃时,此实现不会破坏互斥量。 在这种情况下,互斥锁将被解锁。 如果panic被捕获,这可能会使受互斥锁保护的数据处于不一致的状态。
3.4 使用Arc
在 Rust 中,常见用法是使用 Arc 在多个线程之间共享一个值的所有权(通常与 Mutex 配对使用,形式为 Arc<Mutex<T>>)。但是,你不需要对存储在 State 中的内容使用 Arc,因为 Tauri 会为你完成这项工作。
如果 State 的生命周期要求阻止你将状态移动到新线程中,你可以改为将 AppHandle 移动到线程中,然后检索你的状态,如下面“使用 Manager trait 访问状态”部分所示。AppHandle 特意设计成易于克隆,以用于此类用例。
3.5 使用 Manager Trait来访问状态
有时你可能需要在命令之外访问状态,例如在不同的线程中或在像 on_window_event 这样的事件处理程序中。在这种情况下,你可以使用实现了 Manager 特征的类型(例如 AppHandle)的 state() 方法来获取状态:
use tauri::{Builder, GlobalWindowEvent, Manager};#[derive(Default)]
struct AppState {counter: u32,
}// In an event handler:
fn on_window_event(event: GlobalWindowEvent) {// Get a handle to the app so we can get the global state.let app_handle = event.window().app_handle();let state = app_handle.state::<Mutex<AppState>>();// Lock the mutex to mutably access the state.let mut state = state.lock().unwrap();state.counter += 1;
}fn main() {Builder::default().setup(|app| {app.manage(Mutex::new(AppState::default()));Ok(())}).on_window_event(on_window_event).run(tauri::generate_context!()).unwrap();
}
当你不能依赖命令注入时,此方法非常有用。例如,如果你需要将状态移动到使用 AppHandle 更容易的线程中,或者你不在命令上下文中。
3.6 修复Mismatching Types
如果你为
State参数使用了错误的类型,你将得到一个运行时 panic,而不是编译时错误。例如,如果你使用
State<'_, AppState>而不是State<'_, Mutex<AppState>>,则不会有任何状态使用该类型进行管理。
如果你愿意,你可以用类型别名包装你的状态以防止这个错误:
use std::sync::Mutex;#[derive(Default)]
struct AppStateInner {counter: u32,
}type AppState = Mutex<AppStateInner>;
但是,请确保按原样使用类型别名,而不是再次将其包装在 Mutex 中,否则你将遇到同样的问题。
四、使用第三方状态管理库
除了 Tauri 2.0 的内置状态管理机制,我们还可以使用 Rust 生态系统中的第三方状态管理库来实现更复杂的状态管理需求。
4.1 Redux 启发的状态管理:Yewdux
Yewdux 是一个受 Redux 启发的 Rust 状态管理库,它提供了一种基于单向数据流的状态管理模式。
-
安装
Yewdux:cargo add yewdux -
定义状态和
Reducer:use yewdux::prelude::*;#[derive(Default, Clone, PartialEq, Eq, Store)] struct AppState {counter: i32, }#[derive(Clone, PartialEq, Eq)] enum Action {Increment, }impl Reducer<AppState> for Action {fn apply(&self, mut state: Rc<AppState>) -> Rc<AppState> {let state = Rc::make_mut(&mut state);match self {Action::Increment => state.counter += 1,}state.clone().into()} }- 我们定义了一个名为
AppState的结构体,其中包含一个counter字段。 - 我们定义了一个名为
Action的枚举,表示可以对状态执行的操作。 - 我们为
Action实现了Reducer<AppState>trait,定义了如何根据不同的Action来更新状态。
- 我们定义了一个名为
-
在 Tauri 中使用
Yewdux:use yewdux::prelude::*;#[tauri::command] fn increment_counter(dispatch: Dispatch<AppState>) -> Result<(), String> {dispatch.apply(Action::Increment);Ok(()) }#[tauri::command] fn get_counter(dispatch: Dispatch<AppState>) -> Result<i32, String> {Ok(dispatch.get().counter) }fn main() {tauri::Builder::default().setup(|app| {let dispatch = Dispatch::<AppState>::new();app.manage(dispatch);Ok(())}).invoke_handler(tauri::generate_handler![increment_counter, get_counter]).run(tauri::generate_context!()).expect("error while running tauri application"); }- 在
main函数中,我们使用Dispatch::<AppState>::new()创建一个Dispatch实例,并将其注册为 Tauri 的全局状态。 - 在命令中,我们通过
Dispatch<AppState>类型的参数来访问和修改状态。 increment_counter命令使用dispatch.apply(Action::Increment)来触发状态更新。get_counter命令使用dispatch.get().counter来获取状态的当前值。
- 在
-
在前端使用
Yewdux:与 Tauri 内置状态管理类似,我们可以使用
invoke函数来调用后端命令,并通过事件或轮询来获取状态更新。
4.2 其他状态管理库
除了 Yewdux,Rust 生态系统中还有其他一些状态管理库可供选择,例如:
Relm4: 一个基于Elm架构的 GUI 库,它内置了状态管理机制。Iced: 一个跨平台的 GUI 库,它也提供了自己的状态管理方案。
五、状态管理的最佳实践
在 Tauri 2.0 应用程序中实现全局状态管理时,可以遵循以下最佳实践:
- 选择合适的状态管理方案: 根据应用程序的复杂度和需求选择合适的状态管理方案。对于简单的应用程序,Tauri 2.0 的内置状态管理机制可能就足够了。对于更复杂的应用程序,可以考虑使用第三方状态管理库。
- 保持状态的单一数据源: 避免在多个地方维护相同的状态,确保状态的唯一性和一致性。
- 使用不可变数据: 尽可能使用不可变数据来表示状态,避免意外的状态修改。
- 最小化状态更新: 仅在必要时更新状态,避免不必要的状态更新和渲染。
- 使用选择器(Selectors): 如果状态数据比较复杂,可以使用选择器来从状态中提取所需的数据,避免在组件中直接访问原始状态。
- 使用调试工具: 利用 Tauri 2.0 和状态管理库提供的调试工具来跟踪状态变化和调试问题。
- 编写测试: 为状态管理逻辑编写单元测试和集成测试,确保状态管理的正确性和稳定性。
六、实战案例:构建一个简单的计数器应用
为了更好地理解如何在 Tauri 2.0 应用程序中实现全局状态管理,我们将构建一个简单的计数器应用。
6.1 项目设置
-
创建新的 Tauri 项目:
cargo tauri init按照提示输入项目名称、窗口标题等信息。
-
安装 Tauri API:
npm install @tauri-apps/api
6.2 后端代码
-
定义状态类型:
// src-tauri/src/main.rs#[derive(Default)] struct AppState {counter: std::sync::Mutex<i32>, } -
实现命令:
// src-tauri/src/main.rs#[tauri::command] fn increment_counter(state: tauri::State<AppState>, window: tauri::Window) -> Result<(), String> {let mut counter = state.counter.lock().map_err(|e| e.to_string())?;*counter += 1;window.emit("counter-updated", *counter).map_err(|e| e.to_string())?;Ok(()) }#[tauri::command] fn get_counter(state: tauri::State<AppState>) -> Result<i32, String> {let counter = state.counter.lock().map_err(|e| e.to_string())?;Ok(*counter) } -
注册状态和命令:
// src-tauri/src/main.rsfn main() {tauri::Builder::default().manage(AppState::default()).invoke_handler(tauri::generate_handler![increment_counter, get_counter]).run(tauri::generate_context!()).expect("error while running tauri application"); }
6.3 前端代码
-
创建 HTML 结构:
<!-- src/index.html --><!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Tauri Counter App</title></head><body><h1>Counter: <span id="counter">0</span></h1><button id="increment-button">Increment</button><script src="main.js"></script></body> </html> -
编写 JavaScript 代码:
// src/main.jsimport { invoke } from '@tauri-apps/api/tauri'; import { listen } from '@tauri-apps/api/event';async function incrementCounter() {await invoke('increment_counter'); }// 监听 counter-updated 事件 listen('counter-updated', (event) => {document.getElementById('counter').textContent = event.payload; });// 在页面加载时更新计数器 async function updateCounter() {const counter = await invoke('get_counter');document.getElementById('counter').textContent = counter; } updateCounter()// 绑定按钮点击事件 document.getElementById('increment-button').addEventListener('click', incrementCounter);
6.4 运行应用
cargo tauri dev
现在,你应该可以看到一个简单的计数器应用。点击 “Increment” 按钮,计数器的值会增加,并且界面会实时更新。
总结
全局状态管理是构建复杂 Tauri 2.0 应用程序的关键。本文深入探讨了 Tauri 2.0 中的全局状态管理,介绍了 Tauri 2.0 的内置状态管理机制以及如何使用第三方状态管理库。通过结合 Rust 语言的优势和 Tauri 2.0 框架的功能,我们可以构建高性能、安全且易于维护的桌面应用程序。
希望本文能够帮助你更好地理解 Tauri 2.0 中的全局状态管理,并在你的项目中应用这些知识。如果你有任何问题或建议,欢迎留言讨论。
相关文章:
【一起学Rust | Tauri2.0框架】基于 Rust 与 Tauri 2.0 框架实现全局状态管理
前言 在现代应用程序开发中,状态管理是构建复杂且可维护应用的关键。随着应用程序规模的增长,组件之间共享和同步状态变得越来越具有挑战性。如果处理不当,状态管理可能会导致代码混乱、难以调试,并最终影响应用程序的性能和可扩…...
扩散模型算法实战——三维重建的应用
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 引言 三维重建是计算机视觉和图形学中的一个重要研究方向,旨在从二维图像或其他传感器数据中恢复…...
社群经济4.0时代:开源链动模式与AI技术驱动的电商生态重构
摘要:在Web3.0技术浪潮与私域流量红利的双重驱动下,电商行业正经历从"流量收割"到"用户深耕"的范式转变。本文基于社群经济理论框架,结合"开源链动21模式"、AI智能名片、S2B2C商城小程序源码等创新工具&#x…...
【Linux系统】进程等待:告别僵尸进程深入理解Linux进程同步的核心密码
Linux系列 文章目录 Linux系列前言一、进程等待的核心目的二、进程等待的实现方式2.1 wait()函数2.2 waitpid()函数 总结 前言 在Linux系统中,进程等待(Process Waiting)是多进程编程中的核心机制,指父进程…...
根据文件名称查询文件所在位置
在 Linux 中,根据文件名称查询文件所在位置主要通过命令行工具实现,以下是几种常用方法: --- ### **1. 使用 find 命令(最灵活)** find 命令可以递归搜索指定目录下的文件,支持按名称、类型、时间等条件过…...
六十天前端强化训练之第二十五天之组件生命周期大师级详解(Vue3 Composition API 版)
欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗,谢谢大佬! 目录 一、生命周期核心知识 1.1 生命周期全景图 1.2 生命周期钩子详解 1.2.1 初始化阶段 1.2.2 挂载阶段 1.2.3 更新阶段 1.2.4 卸载阶段 1.3 生命周期执行顺序 1.4 父子组…...
Pytorch使用手册(专题五十)—自定义运算符
1. PyTorch 自定义运算符 PyTorch 提供了一个庞大的运算符库,这些运算符可以对张量进行操作(例如 torch.add、torch.sum 等)。然而,您可能希望向 PyTorch 引入一个新的自定义操作,并使其能够与诸如 torch.compile、autograd 和 torch.vmap 等子系统协同工作。为此,您必须…...
springboot整合mybatis-plus(保姆教学) 及搭建项目
一、Spring整合MyBatis (1)将MyBatis的DataSource交给Spring IoC容器创建并管理,使用第三方数据库连接池(Druid,C3P0等)代替MyBatis内置的数据库连接池 (2)将MyBatis的SqlSessionFactory交给Spring IoC容器创建并管理,使用spring-mybatis整…...
VSCode创建VUE项目(三)使用axios调用后台服务
1. 安装axios,执行命令 npm install axios 2. 在 main.ts 中引入并全局挂载 Axios 实例 修改后的 代码(也可以单独建一个页面处理Axios相关信息等,然后全局进行挂载) import { createApp } from vue import App from ./App.vue import rou…...
JVM常用垃圾回收器
Serial 和Serial Old收集器 Serial 系列的垃圾收集器采用了简单高效、资源消耗最少、单线程收集的设计思路。 简单高效:由于硬件资源有限,垃圾回收器需要设计得简单高效,以减少系统资源的占用。Serial 系列的垃圾收集器实现简单,…...
车辆模型——运动学模型
文章目录 约束及系统移动机器人运动学模型(Kinematic Model)自行车模型含有加速度 a a a 的自行车模型系统偏差模型 在机器人的研究领域中,移动机器人的系统建模与分析是极为关键的基础环节,本文以非完整约束的轮式移动机器人为研…...
EJS缓存解决多页面相同闪动问题
基于 EJS 的模板引擎特性及其缓存机制,以下是关于缓存相同模块的详细解答: 一、EJS 缓存机制的核心能力 模板编译缓存 EJS 默认会将编译后的模板函数缓存在内存中,当相同模板文件被多次渲染时,会直接复用已编译的模板函数&#x…...
<details>和<summary>标签的用途,如何使用它们实现可折叠内容
大白话和标签的用途,如何使用它们实现可折叠内容 <details> 和 <summary> 标签用途 <details> 和 <summary> 标签是 HTML 里的实用标签,二者配合能创建出可折叠内容。 <details> 标签就像是一个容器,能把那…...
HUGO介绍、安装、以及使用
HUGO官方网站,文章内容的简介大部分来自官网的翻译,官网是纯英文描述,英语好的可以前往官方网站,博主在这里简介中简单翻译处理包括一些链接的引用,主要是讲解一下如何安装和使用。 这里再粘贴一个三方网站opendocs.i…...
【STM32实物】基于STM32的太阳能充电宝设计
基于STM32的太阳能充电宝设计 演示视频: 基于STM32的太阳能充电宝设计 硬件组成: 系统硬件包括主控 STM32F103C8T6、0.96 OLED 显示屏、蜂鸣器、电源自锁开关、温度传感器 DS18B20、继电器、5 V DC 升压模块 、TB4056、18650锂电池、9 V太阳能板、稳压降压 5 V三极管。 功能…...
【Netty】长连接与短连接的不同实现
长连接与短连接的不同实现 配置层面 // 长连接配置 bootstrap.option(ChannelOption.SO_KEEPALIVE, true) // 启用 TCP keepalive.option(ChannelOption.TCP_NODELAY, true); // 禁用 Nagle 算法// 短连接不需要这些配置心跳机制 // 长连接需要心跳 pipeline.addLast(new Idl…...
安装 OpenSSL 1.1.1 的完整脚本适用于 Ubuntu 22.04 系统
#!/bin/bash # 更新系统包 sudo apt-get update # 安装编译工具和依赖库 sudo apt-get install -y build-essential checkinstall zlib1g-dev # 下载 OpenSSL 1.1.1 源码 wget https://www.openssl.org/source/openssl-1.1.1.tar.gz # 检查下载是否成功 if [ $? -ne 0 ]; …...
24-智慧旅游系统(协同过滤算法)
介绍 技术: 基于 B/S 架构 SpringBootMySQLLayuivue 环境: Idea mysql maven jdk1.8 管理端功能 管理端主要用于对系统内的各类旅游资源进行管理,包括用户信息、旅游路线、车票、景点、酒店、美食、论坛等内容。具体功能如下: …...
Vue 中的日期格式化实践:从原生 Date 到可视化展示!!!
📅 Vue 中的日期格式化实践:从原生 Date 到可视化展示 🚀 在数据可视化场景中,日期时间的格式化显示是一个高频需求。本文将以一个邀请码关系树组件为例,深入解析 Vue 中日期格式化的 核心方法、性能优化 和 最佳实践…...
2025年使用Scrapy和Playwright解决网页抓取挑战的方案
0. 引言 随着互联网技术的发展,网页内容呈现方式越来越复杂,大量网站使用JavaScript动态渲染内容,这给传统的网络爬虫带来了巨大挑战。在2025年的网络爬虫领域,Scrapy和Playwright的结合为我们提供了一个强大的解决方案ÿ…...
可靠消息投递demo
以下是一个基于 Spring Boot RocketMQ 的完整分布式事务实战 Demo,包含事务消息、本地事务、自动重试、死信队列(DLQ) 等核心机制。代码已充分注释,可直接运行。 一、项目结构 src/main/java ├── com.example.rocketmq │ …...
阻止 Mac 在运行任务时进入休眠状态
掌握Caffeinate命令:让您的 Mac 保持清醒以完成关键任务 开发人员经常发现自己在 Mac 上运行持续时间较长的进程。无论是大量文件上传、广泛的数据分析脚本,还是复杂的构建过程,我们最不希望的就是我们的机器在任务中途进入睡眠状态。输入 c…...
Copilot提示词库用法:调整自己想要的,记住常用的,分享该共用的
不论你是 Microsoft 365 Copilot 的新用户还是熟练运用的老鸟,不论你是使用copilot chat,还是在office365中使用copilot,copilot提示词库都将帮助你充分使用copilot这一划时代的产品。它不仅可以帮助你记住日常工作中常用的prompt提示词&…...
Python实战(3)-数据库操作
前面说过,可用的SQL数据库引擎有很多,它们都有相应的Python模块。这些数据库引擎大都作为服务器程序运行,连安装都需要有管理员权限。为降低Python DB API的使用门槛,我选择了一个名为SQLite的小型数据库引擎。它不需要作为独立的…...
LeetCode 160 Intersection Of Two Linked Lists 相交链表 Java
题目:找到两个相交列表的起始点,如图c1开始为A和B两个链表的相交点 举例1:8为两个链表的相交点。 注意:相交不止是数值上的相同。 举例2:2为相交点 举例3:没有相交点 解题思路: 相交证明最后一…...
AI Agent中的MCP详解
一、协议定义与核心价值 MCP(Model Context Protocol,模型上下文协议)是由Anthropic公司于2024年11月推出的开放标准协议,其核心目标是通过建立统一接口规范,解决AI模型与外部系统集成效率低下的行业痛点。该协议通过标准化通信机制,使大型语言模型(LLM)能够无缝对接数…...
win系统上自动化安装配置WSL linux和各种生信工具教程
windows系统上自动化安装配置WSL linux系统和各种生信工具教程 高通量测序原始数据的上游分析模块介绍 我开发的OmicsTools软件的这些分析测序原始数据的上游处理分析模块需要使用到linux和linux系统中的一些生信工具,在这里我开发了在windows系统中自动化安装WSL …...
统计可重复列表中的TOP N
文章目录 方案1:HashMap统计 全排序实现步骤:代码实现:优缺点: 方案2:HashMap统计 最小堆(优先队列)实现步骤:代码实现:优缺点: 方案3:Java Str…...
PowerBI纯小白如何驾驭DAX公式一键生成:copilot for fabric
在2025年2月份更新中,powerbi desktop里的copilot功能还新增了一个非常强大的功能:一键生成多个度量值,并直接加载到模型。 直接上示例展示: 打开DAX查询视图,在copilot窗格中直接输入想要生成多个度量值,…...
Pytest的夹具
1、pytest的前置后置夹具 fixture 有些内容是在每个用例执行之前都要运行操作:-- 用例前置 接口:购物车模块先登录 --登录结果 【token鉴权】 UI: 每次用例 打开浏览器 --driver 有些内容在每个用例之后都要运行操作:–用例后置 接口: 数据清除 UI:关闭浏览器 叫做用例的…...
