基于SeaORM+MySQL+Tauri2+Vite+React等的CRUD交互项目

目的

1、学习Tauri2

2、学习SeaORM

3、完成前后端交互

4、本地打包,Github工作流打包

想法来源

看到这篇文章

Tauri2+Leptos开发桌面应用--Sqlite数据库操作_tauri sqlite-CSDN博客https://blog.csdn.net/weixin_44274609/article/details/144796615这位大佬写的很好,笔者才发现原来还可以连接数据库,既然如此,写一写前后端。

但是笔者学了一下sqlx,感觉像是JDBC,或者PyMySQL。sqlx不像是ORM框架,还要写sql语句,感觉有点麻烦。

因此,笔者搜了搜,选择SeaORM,虽然没学过,无所谓。

SeaORM的官网如下。

Index | SeaORM 🐚 An async & dynamic ORM for Rusthttps://www.sea-ql.org/SeaORM/docs/index/其他参考

【从零开始的rust web开发之路 三】orm框架sea-orm入门使用教程-CSDN博客https://blog.csdn.net/qq_35270805/article/details/135946438Rust语言从入门到精通系列 - SeaORM框架实践(基础篇)SeaORM是一个基于Rust语言的ORM(对象关系映射 - 掘金https://juejin.cn/post/7239502215889879077

正文

建立Tauri2项目

复制代码
pnpm create tauri-app

结果如下

进入项目,修改package.json文件中的script内容

javascript 复制代码
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "tauri:build": "tauri build",
    "tauri:dev": "tauri dev",
    "android": "tauri android dev"
  }

启动命令

复制代码
pnpm run tauri

经过一段时间的编译,结果如下

修改前端项目------配置路由

安装react-router

rust 复制代码
pnpm add react-router-dom

路由如下

rust 复制代码
import Root from "../pages/Root.jsx";
import Home from "../pages/Home.jsx";
import Book from "../pages/Book.jsx";

const routes=[
    {
        path:'/',
        element:<Root/>,
        children:[
            {
                index:true,
                element:<Home/>
            },
            {
                path:'book',
                element:<Book/>
            }
        ]
    }
]

export default routes;

组件的内容笔者使用Curor生成。

创建表

不搞那么复杂,就创建一张表book。sql语句如下。

sql 复制代码
-- 创建书
CREATE TABLE books (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(100) NOT NULL,
    author VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

尝试单独使用SeaORM

使用RustRover或者Cargo new创建一个测试项目。

配置依赖

在Cargo.toml文件中,引入依赖

sql 复制代码
[package]
name = "rust-sql" # 项目名称
version = "0.1.0"  # 版本号
edition = "2024"  # Rust 2024

[dependencies] # 依赖项
# sea-orm是一个Rust的ORM库,支持多种数据库,features参数指定了使用的mysql数据库和tokio的runtime
sea-orm={version = "1.1.7",features = ["runtime-tokio-rustls","sqlx-mysql","rust_decimal"]}
# dotenv是一个Rust的库,用于读取.env文件
dotenv = "0.15.0"
# rust_decimal是一个Rust的库,用于处理十进制数
rust_decimal = "1.36.0"
# tokio是一个Rust的异步运行时库
tokio = {version = "1.44.1",features = ["full"]}

在Rust里面,crate 是指包或库,features中选择使用那些**crate,**没有features,则使用默认

在配置sea-orm时,使用了runtime-tokio-rustls、sqlx-mysql、rust_decimal三个crate。

安装sea-orm-cli

sql 复制代码
cargo install sea-orm-cli

sea-orm-cli是个命令行工具,就像Django的命令一样。

进行数据迁移

命令如下

sql 复制代码
sea-orm-cli generate entity -u mysql://root:[email protected]:3306/store -o src/entity

这个命令实现sql表转换为Rust的实体,就相当Django的model。

在src/entity目录下就会生成实体代码。当然,也可以自己写。

查看Rust实体代码

目录文件如下

数据库里面有多张表,关注book.rs

mod.rs相当于Python的__init__.py文件。

进入book.rs中

sql 复制代码
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.7

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "books")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub title: String,
    pub author: String,
    #[sea_orm(column_type = "Decimal(Some((10, 2)))")]
    pub price: Decimal,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

use------简单来说,导包,类似于import

#[derive()] 是属性宏,简单来说,增加新的属性。Clone------能否复制的属性,Debug能够调试的属性,其他属性类似,可以问问AI

pub------public
impl ActiveModelBehavior for ActiveModel {}------ActiveModelBehavior 是一个trait,而triat是可以共享的,简单的说,ActiveModel是结构体,实现了ActiveModelBehavior,很明显,通过{}可以猜测 ActiveModelBehavior 中定义了默认方法,直接使用默认方法。

写一个方法------获得所有的书

rust 复制代码
use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, DbErr, EntityTrait,ActiveValue};
use dotenv::dotenv;
use std::env;
use rust_decimal::Decimal;

pub mod entity;


use entity::books::Entity as BooksDao;
use entity::books::Model as BooksModel;
use entity::books::ActiveModel as BooksActiveModel;

#[tokio::main]
async fn main() {
    // 加载环境变量
    dotenv().ok();

    // 从环境变量中获取数据库连接字符串
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");

    // 连接数据库
    let db = Database::connect(database_url).await.unwrap();

    // 调用 get_books 函数获取书籍数据
    match get_books(&db).await {
        Ok(books) => {
            println!("Books: {:?}", books);
        }
        Err(err) => {
            eprintln!("Error fetching books: {:?}", err);
        }
    }

}

// 异步函数,用于获取所有书籍
async fn get_books(db: &DatabaseConnection) -> Result<Vec<BooksModel>, DbErr> {
    BooksDao::find().all(db).await
}

在根目录下新建一个.env文件。里面写连接数据库的URL

&DatabaseConnection:对DatabaseConnection的不可变引用

Result<Vec<BooksModel>, DbErr>:成功Vec<BooksModel>,失败返回DbErr

运行,结果如下。

SeaORM和Tauri2联合使用

其实笔者刚开始不知道怎么使用,但是看到github上的大佬写的模板。

jthinking/tauri-seaorm-template: Tauri + SeaORM + SQLite template, ORM and Migration support.https://github.com/jthinking/tauri-seaorm-template再问一问AI,懂了

总体使用流程

1、实体模型在一个项目,比如entity项目

2、需要使用的方法,在另一个项目中service中,

3、entity和service作为tauri项目的依赖。

entity项目

每个Cargo项目都有Cargo.toml文件,

src/lib.rs:整个库的入口点,可以定义哪些模块、函数、结构体等是公开的。

因此,内容如下

rust 复制代码
pub mod books;

entity的Cargo.toml的内容如下。

rust 复制代码
[package]
name = "entity"
version = "0.1.0"
edition = "2024"
publish = false

[lib]
name = "entity"
path = "src/lib.rs"

[dependencies]
# serde 是一个Rust的序列化和反序列化库
serde = { version = "1", features = ["derive"] }

sea-orm = "1.1.7"

需要book实现序列化和反序列化。

需要在属性宏上加上Serialize、Deserialize。即

实现序列化这一点非常关键。

service项目与展示逻辑的实现

主要业务的逻辑代码,暂时先写获得所有书的逻辑

其中Cargo.toml文件的内容如下

rust 复制代码
[package]
name = "service"
version = "0.1.0"
edition = "2024"

[dependencies]
# 导入entity模块
entity = { path = "../entity" }

[dependencies.sea-orm]
version = "1.1.7" #
features = [
    "sqlx-mysql",
    "runtime-tokio-native-tls",
]
[dev-dependencies]
tokio = { version = "1.44.1", features = ["full"] }

在service/src/book_query.rc的内容,与获得所有书的逻辑相似

rust 复制代码
use entity::books::Model as BookModel;
use entity::books::Entity as BookDao;
use sea_orm::{DatabaseConnection, EntityTrait,DbErr};
// 声明一个结构体
pub struct BookQuery; 

// 为结构体实现方法
impl BookQuery{
    pub async fn  get_list(db:&DatabaseConnection)->Result<Vec<BookModel>,DbErr>{
        BookDao::find().all(db).await
    }
}

tauri项目------启动数据库,准备通信

Cargo.toml文件的内容

rust 复制代码
[package]
name = "info"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2024"

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]

tokio = {version = "1.44.1", features = ["full"] }
tauri = { version = "2", features = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri-plugin-opener = "2"
service= { path = "./service" } # 导入service模块
entity= { path ="./entity" }  # 导入entity模块


[workspace]
# 项目的成员
members = [".", "service", "entity"]  

整体项目结构

经过前面这么多布局,可以写最关键的启动文件了

rust 复制代码
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::State;
use entity::books::Model as BooksModel;
use service::BookQuery;
use dotenv::dotenv;
use std::env;


use service::{
    sea_orm::{DatabaseConnection,Database}
};

// 定义一个异步函数,用于获取书籍数据
#[tauri::command]
async fn get_books(state: State<'_,AppState>)->Result<Vec<BooksModel>,String>{
    BookQuery::get_list(&state.connect)
        .await
        .map_err(|e| e.to_string())
}


// 定义一个结构体,用于存储应用程序的状态
#[derive(Clone)]
struct AppState{
    connect:DatabaseConnection,
}


// 启动程序
#[tokio::main]
async fn main() {
    dotenv().ok();
    let database_url = env::var("DATABASE_URL").expect("需要设置DATABASE_URL ");
    // 连接数据库
    let connect=Database::connect(database_url)
        .await
        .expect("连接失败");
    let state=AppState{
        connect
    };
    tauri::Builder::default()
        .manage(state)// 全局状态管理
        .invoke_handler(tauri::generate_handler![
            get_books
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
    
}

这里有个细节。

来源与deepseek的回答

Result<T, E> 的序列化要求

  • T:成功时的返回值类型,必须实现 Serialize

  • E:错误时的返回值类型,必须实现 Serializestd::fmt::Debug

需要实现序列化,前面在entity中实现了。

前端发送信号------get_books

关键代码

rust 复制代码
import {useEffect, useState} from 'react';
import {invoke} from '@tauri-apps/api/core'


 const [books, setBooks] = useState([])
 function get_books(){
   invoke('get_books').then((books) => {
      setBooks(books);
    });
  }
  useEffect(() => {
   get_books()
  }, []);

invoke作为交互的关键函数,第一个参数是拥有**#[tauri::command]**的需要使用函数名,异步。

运行

rust 复制代码
pnpm run tauri

结果如下

项目终于建立完成,成功。哈哈哈哈哈。

数据和页面都是AI生成的。

本来想把CRUD全部写出来的,都是重复操作,懒得写。

本地打包

完成CURD之后。

打包后,双击没有运行,后来发现是没有.env文件。

修改rust的代码,或者把.env文件放到安装目录下。可以运行

结果如下

github工作流打包

.github/workflows/build.yaml

复制代码
name: 打包tauri
on:
  push:
    tags:
      - "v*.*.*"

jobs:
  build:
    runs-on: windows-latest
    steps:
      - name: 读取仓库
        uses: actions/checkout@v4
      - name: 设置node
        uses: actions/setup-node@v4
        with:
              node-version: '23'
      - name: 安装pnpm
        run: npm i -g pnpm
      - name: 安装依赖
        run: pnpm install

      - name: 安装 Rust
        uses: dtolnay/rust-toolchain@stable

      - name: 缓存 Rust
        uses: swatinem/rust-cache@v2
        with:
          workspaces: './src-tauri -> target'

      - name: 打包项目
        uses: tauri-apps/[email protected]
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags.
          releaseName: 'TSR v__VERSION__' # tauri-action replaces \_\_VERSION\_\_ with the app version.
          releaseBody: 'See the assets to download and install this version'
          releaseDraft: false
          prerelease: false
          publish: true

地址

qe-present/tauri-seaorm-reacthttps://github.com/qe-present/tauri-seaorm-react

相关推荐
Captaincc5 分钟前
这款堪称编程界的“自动驾驶”利器,集开发、调试、提 PR、联调、部署于一体
前端·ai 编程
我是小七呦15 分钟前
万字血书!TypeScript 完全指南
前端·typescript
simple丶18 分钟前
Webpack 基础配置与懒加载
前端·架构
simple丶23 分钟前
领域模型 模板引擎 dashboard应用列表及配置接口实现
前端·架构
冰夏之夜影24 分钟前
【css酷炫效果】纯css实现液体按钮效果
前端·css·tensorflow
25 分钟前
告别手写Codable!Swift宏库ZCMacro让序列化更轻松
前端
摘笑1 小时前
vite 机制
前端
Channing Lewis1 小时前
API 返回的PDF是一串字符,如何转换为PDF文档
前端·python·pdf
海盗强1 小时前
css3有哪些新属性
前端·css·css3
Cutey9161 小时前
前端如何实现菜单的权限控制(RBAC)
前端·javascript·设计模式