使用Rust实现简单的CURD 通过Actix-web & Sqlx 现实

在这里插入图片描述

Actix-web & Sqlx

使用Rust实现简单的CURD

依赖如下

Cargo.toml

[dependencies]
# 运行时
tokio = { version = "1", features = ["full"] }
# web框架
actix-web = "4"
actix-rt = "2"

# 数据库
sqlx ={ version = "0.6.0",default-features = false,features = ["mysql","runtime-tokio-rustls","macros","chrono"]}

#读取配置文件
dotenvy = "0.15.7"
# 随机库
#rand = "0.8.5"
# 缓存库
#cached = "0.22.0"

#序列化和反序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"

#日期支持
chrono = { version = "0.4", features = ["serde"] }

# 日志
log = "0.4"
env_logger="0.10"

最终的项目结构,项目比较简单,所以没有service

│  .env
│  .gitignore
│  Cargo.lock
│  Cargo.toml
└─src
    │  error.rs
    │  main.rs
    ├─controller
    │  │  mod.rs
    │  ├─index
    │  │      mod.rs
    │  └─user
    │          mod.rs
    └─pojo
        │  mod.rs
        └─user
                mod.rs

在这里插入图片描述

.env

DATABASE_URL=mysql://root:root@192.168.0.115:3306/test
IP_PORT=127.0.0.1:8080

main.rs

use actix_web::{
    web::{self, Data},
    App, HttpServer,
};
use sqlx::{mysql::MySqlPoolOptions, MySqlPool};
use std::env;
mod controller;
pub mod error;
mod pojo;
use controller::index;
use controller::user::user_route;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    //日志初始化
    env_logger::init();
    //开启读取配置
    dotenvy::dotenv().ok();
    // 数据库连接池
    let mysql_conn = mysql_conn().await;
    //读取启动端口
    let ip_port = env::var("IP_PORT").expect("Not configured in .env");
    
    HttpServer::new(move || {
        App::new()
            .app_data(Data::new(AppState {
                app_name: String::from("rust——learn"),
                database: mysql_conn.clone(),
            }))
            .service(index::hello)
            .service(web::scope("/user").configure(user_route))
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(ip_port)?
    .run()
    .await
}
async fn manual_hello(data: web::Data<AppState>) -> String {
    format!("hey :{:?}", data.app_name)
}
#[derive(Debug)]
struct AppState {
    app_name: String,
    database: MySqlPool,
}
pub async fn mysql_conn() -> MySqlPool {
    let database_url = env::var("DATABASE_URL").expect("Not configured in .env");
    MySqlPoolOptions::new()
        .max_connections(50)
        .connect(&database_url)
        .await
        .unwrap()
}

error.rs 自定义错误结构 这里不用实现这么多Trait的

use std::{fmt::Display, num::ParseIntError};

use actix_web::{HttpResponse, Responder, ResponseError};
use serde::Serialize;
use serde_json::json;
use sqlx::mysql::MySqlDatabaseError;

#[derive(Debug, Serialize)]
pub struct AppError {
    pub code: i32,
    pub message: Option<String>,
    pub data: Option<String>,
}
impl AppError {
    pub fn new(message: String) -> Self {
        AppError{
            code: -1,
            message: Some(message),
            data: None,
        }
    }
}

impl From<sqlx::Error> for AppError {
    fn from(value: sqlx::Error) -> Self {
        return AppError {
            code: -1,
            message: Some(value.to_string()),
            data: None,
        };
    }
}

impl From<&dyn std::error::Error> for AppError {
    fn from(arg: &dyn std::error::Error) -> Self {
        return AppError {
            code: -1,
            message: Some(arg.to_string()),
            data: None,
        };
    }
}
impl std::error::Error for AppError {}
impl Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", json!(self))
    }
}
impl Responder for AppError {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> actix_web::HttpResponse<Self::Body> {
        let body = serde_json::to_string(&self).unwrap();
        HttpResponse::Ok()
            .content_type("application/json")
            .body(body)
    }
}
impl ResponseError for AppError {}

impl From<MySqlDatabaseError> for AppError {
    fn from(err: MySqlDatabaseError) -> Self {
        AppError {
            code: -1,
            message: Some(format!("{}", err)),
            data: None,
        }
    }
}
impl From<ParseIntError> for AppError {
    fn from(value: ParseIntError) -> Self {
        AppError {
            code: -1,
            message: Some(format!("{}", value)),
            data: None,
        }
    }
}

pojo/mod.rs 基本返回结构

use actix_web::{HttpResponse, Responder};
use serde::Serialize;
pub mod user;


#[derive(Serialize)]
pub struct ResJson<T> {
    pub code: i32,
    pub message: String,
    pub data: Option<T>,
}
impl<T> ResJson<T> {
    pub fn success(data: Option<T>) -> ResJson<T> {
        ResJson {
            code: 0,
            message: "成功".to_string(),
            data,
        }
    }

    pub fn error(message: String) -> ResJson<T> {
        ResJson {
            code: -1,
            message,
            data: None,
        }
    }
}

impl<T: Serialize> Responder for ResJson<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse<Self::Body> {
        let body = serde_json::to_string(&self).unwrap();
        HttpResponse::Ok()
            .content_type("application/json")
            .body(body)
    }
}

正片开始

controller/user/mod.rs

use actix_web::{
    delete, get, post, put,
    web::{self, Form},
    HttpRequest,
};
use log::{debug, info};
use serde::Deserialize;
use sqlx::MySql;

use crate::{
    error::AppError,
    pojo::{user::User, ResJson},
    AppState,
};

pub fn user_route(cfg: &mut web::ServiceConfig) {
    cfg.service(list)
        .service(one)
        .service(create)
        .service(update)
        .service(search)
        .service(delete);
}

#[derive(Deserialize, Debug)]
struct ListParam {
    page_no: i32,
    page_size: i32,
    name: Option<String>,
}
/// 用户列表查询
/// GET
/// # Arguments
///
/// # Returns
/// Vec<User>
#[get("")]
pub async fn list(
    param: Form<ListParam>,
    data: web::Data<AppState>,
) -> Result<ResJson<Vec<User>>, AppError> {
    let list_param = param.into_inner();
    info!("{:?}", list_param);
    let name = list_param.name;
    let page_no: i32 = list_param.page_no - 1;
    let page_size: i32 = list_param.page_size;
    let rows = sqlx::query!(
        r#"select id,name,gender from user order by id desc limit ?,? "#,
        page_no,
        page_size
    )
    .fetch_all(&data.database)
    .await?;

    let users: Vec<_> = rows
        .into_iter()
        .filter(|r| {
            name.is_none()
                || r.name
                    .as_deref()
                    .unwrap_or_default()
                    .contains(&name.as_deref().unwrap_or_default())
        })
        .map(|row| User {
            id: row.id as u32,
            name: row.name.unwrap_or_default(),
            gender: row.gender.unwrap_or_default(),
        })
        .collect();
    Ok(ResJson::success(Some(users)))
}

/// 根据id查询用户
/// GET
/// # Arguments
/// * `id` - 用户的主键
/// #Returns
/// User
#[get("/{id}")]
pub async fn one(path: web::Path<u32>, data: web::Data<AppState>) -> ResJson<User> {
    let id = path.into_inner();
    info!("id:{}", id);
    match sqlx::query!(r#"select id,name,gender from user where id = ?"#, id)
        .fetch_one(&data.database)
        .await
    {
        Ok(row) => {
            let name = row.name.unwrap_or_else(|| String::from("default"));
            let gender = row.gender.unwrap_or_else(|| String::from("unknown"));
            let user = User::new(id, name, gender);
            ResJson::success(Some(user))
        }
        Err(e) => {
            debug!("database error:{}", e);
            ResJson::error(format!("无记录"))
        }
    }
}

#[derive(Debug, Deserialize)]
struct InsertUser {
    pub name: String,
    pub gender: String,
}
/// 创建用户
/// POST
/// #Arguments
/// * `form` - 包含用户信息的Form对象
/// #Returns
/// User
#[post("")]
pub async fn create(
    form: web::Form<InsertUser>,
    data: web::Data<AppState>,
) -> Result<ResJson<User>, AppError> {
    let user = form.0;
    info!("{:?}", user);
    let sqlx = sqlx::query::<MySql>(r#"insert into user(name,gender) values(?,?)"#)
        .bind(&user.name)
        .bind(&user.gender)
        .execute(&data.database)
        .await?
        .last_insert_id();
    Ok(ResJson::success(Some(User {
        id: sqlx as u32,
        name: user.name,
        gender: user.gender,
    })))
}
/// 更新用户
/// PUT
/// #Arguments
/// * `user` - 用户结构体
/// #Returns
/// User
#[put("")]
pub async fn update(
    web::Json(user): web::Json<User>,
    data: web::Data<AppState>,
) -> Result<ResJson<String>, AppError> {
    println!("{:?}", user);
    let rows_affected =
        sqlx::query::<MySql>(r#"update user set name  = ?,gender = ? where id = ?"#)
            .bind(&user.name)
            .bind(&user.gender)
            .bind(&user.id)
            .execute(&data.database)
            .await?
            .rows_affected();
    if rows_affected == 1 {
        Ok(ResJson::success(Some("用户修改成功".to_string())))
    } else {
        Err(AppError::new("修改失败".to_string()))
    }
}

#[derive(Deserialize)]
struct Info {
    pub name: String,
    pub gender: String,
}

///搜索用户
/// GET
/// #Arguments
/// * `info`` Info结构体,用于反序列化请求参数
/// #Returns
/// Vec<User>
#[get("/search/")]
pub async fn search(
    info: web::Query<Info>,
    req: HttpRequest,
    state: web::Data<AppState>,
) -> ResJson<Vec<User>> {
    info!("app_name:{}", state.app_name);
    info!("{}", req.query_string());
    info!("name:{}", info.name);
    info!("gender:{}", info.gender);
    let users = vec![
        User {
            id: 1,
            name: String::from("zs"),
            gender: "男".to_string(),
        },
        User {
            id: 3,
            name: String::from("ww"),
            gender: "男".to_string(),
        },
        User {
            id: 2,
            name: String::from("ls"),
            gender: "女".to_string(),
        },
        User {
            id: 4,
            name: String::from("小美"),
            gender: "女".to_string(),
        },
    ];
    info!("{}", info.name.is_empty());
    let res_user: Vec<User> = users
        .into_iter()
        .filter(|x| {
            (info.name.is_empty() || x.name.contains(&info.name))
                && (info.gender.is_empty() || x.gender == info.gender)
        })
        .collect();
    ResJson::success(Some(res_user))
}

///删除用户
/// DELETE
/// #Arguments
/// * `path` 
/// #Returns
/// String
#[delete("/{id}")]
pub async fn delete(
    path: web::Path<u32>,
    data: web::Data<AppState>,
) -> Result<ResJson<String>, AppError> {
    let id = path.into_inner();
    let rows_affected = sqlx::query!("delete from user where id = ?", id)
        .execute(&data.database)
        .await?
        .rows_affected();
    if rows_affected==1 {
        Ok(ResJson::success(Some("删除成功".to_string())))
    }else {
        Err(AppError::new("删除失败".to_string()))
    }
}

测试
列表
在这里插入图片描述
根据id查看用户
在这里插入图片描述
添加用户
在这里插入图片描述
更新
在这里插入图片描述
搜索
在这里插入图片描述
删除
在这里插入图片描述

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值