起初在弄数据库时总是有问题,因为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
项目完成内容介绍
- axum
- mysql
- 日志
- 中间件 鉴权
- 密码加密
项目代码
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 有8 和5.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(¶ms)))
}
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;