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

发布于:2025-03-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

目的

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内容

  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "tauri:build": "tauri build",
    "tauri:dev": "tauri dev",
    "android": "tauri android dev"
  }

启动命令

pnpm run tauri

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

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

安装react-router

pnpm add react-router-dom

 路由如下

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语句如下。

-- 创建书
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文件中,引入依赖

[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

cargo install sea-orm-cli

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

进行数据迁移

命令如下

sea-orm-cli generate entity -u mysql://root:123456@127.0.0.1:3306/store -o src/entity

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

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

查看Rust实体代码

目录文件如下

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

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

进入book.rs中

//! `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 中定义了默认方法,直接使用默认方法。

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

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:整个库的入口点,可以定义哪些模块、函数、结构体等是公开的。

因此,内容如下

pub mod books;

entity的Cargo.toml的内容如下。

[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文件的内容如下

[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的内容,与获得所有书的逻辑相似

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文件的内容

[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"]  

 整体项目结构

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

// 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:错误时的返回值类型,必须实现 Serialize 和 std::fmt::Debug

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

前端发送信号——get_books

关键代码

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]的需要使用函数名,异步。

运行

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/tauri-action@v0.5.19
        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