Tauri2+Leptos开发桌面应用--Sqlite数据库操作

 在之前工作(使用Tauri + Leptos开发带系统托盘桌面应用-CSDN博客)的基础上,继续尝试对本地Sqlite数据库进行读、写、删除操作,开发环境还是VS Code+Rust-analyzer。

最终程序界面如下:

 主要参考文章:Building a todo app in Tauri with SQLite and sqlx

1. 创建项目

还是用create-tauri-app来创建一个新项目,并安装sqlx-cli。

cargo create-tauri-app
cargo install sqlx-cli

create-tauri-app升级到了4.5.9版本,Leptos也从0.6版本更新到了0.7版本。

src-tauri/Cargo.toml文件内容如下: 

[package]
name = "acid-index"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "acid_index_lib"
crate-type = ["staticlib", "cdylib", "rlib"][build-dependencies]
tauri-build = { version = "2", features = [] }[dependencies]
tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
#tauri-plugin-sql = {version = "2", features = ["sqlite"] }  # or "postgres", or "mysql"
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio"] }
tokio = { version ="1", features = ["full"] }
futures = "0.3.31"
log = "0.4.22"#for win7
#tauri-plugin-notification = { version = "2", features = [ "windows7-compat" ] }

VS Code的rust-analyzer插件会自动完成相关依赖包的安装

2. Sqlite数据库操作

 使用sqlx实现对Sqlite数据库的操作,数据库文件名为:db.sqlite,默认在appDataDir文件夹下面,这个找了很久,Win10系统下为:C:\Users\<user_name>\AppData\Roaming\<identifier>\ 文件夹,其中"username"为用户名,"identifier"为tauri.conf.json中定义的"identifier",此处为com.acid-index.app。

本例中新建一个数据库,包含一个users表格。数据库迁移文件在src-tauri/migrates/文件夹下面,使用如下命令生成:

cd src-tauri
sqlx migrate add create_users_table

然后修改 src-tauri/migrates/文件夹下面的sql文件,内容如下:

-- Add migration script here
CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT,  username TEXT NOT NULL,  email TEXT);

还是通过#[tauri::command]新建invoke handle函数实现对数据库的操作,用于前端leptos调用。主要程序在src-tauri\src\lib.rs中,具体如下:

use futures::TryStreamExt;
use sqlx::{migrate::MigrateDatabase, prelude::FromRow, sqlite::SqlitePoolOptions, Pool, Sqlite};
use tauri::{App, Manager};type Db = Pool<Sqlite>;
struct DbState {db: Db,
}async fn setup_db(app: &App) -> Db {let mut path = app.path().app_data_dir().expect("获取程序数据文件夹路径失败!");match std::fs::create_dir_all(path.clone()) {Ok(_) => {}Err(err) => {panic!("创建文件夹错误:{}", err);}};//C:\Users\<user_name>\AppData\Roaming\com.mynewapp.app\db.sqlite path.push("db.sqlite");Sqlite::create_database(format!("sqlite:{}", path.to_str().expect("文件夹路径不能为空!")).as_str(),).await.expect("创建数据库失败!");let db = SqlitePoolOptions::new().connect(path.to_str().unwrap()).await.unwrap();//创建迁移文件位于./migrations/文件夹下    //cd src-tauri//sqlx migrate add create_users_tablesqlx::migrate!("./migrations/").run(&db).await.unwrap();db
}// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {format!("你好, {}!Rust向你问候了!", name)
}use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, FromRow)]
struct User {id: u16,username: String,email: String,
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct UserId {id: u16,
}#[tauri::command]
async fn get_db_value(state: tauri::State<'_, DbState>, window: tauri::Window) -> Result<String, String> {let db = &state.db;let query_result:Vec<User> = sqlx::query_as::<_, User>(       //查询数据以特定的格式输出"SELECT * FROM users").fetch(db).try_collect().await.unwrap();let mut div_content = String::new();for user in query_result.iter(){div_content += &format!(r#"<p>ID:{},姓名:{},邮箱:{}</p>"#, user.id, user.username, user.email);}   // 获取当前窗口let current_window = window.get_webview_window("main").unwrap();let script = &format!("document.getElementById('db-item').innerHTML = '{}';",div_content);current_window.eval(script).unwrap();Ok(String::from("数据库读取成功!"))
}//use tauri::Error;
#[tauri::command]
async fn insert_db_item(state: tauri::State<'_, DbState>, username: &str, email: Option<&str>) -> Result<String, String> {let db = &state.db;email.unwrap_or("not set yet");     //email类型为Option<&str>,其结果为None或者Some(&str)sqlx::query("INSERT INTO users (username, email) VALUES (?1, ?2)").bind(username).bind(email).execute(db).await.map_err(|e| format!("数据库插入项目错误: {}", e))?;Ok(String::from("插入数据成功!"))
}#[tauri::command]
async fn del_last_user(state: tauri::State<'_, DbState>) -> Result<String, String> {let db = &state.db;let last_id:UserId = sqlx::query_as::<_,UserId>("SELECT id FROM users ORDER BY id DESC LIMIT 1").fetch_one(db).await.unwrap();sqlx::query("DELETE FROM users WHERE id = ?1").bind(last_id.id).execute(db).await.map_err(|e| format!("could not delete last user: {}", e))?;Ok(String::from("最后一条数据删除成功!"))
}mod tray;       //导入tray.rs模块#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {tauri::Builder::default().plugin(tauri_plugin_opener::init()).invoke_handler(tauri::generate_handler![greet, get_db_value, insert_db_item, update_user, del_last_user]).setup(|app| {#[cfg(all(desktop))]{let handle = app.handle();tray::create_tray(handle)?;         //设置app系统托盘}tauri::async_runtime::block_on(async move {let db = setup_db(&app).await;         //setup_db(&app:&mut App)返回读写的数据库对象app.manage(DbState { db });                   //通过app.manage(DbState{db})把数据库对象传递给state:tauri::State<'_, DbState>});Ok(())}).run(tauri::generate_context!()).expect("运行Tauri程序的时候出错!");
}

setup_db函数新建了数据库,设置迁移(如数据库文件存在将直接使用),并将数据库返回给了全局状态:tauri::Builder::default().manager(state),这样注册的invoke handle函数就可以直接调用数据库。

对Leptos不熟悉,get_db_value函数对前端界面元素的操作也是在src-tauri\src\lib.rs中通过调用javascript脚本实现的。其实更好的办法是将数据库查询的结果通过invoke传递给前端,前端Leptos再对接收到的JsValue数据进行处理。

具体修改get_db_value函数如下:

#[tauri::command]
async fn get_db_value(state: tauri::State<'_, DbState>) -> Result<Vec<User>, String> {let db = &state.db;let query_result:Vec<User> = sqlx::query_as::<_, User>(       //查询到的数据以特定的格式输出"SELECT * FROM users").fetch(db).try_collect().await.unwrap();Ok(query_result)        //查询结果传递给前端
}

前端需要定义接收数据的格式,然后使用serde_wasm_bindgen::from_value()将invoke得到的JsValue数据格式转换成rust数据格式以供使用(因为前端是Leptos)。

#[derive(Serialize, Deserialize)]
struct User {id: u16,username: String,email: String,
}#[component]
pub fn App(initial_value:i32) -> impl IntoView {         ......let (div_content, set_div_content) = signal(String::new());let get_db_item = move |ev: SubmitEvent| {ev.prevent_default();           //类似javascript中的Event.preventDefault()spawn_local(async move {let user_js = invoke_without_args("send_db_item").await;let user_vec: Vec<User> = serde_wasm_bindgen::from_value(user_js).map_err(|_| JsValue::from("Deserialization error")).unwrap();let mut receive_msg = String::from("读取数据库ID序列为:[");let mut inner_content = String::new();for user in user_vec{receive_msg += &format!("{}, ", user.id);inner_content += &format!(r###"<p><input type="checkbox" name="items" value="{}"> ID:{},姓名:{},邮箱:{}</p>"###, user.id, user.id, user.username, user.email);}receive_msg += "]";set_insert_msg.set(receive_msg);set_div_content.set(inner_content);               });};view! { ......//使用inner_html修改div内容<div class="db-window" id="db-item" inner_html=div_content />......}

 3. 前端Leptos调用

主要是界面元素设计(包括style.css)和Invoke调用,对于没有参数的调用,要增加如下设置:

#[wasm_bindgen]
extern "C" {
+   #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)]
+   async fn invoke_without_args(cmd: &str) -> JsValue;#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] //Tauri API 将会存储在 window.__TAURI__ 变量中,并通过 wasm-bindgen 导入。async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}

src/app.rs文件内容如下:

use leptos::task::spawn_local;
use leptos::{ev::SubmitEvent, prelude::*};
use serde::{Deserialize, Serialize};        //#derive(serialize, Deserialize)用于请求响应传递参数的序列化
use wasm_bindgen::prelude::*;//‌WASM(WebAssembly)是一种低级字节码格式,可以从C、C++、Rust等高级语言编译而来,旨在在Web端实现接近原生的执行效率‌‌
//wasm 并不是传统意义上汇编语言(Assembly),而是一种中间编译的字节码,可以在浏览器上运行非 JavaScript 编写的代码
//wasm-bindgen主要目的是实现Rust与现有JavaScript环境的无缝集成‌,自动生成必要的绑定和胶水代码,确保Rust函数和JavaScript之间平滑通信
#[wasm_bindgen]
extern "C" {#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)]async fn invoke_without_args(cmd: &str) -> JsValue;#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] //Tauri API 将会存储在 window.__TAURI__ 变量中,并通过 wasm-bindgen 导入。async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}//derive是一个编译器指令,在类型定义(如结构体或枚举)上添加#[derive(...)]让编译器为一些特性提供基本的实现,
//...表示要为其提供基本实现的特性列表。#[derive(Serialize, Deserialize)],可以轻松地为结构体实现序列化和反序列化功能。
//要在Rust中使用序列化和反序列化,首先需要在Cargo.toml文件中引入serde库
//serde = { version = "1.0", features = ["derive"] }
//serde_json = "1.0"
//序列化后的变量作为函数invoke(cmd, args: JsValue)的参数,JsValue为序列化格式
#[derive(Serialize, Deserialize)]
struct GreetArgs<'a> {name: &'a str,
}#[derive(Serialize, Deserialize)]
struct InsertArgs<'a> {      //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。username: &'a str,email: &'a str,         
}#[derive(Serialize, Deserialize)]
struct User {id: u16,username: String,email: String,
}//Component是一个重要的概念,它通常指的是项目中的一部分代码或功能模块。
#[component]
pub fn App(initial_value:i32) -> impl IntoView {         //函数返回IntoView类型,即返回view!宏,函数名App()也是主程序view!宏中的组件名(component name)。let (name, set_name) = signal(String::new());let (greet_msg, set_greet_msg) = signal(String::new());let (value, set_value) = signal(initial_value);let (username, set_username) = signal(String::new());let (email, set_email) = signal(String::new());let (insert_msg, set_insert_msg) = signal(String::new());//let (fetch_msg, set_fetch_msg) = signal(String::new());let update_name = move |ev| {       //将闭包传递给按钮组件的onclick参数,鼠标点击触发事件,更新之前创建signal中的值。let v = event_target_value(&ev);set_name.set(v);};let get_db_item = move |ev: SubmitEvent| {ev.prevent_default();           //类似javascript中的Event.preventDefault(),处理<input>字段非常有用spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let new_msg = invoke_without_args("get_db_value").await.as_string().unwrap();set_insert_msg.set(new_msg);});};let greet = move |ev: SubmitEvent| {ev.prevent_default();           //类似javascript中的Event.preventDefault(),处理<input>字段非常有用spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let name = name.get_untracked();    //防止通常由Leptos signal产生的反应式绑定,因此即使值发生变化,Leptos也不会尝试更新闭包。if name.is_empty() {return;}let args = serde_wasm_bindgen::to_value(&GreetArgs { name: &name }).unwrap();   //参数序列化// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/let new_msg = invoke("greet", args).await.as_string().unwrap();     //使用invoke调用greet命令,greet类似于APIset_greet_msg.set(new_msg);});};let update_user = move |ev| {       //将闭包传递给按钮组件的onclick参数,更新之前创建signal中的值。let v = event_target_value(&ev);set_username.set(v);};let update_email = move |ev| {       //将闭包传递给按钮组件的onclick参数,更新之前创建signal中的值。let v = event_target_value(&ev);set_email.set(v);};let insert_db_item = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {let username = username.get_untracked();let email = email.get_untracked();if username.is_empty() {return;}let args = serde_wasm_bindgen::to_value(&InsertArgs { username: &username, email:&email }).unwrap();   //参数序列化// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/let new_msg = invoke("insert_db_item", args).await.as_string().unwrap_or(String::from("Insert Data Failed!!!"));     //使用invoke调用insert_db_item命令,greet类似于APIset_insert_msg.set(new_msg);});     };let del_last_item = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let new_msg = invoke_without_args("del_last_user").await.as_string().unwrap_or(String::from("Delete Data Failed!!!")); set_insert_msg.set(new_msg);});};  let clear = move |_| {set_value.set(0);};let decrement = move |_| {set_value.update(|v| *v -=1);};       //update()函数接受闭包,参数是signal的setter值set_valuelet increment = move |_| {set_value.update(|v| *v +=1);};view! {                                              //view!宏作为App()函数的返回值返回IntoView类型<main class="container"><h1>"Welcome to Tauri(2.1.1) + Leptos(0.7.2)"</h1><div class="row"><a href="https://tauri.app" target="_blank"><img src="public/tauri.svg" class="logo tauri" alt="Tauri logo"/></a><a href="https://docs.rs/leptos/" target="_blank"><img src="public/leptos.svg" class="logo leptos" alt="Leptos logo"/></a><a href="https://scybbd.com" target="_blank"><img src="public/doughnutS.svg" class="logo scybbd" alt="YislWll's website"/></a></div><p>"点击Tauri、Leptos和Scybbd的logo了解更多..."</p><form class="row" id="greet-form" on:submit=greet><inputid="greet-input"placeholder="请输入一个名字..."on:input=update_name/><button type="submit" id="greet-button">"打招呼"</button></form><p>{ move || greet_msg.get() }</p><div><button style="margin:0 10px 0 10px;" on:click = clear>"清零"</button><button style="margin:0 10px 0 10px;" on:click = decrement>"-1"</button><span style="margin:0 10px 0 10px;" class:red=move||{value.get()<0}>"数值:"{value}</span>     //当值小于0时,字体变成红色<button style="margin:0 10px 0 10px;" on:click = increment>"+1"</button></div><p></p><form class="row" id="insert-form" on:submit=insert_db_item><inputid="user-input"placeholder="请输入姓名..."on:input=update_userstyle="margin:0 10px 0 10px;" /><inputid="email-input"placeholder="请输入邮箱(可选)..."on:input=update_emailstyle="margin:0 10px 0 10px;" /><button type="submit" id="insert-button"  style="margin:0 10px 0 10px;" >"插入数据库"</button></form><p class="info">数据库变动信息:{ move || insert_msg.get() }</p> <div class="form-container"><div class="db-window" id="db-item"><p></p></div><div class="btn-window"><form class="row" on:submit=get_db_item><button type="submit" style="margin:10px 5px 10px 5px;" id="get-button" style="margin:0 10px 0 10px;" >"读取数据库"</button></form><form class="row" on:submit=del_last_item><button type="submit" style="margin:10px 5px 10px 5px;" id="del-button" style="margin:0 10px 0 10px;" >"删除最后项"</button></form></div></div></main>}
}

Leptos 0.7版与0.6版有所区别,create_signal换成了signal。

至此,Tuari+Leptos应用程序通过sqlx对数据库进行操作的简单功能基本完成。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每日一些题

题解开始之前&#xff0c;给大家安利一个上班偷偷学习的好搭档&#xff0c;idea中的插件有一个叫 LeetCode with labuladong&#xff0c;可以在idea中直接刷力扣的题目。 朋友们上班没事的时候&#xff0c;可以偷偷摸几题。看八股的话&#xff0c;可以用面试鸭&#xff0c;也是…

Docker--Docker Container(容器) 之 操作实例

容器的基本操作 容器的操作步骤其实很简单&#xff0c;根据拉取的镜像&#xff0c;进行启动&#xff0c;后可以查看容器&#xff0c;不用时停止容器&#xff0c;删除容器。 下面简单演示操作步骤 1.创建并运行容器 例如&#xff0c;创建一个名为"my-nginx"的交互…

高频 SQL 50 题(基础版)_1068. 产品销售分析 I

销售表 Sales&#xff1a; (sale_id, year) 是销售表 Sales 的主键&#xff08;具有唯一值的列的组合&#xff09;。 product_id 是关联到产品表 Product 的外键&#xff08;reference 列&#xff09;。 该表的每一行显示 product_id 在某一年的销售情况。 注意: price 表示每…

linux进阶

目录 变量 shell变量 环境变量 预定义变量 位置变量 其他 管道与重定向 管道 重定向 shell脚本 分支结构 循环结构 数组 脚本实例 变量 shell变量 shell变量&#xff1a;shell程序在内存中存储数据的容器 shell变量的设置&#xff1a;colorred 将命令的结果赋值…

“TypeScript版:数据结构与算法-初识算法“

引言 在算法与编程的广阔世界里&#xff0c;总有一些作品以其独特的魅力和卓越的设计脱颖而出&#xff0c;成为我们学习和研究的典范。今天&#xff0c;我非常荣幸地向大家分享一个令人印象深刻的算法——Hello算法。 Hello算法不仅展现了作者深厚的编程功底&#xff0c;更以…

【复盘】2024年终总结

工作 重构风控系统 今年上半年其实就是整体重构系统&#xff0c;经历了多次加班的&#xff0c;其中的辛酸苦辣只有自己知道&#xff0c;现在来看的话&#xff0c;其实对自己还有一定的成长&#xff0c;从这件事情上也明白 绩效能不能拿到A&#xff0c;在分配的任务的时候就决…

RedisDesktopManager新版本不再支持SSH连接远程redis后

背景 RedisDesktopManager(又名RDM)是一个用于Windows、Linux和MacOS的快速开源Redis数据库管理应用程序。这几天从新下载RedisDesktopManager最新版本&#xff0c;结果发现新版本开始不支持SSH连接远程redis了。 解决方案 第一种 根据网上有效的信息&#xff0c;可以回退版…

[卫星遥感] 解密卫星目标跟踪:挑战与突破的深度剖析

目录 [卫星遥感] 解密卫星目标跟踪&#xff1a;挑战与突破的深度剖析 1. 卫星目标跟踪的核心挑战 1.1 目标的高速与不确定性 1.2 卫星传感器的局限性 1.3 数据处理与融合问题 1.4 大尺度与实时性要求 2. 当前卫星目标跟踪的主流技术 2.1 卡尔曼滤波&#xff08;Kalman …

OpenCV-Python实战(9)——滤波降噪

一、均值滤波器 cv2.blur() img cv2.blur(src*,ksize*,anchor*,borderType*)img&#xff1a;目标图像。 src&#xff1a;原始图像。 ksize&#xff1a;滤波核大小&#xff0c;&#xff08;width&#xff0c;height&#xff09;。 anchor&#xff1a;滤波核锚点&#xff0c…

【查询函数】.NET开源ORM框架 SqlSugar 系列

目录 一、基本用法 &#x1f48e; 二、C#函数 &#x1f50e; 三、逻辑函数 &#x1f3a1; 3.1 case when 3.2 IsNulll 四、时间函数 &#x1f570;️ 4.1 是否是同一天 4.2 是否是同一月 4.3 是否是同一年 4.4 是否是同一时间 4.5 在当前时间加一定时间 4.6 在当前…

二、github基础

Github基础 备用github.com网站一、用户界面-Overview&#xff08;概览&#xff09;1用户信息2 导航栏3 热门仓库4 贡献设置5贡献活动6搜索和筛选7自定义收藏8贡献统计9最近活动10其他链接 二、用户界面-Repositories&#xff08;仓库&#xff09;1 libusb_stm322 savedata3 Fi…

Elasticsearch VS Easysearch 性能测试

压测环境 虚拟机配置 使用阿里云上规格&#xff1a;ecs.u1-c1m4.4xlarge&#xff0c;PL2: 单盘 IOPS 性能上限 10 万 (适用的云盘容量范围&#xff1a;461GiB - 64TiB) vCPU内存 (GiB)磁盘(GB)带宽&#xff08;Gbit/s&#xff09;数量1664500500024 Easysearch 配置 7 节点…

Echarts+vue电商平台数据可视化——webSocket改造项目

websocket的基本使用&#xff0c;用于测试前端能否正常获取到后台数据 后台代码编写&#xff1a; const path require("path"); const fileUtils require("../utils/file_utils"); const WebSocket require("ws"); // 创建WebSocket服务端的…

jenkins修改端口以及开机自启

修改Jenkins端口 方式一&#xff1a;通过配置文件修改&#xff08;以CentOS为例&#xff09; 找到配置文件&#xff1a;在CentOS系统中&#xff0c;通常可以在/etc/sysconfig/jenkins文件中修改Jenkins的配置。如果没有这个文件&#xff0c;也可以查看/etc/default/jenkins&…

《Vue3实战教程》34:Vue3状态管理

如果您有疑问&#xff0c;请观看视频教程《Vue3实战教程》 状态管理​ 什么是状态管理&#xff1f;​ 理论上来说&#xff0c;每一个 Vue 组件实例都已经在“管理”它自己的响应式状态了。我们以一个简单的计数器组件为例&#xff1a; vue <script setup> import { r…

简单使用linux

1.1 Linux的组成 Linux 内核&#xff1a;内核是系统的核心&#xff0c;是运行程序和管理 像磁盘和打印机等硬件设备的核心程序。 文件系统 : 文件存放在磁盘等存储设备上的组织方法。 Linux 能支持多种目前浒的文件系统&#xff0c;如 ext4 、 FAT 、 VFAT 、 ISO9660 、 NF…

微服务のGeteWay

目录 概念&#xff1a; 三大核心&#xff1a; 工作流程&#xff1a; 9527网关如何做路由映射&#xff1a; GetWay高级特性&#xff1a; 按服务名动态路由服务&#xff1a; 断言Route Predicate Factories &#xff1a; 获取当前时区时间&#xff1a; After Route &…

idea 的 springboot项目spring-boot-devtools 自动编译 配置热部署

1&#xff0c;设置一 2&#xff0c;设置二 设置二&#xff08;旧版本&#xff09; CtrlShiftAlt/ 点击弹出框中Registry... 引入&#xff08;如果报错&#xff0c;换不同的版本&#xff09; <dependency><groupId>org.springframework.boot</groupId><a…

GitHub CLI 安装指南

GitHub CLI 是 GitHub 官方提供的命令行工具&#xff0c;可以帮助开发者方便地与 GitHub 平台进行交互&#xff0c;例如克隆仓库、提交代码、创建 Pull Request 等。 相比传统的 HTTPS 下载和操作&#xff0c;GitHub CLI 提供了以下显著的优势和特殊功能&#xff1a; GitHub …

建立一个Macos载入image的实例含界面

前言 为了方便ios程序的开发&#xff0c;有时候需要先用的Macos平台进行一些功能性的程序开发。 作为对比和参考。 1、创建一个MacOS的App 2、主界面控件的增加 添加的控件方法与ios相同&#xff0c;也是再用commandshiftL&#xff08;CtrlShiftL&#xff09;,就会弹出控件…