Rust axum使用 sqlx 的一个简单web服务

2,537 阅读9分钟

起初在弄数据库时总是有问题,因为mysql5.7和8,后来发现大佬的文章,参照写完成了整个流程,大佬的没有贴源码,有几个地没跑通。经过尝试改进也算跑过了,在这只做一个代码的记录方便后期使用。

大佬的文章写的很详细,有需要可以直接稳步:Rust axum

PS:除了贴上代码,最后也会把Github代码贴出来

工作目录及使用的crate库
[workspace]
members = [
    "bin",
    "db",
    "common",
    "routers",
    "middleware",
    "config",
]


[workspace.dependencies]
# axum = "0.6.7"
axum = { version = "0.6.7", features = ["headers"]}
tokio = { version = "1.25.0", features = ["full"] }
sqlx = { version = "0.6.2", features = [ "mysql", "runtime-tokio-rustls", "chrono"] }  # mysql8
# sqlx = { version = "0.6.2", features = [ "mysql", "runtime-tokio-native-tls", "chrono"] } 5.7
dotenv = "0.15.0"
once_cell = "1.17.1"
serde_json = "1.0.93"
serde = { version = "1.0.152", features = ["derive"] }
log = "0.4.17"
log4rs = "1.2.0"
jsonwebtoken = "8.2.0"
# env_logger = "0.10.0"
bcrypt = "0.14.0"
chrono = {version = "0.4.24", features = ["serde"]}
  • Tokio异步运行时,rust目前最流行,最有名气的异步库。
  • axun,Tokio系旗下的web框架,这次就是要体验一下Tokio全家桶
  • sqlx 异步数据库框架,并不是orm框架那种,没有DSL,用户自己编写sql语句,将查询结果按列取出或映射到struct上
  • once_cell初始化全局变量库
  • serde、serde_json序列化专用库
  • log、log4rs,日志库 update
  • dotenv环境变量库
  • jsonwebtoken jwt认证库
  • bcrypt 密码加密 解密库 add
  • chrono 时间

取现成的linken_coding

工作目录文件夹:

bin
common
config
db
middlewre
routers
.env
Cargo.toml

项目工程化

Rust笔记,如果使用Rust workspace创建更大的工程 ### Rust 如何在大工程中模块引入 有介绍关于工程中的使用非单个 src

项目完成内容介绍

  1. axum
  2. mysql
  3. 日志
  4. 中间件 鉴权
  5. 密码加密

项目代码

bin
rust_web/rust_axum/bin/src/main.rs
use axum;
use common::{log};
use db::mysql;
use routers::init::routers;
use std::{net::SocketAddr, str::FromStr};

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    //连接数据库
    mysql::init_db_pool().await?;
    // 日志系统
    log::start_logs();
    //路由
    let app = routers();
    let addr = SocketAddr::from_str("127.0.0.1:5050").unwrap();
    // axum::Server::bind(&"0.0.0.0:5000".parse().unwrap()) 与上相同
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
    Ok(())
}


rust_web/rust_axum/bin/Cargo.toml
[package]
name = "bin"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { workspace = true }
tokio = { workspace = true }
sqlx = { workspace = true }

db = { path = "../db" }
routers = { path = "../routers" }
common = { path = "../common" }

申明: 文中都有详细解释linken_coding 我的文章只是把整个流程跑通了,只做记录用,方便后期使用

db
rust_web/rust_axum/db/src/lib.rs
pub mod mysql;

rust_web/rust_axum/db/src/mysql.rs
use dotenv::dotenv;
use once_cell::sync::OnceCell;
use sqlx::{mysql::MySqlPoolOptions, Error, MySqlPool};
use std::{env, process};

static MYSQL_POOL: OnceCell<MySqlPool> = OnceCell::new();

//建立mysql连接
pub async fn init_db_pool() -> Result<(), Error> {
    //在取env时需要使用dotenv  要不取的是系统的
    dotenv().ok();
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let pool = match MySqlPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
    {
        Ok(pool) => {
            println!("✅Connection to the database is successful!");
            pool
        }
        Err(err) => {
            println!("🔥 Failed to connect to the database: {:?}", err);
            process::exit(1);
        }
    };
    assert!(MYSQL_POOL.set(pool).is_ok());
    Ok(())
}

//获取数据库
pub fn get_pool() -> Option<&'static MySqlPool> {
    MYSQL_POOL.get()
}
// 这块需要注意
如果本地是mysql 有85.7 对应选择使用。
sqlx = { version = "0.6.2", features = [ "mysql", "runtime-tokio-rustls", "chrono"] }  # mysql8
# sqlx = { version = "0.6.2", features = [ "mysql", "runtime-tokio-native-tls", "chrono"] } 5.7
以上都试了可以,本来是5.7有说能同时装两个结果崩了,  崩了,还好没什么数据。后来直接按了个8
所以两种都用过
rust_web/rust_axum/db/Cargo.toml
[package]
name = "db"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { workspace = true }
tokio = { workspace = true }
dotenv = { workspace = true }
sqlx = { workspace = true }
once_cell = { workspace = true }

rust_web/rust_axum/.env
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=
DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@localhost:3307/${MYSQL_DATABASE}
#mysql 的配置文件,对应都在这里了

接下来是路由 路由的controllers与servers以及struct 整合到一起了

routers
rust_web/rust_axum/routers/Cargo.toml
[package]
name = "routers"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }
sqlx = { workspace = true }
jsonwebtoken = { workspace = true }
chrono = { workspace = true }

middleware = { path = "../middleware" }
common = { path = "../common" }
db = { path = "../db" }

rust_web/rust_axum/routers/src/lib.rs
pub mod auth;
//pub mod todo;
//pub mod pay;
....
pub mod init;
rust_web/rust_axum/routers/src/init.rs

use axum::{middleware::from_extractor, routing::MethodRouter, Router};
// api
use super::auth;
use middleware::auth::Claims;
//构建路由公共方法
pub fn handle_router(path: &str, method_router: MethodRouter) -> Router {
    // let _path = format!("/api{}", path); // 统一api 路径
    // let mut _path = String::from("/api");
    let mut _path = "/api".to_string();
    _path.push_str(path);
    Router::new().route(&_path, method_router)
}

//api 路由入口
pub fn routers() -> Router {
    auth_init_router().merge(init_router())
}

//需要权限认证的路由
fn auth_init_router() -> Router {
    let app = Router::new()
        .merge(auth::get_user_list())
        .layer(from_extractor::<Claims>());
    return app;
}

//不需要权限认证的路由
fn init_router() -> Router {
    let app = Router::new()
        .merge(auth::login()) //登录
        .merge(auth::register()); //注册
    return app;
}
rust_web/rust_axum/routers/src/auth 文件夹
todo pay 文件夹内目录结构与auth一样
rust_web/rust_axum/routers/src/auth/mod.rs
mod api;
mod dto;
mod handler;
mod servers;
pub use api::{get_user_list, login, register};

rust_web/rust_axum/routers/src/auth/api.rs
use super::handler::{
    authorize,
    get_user_list as get_user_lists,
    login as user_login,
    // update_user as update_user_info,
};
use crate::init::handle_router;
use axum::{
    routing::{get, post},
    Router,
};

//注册
pub fn register() -> Router {
    //构建注册路由
    handle_router("/register", post(authorize))
}

//登录
pub fn login() -> Router {
    //构建登录路由
    handle_router("/login", post(user_login))
}

//查询用户信息列表
pub fn get_user_list() -> Router {
    //构建登录路由
    handle_router("/user-list", get(get_user_lists))
}

rust_web/rust_axum/routers/src/auth/dto.rs
use serde::{Deserialize, Serialize};
use sqlx::{Decode, Encode, FromRow, Type};

//token结构体
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthToken {
    pub access_token: String,
    pub token_type: String,
}

impl AuthToken {
    pub fn new(access_token: String) -> Self {
        Self {
            access_token,
            token_type: "Bearer".to_string(),
        }
    }
}

//注册请求体
#[derive(Debug, Clone, Deserialize, Serialize, Decode, Encode, Type, FromRow)]
pub struct AuthPayload {
    pub password: String,
    pub username: String,
    pub cred: String,
    pub email: String,
}

// 信息列表请求体
#[derive(Debug, Clone, Deserialize, Serialize, Decode, Encode, Type, FromRow)]
pub struct LianxXiPayload {
    pub name: String,
    pub age: i32,
    pub create_date: Option<DateTime<Utc>>,
    pub update_date: Option<DateTime<Utc>>,
}

//Token 生成
#[derive(Debug, Clone, Deserialize, Serialize, Decode, Encode, Type, FromRow)]
pub struct AuthPayToken {
    pub password: String,
    pub username: String,
    pub exp: Option<i32>,
}
//登录请求体
#[derive(Debug, Clone, Deserialize, Serialize, Decode, Encode, Type, FromRow)]
pub struct LoginPayload {
    pub username: String,
    pub password: String,
}

//登录响应体
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LoginResponse {
    pub username: String,
    pub access_token: String,
    pub token_type: String,
}

rust_web/rust_axum/routers/src/auth/handler.rs
use super::{
    dto::{AuthPayToken, AuthPayload, AuthToken, LianxXiPayload, LoginPayload, LoginResponse},
    servers,
};
use axum::{
    http::header::{HeaderMap, SET_COOKIE},
    response::IntoResponse,
    Json,
};
// use common::response::RespVO;
use common::{
    password,
    response::RespVO
};
use jsonwebtoken::{encode, Header};
use middleware::jwt::KEYS;
const COOKIE_NAME: &'static str = "MERGE_TOKEN";

//注册
pub async fn authorize(Json(payload): Json<AuthPayload>) -> impl IntoResponse {
    // 检查用户名
    if payload.username.is_empty() {
        return Json(RespVO::<AuthToken>::from_error("用户名不能为空!"));
    } else if payload.password.is_empty() {
        return Json(RespVO::<AuthToken>::from_error("密码不能为空!"));
    }
    // 查询用户是否注册过
    let search_result = servers::show(&payload.username).await;
    match search_result {
        Ok(res) => {
            // 查询用户存
            if payload.username == res.username {
                return Json(RespVO::<AuthToken>::from_result_tip("用户名已注册!"));
            };
        }
        Err(_err) => {
            // 查询用户不存在
        }
    }

    let result = servers::create(payload.clone()).await;
    match result {
        Ok(res) => {
            if res == 1 {
                Json(RespVO::<AuthToken>::from_result_tip("注册成功!"))
            } else {
                Json(RespVO::<AuthToken>::from_error("写入数据库失败!"))
            }
        }
        Err(err) => {
            let info = err.to_string();
            Json(RespVO::<AuthToken>::from_error(&info))
        }
    }
}

//创建token
fn init_token(payload: AuthPayload) -> String {
    let claims = AuthPayToken {
        username: payload.username.to_owned(),
        password: payload.password.to_owned(),
        exp: Some(2000000000),
    };
    //创建token, Create the authorization token
    let token = encode(&Header::default(), &claims, &KEYS.encoding)
        .map_err(|_| Json(RespVO::<AuthToken>::from_error("token创建失败!")))
        .unwrap();
    token
}
//登录
pub async fn login(Json(body): Json<LoginPayload>) -> (HeaderMap, Json<RespVO<LoginResponse>>) {
    let mut headers = HeaderMap::new();
    let result = servers::show(&body.username).await;
    match result {
        Ok(res) => {
            let is_valid = password::verify_password(&body.password,&res.password);
            // if body.password != res.password {
            if !is_valid {
                return (
                    headers,
                    Json(RespVO::<LoginResponse>::from_error("密码错误!")),
                );
            }
            let token = init_token(res.clone());
            // 3、把token写入cookie
            // response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");
            let cookie = format!("{}={};HTTPOnly", COOKIE_NAME, &token);
            headers.insert(SET_COOKIE, cookie.as_str().parse().unwrap()); // 设置Cookie
                                                                          // 4、token 返回给用户
            let arg = AuthToken::new(token);
            let params = LoginResponse {
                username: res.username,
                access_token: arg.access_token,
                token_type: arg.token_type,
            };
            (headers, Json(RespVO::<LoginResponse>::from_result(&params)))
        }
        Err(_err) => (
            headers,
            Json(RespVO::<LoginResponse>::from_error("用户名无效!")),
        ),
    }
}
//查询用户信息列表
pub async fn get_user_list() -> impl IntoResponse {
    let result = servers::list().await;
    match result {
        Ok(res) => Json(RespVO::<Vec<LianxXiPayload>>::from_result(&res)),
        Err(err) => {
            let info = err.to_string();
            Json(RespVO::<Vec<LianxXiPayload>>::from_error(&info))
        }
    }
}

rust_web/rust_axum/routers/src/auth/servers.rs
use super::dto::{AuthPayload, LianxXiPayload};
use db::mysql;
use sqlx::{self, Error};
use common::{
    password
};

pub async fn create(user: AuthPayload) -> Result<u64, Error> {
    let sql = "insert into user(email, username, cred, password) values (?, ?, ?, ?)";
    let pool = mysql::get_pool().unwrap();
    let pwd = password::hash_password(&user.password);
    let affect_rows = sqlx::query(sql)
        .bind(&user.email)
        .bind(&user.username)
        .bind(&user.cred)
        .bind(&pwd)
        // .bind()
        .execute(pool)
        .await?
        .rows_affected();
    Ok(affect_rows)
}

/**
 * 测试接口: 查 列表
 */
// pub async fn list() -> Result<Vec<AuthPayload>, Error> {
//     let pool = mysql::get_pool().unwrap();
//     let sql =
//         "select email, username, cred, password from user";
//     let list = sqlx::query_as::<_, AuthPayload>(sql)
//         .fetch_all(pool)
//         .await?;
//     Ok(list)
// }
/**
 * 测试接口: 查 列表
 */
pub async fn list() -> Result<Vec<LianxXiPayload>, Error> {
    let pool = mysql::get_pool().unwrap();
    let sql = "select name, age, create_date, update_date from lianxi_user";
    let list = sqlx::query_as::<_, LianxXiPayload>(sql)
        .fetch_all(pool)
        .await?;
    Ok(list)
}

/**
 * 测试接口: 查
 */
pub async fn show(username: &str) -> Result<AuthPayload, Error> {
    let sql = "select email, username, cred, password from user where username = ?";
    let pool = mysql::get_pool().unwrap();
    let res = sqlx::query_as::<_, AuthPayload>(sql)
        .bind(username)
        .fetch_one(pool)
        .await?;
    Ok(res)
}

接下来是中间件 鉴权

middleware
rust_web/rust_axum/middleware/Cargo.toml
[package]
name = "middleware"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { workspace = true }
jsonwebtoken = { workspace = true }
sqlx = { workspace = true }
once_cell = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }

common = { path = "../common" }

rust_web/rust_axum/middleware/src/lib.rs
pub mod auth;
pub mod jwt;

rust_web/rust_axum/middleware/src/jwt.rs
use jsonwebtoken::{DecodingKey, EncodingKey};
use once_cell::sync::Lazy;
use std::env;

///环境变量密钥,
pub static KEYS: Lazy<Keys> = Lazy::new(|| {
    let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
    // Keys::new(secret.as_bytes())
    Keys::new(secret.as_ref())
});

///认证错误类型
#[derive(Debug)]
pub enum AuthError {
    WrongCredentials,   //错误的凭据
    MissingCredentials, //丢失凭据
    TokenCreation,      //令牌创建
    InvalidToken,       //无效令牌
}
pub struct Keys {
    pub encoding: EncodingKey,
    pub decoding: DecodingKey,
}
impl Keys {
    fn new(secret: &[u8]) -> Self {
        Self {
            encoding: EncodingKey::from_secret(secret),
            decoding: DecodingKey::from_secret(secret),
        }
    }
}

rust_web/rust_axum/middleware/src/auth.rs
use super::jwt::KEYS;
use axum::{
    async_trait,
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
    Json,
};
use common::{cookie::get_cookie, response::RespVO};
use jsonwebtoken::{decode, Validation};
use serde::{Deserialize, Serialize};
use sqlx::{Decode, Encode, Type};

// An extractor that performs authorization.
#[derive(Debug, Clone, Serialize, Deserialize, Decode, Encode, Type)]
pub struct Claims {
    pub username: String,
    pub password: String,
    pub exp: Option<i32>,
}

#[async_trait]
impl<S> FromRequestParts<S> for Claims
where
    S: Send + Sync,
{
    type Rejection = Json<RespVO<String>>;
    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // let TypedHeader(Authorization(bearer)) = parts
        //     .extract::<TypedHeader<Authorization<Bearer>>>()
        //     .await
        //     .map_err(|_| {
        //         Json(RespVO::<String>::from_error_info(
        //             StatusCode::UNAUTHORIZED,
        //             "未认证",
        //         ))
        //     })?;

        // Decode the user data
        // let token = bearer.token();
        // let token_data = decode::<Claims>(&token.to_string(), &KEYS.decoding, &Validation::default())
        //     .map_err(|_| {
        //     Json(RespVO::<String>::from_error_info(
        //         StatusCode::UNAUTHORIZED,
        //         "token无效",
        //     ))
        // })?;

        // Ok(token_data.claims)
        // 方式二,自动获取token
        let token = get_cookie(&parts.headers, "MERGE_TOKEN");
        // println!("{:#?}",token);
        // println!("{:#?}",bearer.token());
        match token {
            Some(token) => {
                let token_data =
                    decode::<Claims>(&token.to_string(), &KEYS.decoding, &Validation::default())
                        .map_err(|_| {
                            Json(RespVO::<String>::from_error_info(
                                StatusCode::UNAUTHORIZED,
                                "token无效",
                            ))
                        })?;
                // println!("{:#?}",token_data);
                Ok(token_data.claims)
            }
            _ => Err(Json(RespVO::<String>::from_error_info(
                StatusCode::UNAUTHORIZED,
                "未认证",
            ))),
        }
    }
}
这块一是通过前台传token
二是在登录的时候  接口自己,然后中件间中自己取cookie
rust_web/rust_axum/.env
JWT_SECRET=12345
这块还有这个
大佬的日志,我这跑不通不知道是不是版本问题,后来我改了
所以config 中只有一个 日志的配置文件
rust_web/rust_axum/config/log4rs.yaml
refresh_rate: 30 seconds

appenders:
  stdout:
    kind: console
    encoder:
      pattern: "{d} {l} {M} :{m}{n}"
  requests:
    kind: rolling_file
    path: "logs/requests.log"
    encoder:
      kind: json
    policy:
      kind: compound
      trigger:
        kind: size
        limit: 10 mb
      roller:
        kind: fixed_window
        pattern: '{0}/requests.log.{{}}'
        base: 1
        count: 5
        pattern: "logs/rolling{}.log"
root:
  level: info
  appenders:
    # - stdout
    - requests


内容简单,可自行完善
最后 就是公共文件
rust_web/rust_axum/common/Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }
log = { workspace = true }
# env_logger = { workspace = true }
log4rs = { workspace = true }

bcrypt = { workspace = true }

rust_web/rust_axum/common/src/lib.rs

rust_web/rust_axum/common/src/cookie.rs
use axum::http::HeaderMap;

const COOKIE_NAME: &str = "MERGE_TOKEN";

pub fn get_cookie(headers: &HeaderMap, cookie_name: &str) -> Option<String> {
    let cookie = headers
        .get(axum::http::header::COOKIE)
        .and_then(|value| value.to_str().ok())
        .map(|value| value.to_string());
    match cookie {
        Some(cookie) => {
            let cookie = cookie.as_str();
            let cs: Vec<&str> = cookie.split(';').collect();
            for item in cs {
                let item: Vec<&str> = item.split('=').collect();
                if item.len() != 2 {
                    continue;
                }
                let key = item[0];
                let val = item[1];
                let key = key.trim();
                let val = val.trim();
                if key == cookie_name {
                    return Some(val.to_string());
                }
            }
            None
        }
        None => None,
    }
}
pub fn set_cookie(value: &str) -> HeaderMap {
    let c = format!("{}={}", COOKIE_NAME, value);
    let mut hm = HeaderMap::new();
    hm.insert(axum::http::header::SET_COOKIE, (&c).parse().unwrap());
    hm
}

rust_web/rust_axum/common/src/log.rs
use log::info;
use log4rs;
pub fn start_logs() {
    log4rs::init_file("./config/log4rs.yaml", Default::default()).unwrap();
    info!("booting up");
    // println!("test");
}

rust_web/rust_axum/common/src/password.rs
use bcrypt::{
    hash,
    verify,
    DEFAULT_COST
};


pub fn hash_password(password: &str) -> String {
    // 加密密码
    let hashed_password = hash(password, DEFAULT_COST).unwrap();
    hashed_password
}
pub fn verify_password(password: &str, hashed_password: &str) -> bool {
    let is_valid = verify(password, &hashed_password).unwrap();
    is_valid
}

rust_web/rust_axum/common/src/response.rs
use axum::http::StatusCode;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
//常量
pub const CODE_SUCCESS: StatusCode = StatusCode::OK;
pub const CODE_FAIL: StatusCode = StatusCode::BAD_REQUEST;

/// http接口返回模型结构,提供基础的 code,msg,data 等json数据结构
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RespVO<T> {
    pub code: Option<u16>,
    pub msg: Option<String>,
    pub data: Option<T>,
}

impl<T> RespVO<T>
where
    T: Serialize + DeserializeOwned + Clone,
{
    pub fn from_result(arg: &T) -> Self {
        Self {
            code: Some(CODE_SUCCESS.as_u16()),
            msg: Some("操作成功".to_string()),
            data: Some(arg.clone()),
        }
    }

    pub fn from_result_tip(arg: &str) -> Self {
        Self {
            code: Some(CODE_SUCCESS.as_u16()),
            msg: Some(arg.to_string()),
            data: None,
        }
    }

    pub fn from_error(arg: &str) -> Self {
        Self {
            code: Some(CODE_FAIL.as_u16()),
            msg: Some(arg.to_string()),
            data: None,
        }
    }

    pub fn from_error_info(code: StatusCode, info: &str) -> Self {
        Self {
            code: Some(code.as_u16()),
            msg: Some(info.to_string()),
            data: None,
        }
    }

    pub fn from_error_infos(info: &str) -> Self {
        Self {
            code: Some(CODE_FAIL.as_u16()),
            msg: Some(info.to_string()),
            data: None,
        }
    }
}

这样项目就简单跑通了 github 我本身是前端。非后端。github 里好像我的日志和密码功能没加。 我放在bitbucket,以上的代码是我本地加了日志和密码的。在上面github完善的。

好了后面有功能再完善吧。学习中。

补充github 也有前端的一个小demo

还有数据库也补一下

/*
 Navicat MySQL Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 80031
 Source Host           : localhost:3306
 Source Schema         : ljm_egg

 Target Server Type    : MySQL
 Target Server Version : 80031
 File Encoding         : 65001

 Date: 04/04/2023 08:37:11
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `email` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `cred` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

SET FOREIGN_KEY_CHECKS = 1;


/*
 Navicat MySQL Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 80031
 Source Host           : localhost:3306
 Source Schema         : ljm_egg

 Target Server Type    : MySQL
 Target Server Version : 80031
 File Encoding         : 65001

 Date: 04/04/2023 11:09:54
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for lianxi_user
-- ----------------------------
DROP TABLE IF EXISTS `lianxi_user`;
CREATE TABLE `lianxi_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `age` int DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

SET FOREIGN_KEY_CHECKS = 1;