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查看用户
添加用户
更新
搜索
删除