Skip to content

CRUD优化

搜索优化

👉接口分页

之前我们的搜索接口并没有进行分页的处理,现在我们就将用户的接口进行分页处理

先看看我们之前的用户接口

javascript
// 查找用户
pub async fn get_all_users(pool: web::Data<MySqlPool>) -> impl Responder {
    let users = sqlx::query_as::<_, User>("SELECT * FROM sys_user")
        .fetch_all(pool.get_ref())
        .await;

    match users {
        Ok(list) => HttpResponse::Ok().json(
        ApiResponse {
            code: 200,
            msg: "注册成功",
            data: Some(list),
        }),
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            HttpResponse::InternalServerError().body("数据库查询失败")
        }
    }
}

完善以后

🍎定义接口数据模型

javascript
// 分页数据模型
#[derive(serde::Deserialize)]
pub struct Pagination {
    pub page: Option<u32>,
    pub page_size: Option<u32>,
}

// 列表数据模型
#[derive(Serialize)]
pub struct ListResponse<T> {
    pub code: i32,
    pub msg: &'static str,
    pub data: Option<Vec<T>>,
    pub total: i64,
}

🍎分页查找

javascript
// 查找用户分页
pub async fn get_all_users(
    pool: web::Data<MySqlPool>,
    query: web::Query<Pagination>,
) -> impl Responder {

    let page = query.page.unwrap_or(1);
    let page_size = query.page_size.unwrap_or(10);
    let offset = (page - 1) * page_size;

    // 查询总数
    let total: (i64,) = match sqlx::query_as("SELECT COUNT(*) FROM sys_user")
        .fetch_one(pool.get_ref())
        .await
    {
        Ok(count) => count,
        Err(e) => {
            eprintln!("查询总数失败: {:?}", e);
            return HttpResponse::InternalServerError().body("查询总数失败");
        }
    };
    // 分页查询
    let users = sqlx::query_as::<_, User>("SELECT * FROM sys_user LIMIT ? OFFSET ?")
        .bind(page_size as i64)
        .bind(offset as i64)
        .fetch_all(pool.get_ref())
        .await;
    match users {
        Ok(list) => HttpResponse::Ok().json(
         ListResponse {
            code: 200,
            msg: "注册成功",
            data: Some(list),
            total: total.0,
        }),
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            HttpResponse::InternalServerError().body("数据库查询失败")
        }
    }
}

🍎参数重命名

这里前端给我们传递的数据我们看一下,我们进行一下适配这个接口的分页数据

javascript
前端给我们传回的数据参数为 

pageNum: 1
pageSize: 10 


目前我们自己参数为
page
pageSize

定义数据格式并重新命名

javascript
// 分页
#[derive(Serialize)]
pub struct Pagination {
    #[serde(rename = "pageNum")]
    pub page_num: Option<u32>,
    #[serde(rename = "pageSize")]
    pub page_size: Option<u32>,
}

我们的用户id也可以进行处理

javascript
user_id: 44

=> 

#[serde(rename = "userId")]
pub user_id: i32,

这个时候我们已经拿到下面这种数据了

userId: 44

这里报错,我们简单处理一下,需要注意前端传给我们的我们需要反序列化 ,一定要明确我们现在是后端

rust
// 分页数据模型 
#[derive(serde::Deserialize)]
pub struct Pagination {
    #[serde(rename = "pageNum")]
    pub page_num: Option<u32>,
    #[serde(rename = "pageSize")]
    pub page_size: Option<u32>,
}

查询信息,这个时候发现我们传入的参数已经都生效了

👉CRUD模块抽离

接下来我们抽离用户CRUD模块,封装成完善的CRUD模块

我们抽离的接口相关的方法都放入

javascript
src\common\apimethods.rs

接口抽离 接下来我们写一个最简单的rust通用查询接口,不带分页的查询,然后使用

🍎src/common/apimethods.rs

简单编写搜索公共接口

javascript
use sqlx::{Pool, MySql, Error, Row,MySqlPool,};
use serde::Serialize;
use std::collections::HashMap;
use sqlx::Column;  // 导入 Column trait


#[allow(unused_imports)]
use crate::common::response::Pagination;// 分页接口返回值

#[allow(unused_imports)]
use crate::common::response::ListResponse;// 列表接口返回值


#[allow(unused_imports)]
use actix_web::{web, HttpRequest, HttpResponse, Responder};

#[allow(unused_imports)]
use crate::modules::user::models::User; // 导入 User 模型

#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {
    pub code: i32,
    pub message: String,
    pub data: Option<T>,
}

#[allow(dead_code)]
pub async fn list_api_page(
    pool: web::Data<MySqlPool>,
    query: web::Query<Pagination>,
) -> impl Responder {

    let page = query.page_num.unwrap_or(1);
    let page_size = query.page_size.unwrap_or(10);
    let offset = (page - 1) * page_size;

    // info!("收到查询请求{}",query.page_num.unwrap_or(1));

    // 查询总数
    let total: (i64,) = match sqlx::query_as("SELECT COUNT(*) FROM sys_user")
        .fetch_one(pool.get_ref())
        .await
    {
        Ok(count) => count,
        Err(e) => {
            eprintln!("查询总数失败: {:?}", e);
            return HttpResponse::InternalServerError().body("查询总数失败");
        }
    };


    // 分页查询
    let users = sqlx::query_as::<_, User>("SELECT * FROM sys_user LIMIT ? OFFSET ?")
        .bind(page_size as i64)
        .bind(offset as i64)
        .fetch_all(pool.get_ref())
        .await;

    // let users = sqlx::query_as::<_, User>("SELECT * FROM sys_user")
    //     .fetch_all(pool.get_ref())
    //     .await;

    match users {
        Ok(list) => HttpResponse::Ok().json(
         ListResponse {
            code: 200,
            msg: "注册成功",
            data: Some(list),
            total: total.0,
        }),
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            HttpResponse::InternalServerError().body("数据库查询失败")
        }
    }
}

🍎在主模块中测试

javascript
#[allow(unused_imports)]
use crate::common::apimethods::list_api_page; // 引入公共分页查询方法

 // 用户查询
pub async fn get_all_users(
    pool: web::Data<MySqlPool>,
    query: web::Query<Pagination>,
) -> impl Responder {
    // 你可以根据需要在这里使用 list_api_page 函数
    list_api_page(pool, query).await
}

测试一下,这里我们提取就o了,接下来把里面能用到的部分从函数外部控制即可

👉接口完善

🍎提取字段

javascript
//提取总数
let querytotal = format!("SELECT COUNT(*) FROM {}", table_name);

    let total: (i64,) = match sqlx::query_as(&querytotal)
        .fetch_one(pool.get_ref())
        .await
    {
        Ok(count) => count,
        Err(e) => {
            eprintln!("查询总数失败: {:?}", e);
            return HttpResponse::InternalServerError().body("查询总数失败");
        }
    };

//提取字段
 let query = format!("SELECT * FROM {} LIMIT ? OFFSET ?", table_name);
    let users = sqlx::query_as::<_, User>(&query)
        .bind(page_size as i64)
        .bind(offset as i64)
        .fetch_all(pool.get_ref())
        .await;

提取完成以后我们直接使用

javascript
 pub async fn get_all_users(
      pool: web::Data<MySqlPool>,
      query: web::Query<Pagination>,
  ) -> impl Responder {
      // 你可以根据需要在这里使用 list_api_page 函数
      list_api_page(pool, query,"sys_user").await
  }

测试ok

🍎精确查询和模糊查询

接下来添加查询条件,根据查询条件筛选数据,这里我们分为常见的两种,一种就是模糊查询,另外一种就是具体查询。

javascript
pub async fn list_api_page(
    pool: web::Data<MySqlPool>,
    query: web::Query<Pagination>,
    _filter: Option<web::Query<ListQuery>>, // 动态查询条件
    table_name: &str,                      // 表名
    exactquery: HashMap<String, String>,   // 精确查询条件
    likequery: HashMap<String, String>,    // 模糊查询条件
) -> impl Responder {
    let page = query.page_num.unwrap_or(1);
    let page_size = query.page_size.unwrap_or(10);
    let offset = (page - 1) * page_size;

    // 查询总数的 SQL
    let mut query_total = format!("SELECT COUNT(*) FROM {}", table_name);

    // 查询条件
    let mut query_params: Vec<String> = Vec::new();  // 存储具体类型
    let mut where_clauses = Vec::new();

    // 处理精确查询(exactquery)
    for (field, value) in exactquery {
        where_clauses.push(format!("{} = ?", field)); // 精确查询
        query_params.push(value); // 使用具体类型
    }

    // 处理模糊查询(likequery)
    for (field, value) in likequery {
        where_clauses.push(format!("{} LIKE ?", field)); // 模糊查询
        query_params.push(format!("%{}%", value)); // 模糊查询时需要加上 %
    }

    // 如果有查询条件,添加 WHERE 子句
    if !where_clauses.is_empty() {
        query_total.push_str(" WHERE ");
        query_total.push_str(&where_clauses.join(" AND "));
    }

    // 创建查询总数
    let mut query = sqlx::query_as::<_, (i64,)>(&query_total);

    // 遍历并逐个绑定参数
    for value in query_params.iter() {
        query = query.bind(value);  // 直接绑定具体的值
    }

    let total: (i64,) = match query.fetch_one(pool.get_ref()).await {
        Ok(count) => count,
        Err(e) => {
            eprintln!("查询总数失败: {:?}", e);
            return HttpResponse::InternalServerError().body("查询总数失败");
        }
    };

    // 分页查询 SQL
    let mut query = format!("SELECT * FROM {}", table_name);

    // 如果有查询条件,拼接 WHERE 子句
    if !where_clauses.is_empty() {
        query.push_str(" WHERE ");
        query.push_str(&where_clauses.join(" AND "));
    }

    // 添加分页条件
    query.push_str(" LIMIT ? OFFSET ?");

    // 合并查询参数
    query_params.push(page_size.to_string());  // 添加分页参数
    query_params.push(offset.to_string());    // 添加偏移量
    println!("mysql查询条件{}",query);
    
    // 创建查询
    let mut query = sqlx::query_as::<_, User>(&query);
    for value in query_params.iter() {
        query = query.bind(value);  // 绑定分页参数
    }

    // 执行查询
    let users = match query.fetch_all(pool.get_ref()).await {
        Ok(list) => list,
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            return HttpResponse::InternalServerError().body("数据库查询失败");
        }
    };

    // 返回结果
    HttpResponse::Ok().json(ListResponse {
        code: 200,
        msg: "查询成功",
        data: Some(users),
        total: total.0,
    })
}

上面的方法我们测试一下,在没有任何条件的时候输出,这不是我们想要的,我们继续优化一下,没有参数的时候就去掉查询条件

javascript
SELECT * FROM sys_user WHERE age = ? AND name LIKE ? LIMIT ? OFFSET ?

构建方法以后在我们的具体部分进行引入

javascript
pub async fn get_all_users(
    pool: web::Data<MySqlPool>,
    query: web::Query<Pagination>,
    filter: Option<web::Query<ListQuery>>, // 接受动态查询条件
) -> impl Responder {
    // 用户查询可以不传递 filters(即 filters 为 None),如果需要过滤条件可以根据需求传递
    let exactquery: HashMap<String, String> = [
         ("age".to_string(), 6.to_string())    // 精确查询
    ]
    .iter()
    .cloned()
    .collect();

    let likequery: HashMap<String, String> = [
        // ("name".to_string(), "6".to_string()) // 模糊查询
    ]
    .iter()
    .cloned()
    .collect();

    list_api_page(pool, query, filter, "sys_user",exactquery,likequery).await
}

测试一下我们的精确查询和模糊查询,功能ok

🍎根据查询分页自动更改接口

接下来我们更改分页接口,有分页数据的时候查询分页数据,没有分页数据的时候查询出所有的,同时给一个总数

测试查询条件为

javascript
{}

返回数据为

javascript
{
    "code": 200,
    "msg": "查询成功",
    "data": [
       xxx
       11 条数据
    ],
    "total": 11
}

有查询条件的时候

javascript
//查询条件
pageNum: 1
pageSize: 10

//查询结果正常
{
    "code": 200,
    "msg": "查询成功",
    "data": [
       xxx
       11 条数据
    ],
    "total": 10
}

这时候我们就实现了一个接口可以进行分页非分页两种功能的实现

🍎精确查询和模糊查询参数传递

上面我们只是模拟了参数传递,接下来我们从前端把查询参数拿过来进行处理,并且简化一下

javascript

let exactquery=["age","username"];
let likequery:=["user_id"];

上面的数据进行处理成为下面数据

let exactquery: HashMap<String, String> = [
  ("age".to_string(), 6.to_string())    // 精确查询
]
.iter()
.cloned()
.collect();

let likequery: HashMap<String, String> = [
        ("age".to_string(), "6".to_string()) // 模糊查询
]
.iter()
.cloned()
.collect();

这里我们还需要注意我们的过滤条件是前端传过来的,优化以后最终版本如下

javascript
// 通用用户查询
pub async fn get_all_users(
    pool: web::Data<MySqlPool>,
    query: web::Query<QueryParams>,
    filter: Option<web::Query<HashMap<String, String>>>
) -> impl Responder {
    // 1. 定义精确查询字段(exact query)和模糊查询字段(like query)
    let exactquery = vec![
        "age".to_string(), 
        "sex".to_string()];
    let likequery = vec!["name".to_string()];
   // 调用 list_api_page 传入查询条件
   list_api_page(pool, query, filter, "sys_user", exactquery, likequery).await
}
javascript

#[allow(dead_code)]
pub async fn list_api_page(
    pool: web::Data<MySqlPool>,
    query: web::Query<QueryParams>,
    _filter: Option<web::Query<HashMap<String, String>>>, // 动态查询条件
    table_name: &str,  // 表名
    exactquery: Vec<String>,  // 精确查询字段名
    likequery: Vec<String>,   // 模糊查询字段名
) -> impl Responder {
    // 查询总数的 SQL
    let mut query_total = format!("SELECT COUNT(*) FROM {}", table_name);

    // 查询条件
    let mut query_params: Vec<String> = Vec::new();  // 存储查询参数
    let mut where_clauses = Vec::new();

    // 处理精确查询(exactquery)
    for field in exactquery {
        if let Some(value) = _filter.as_ref().and_then(|f| f.get(&field)) {
            if !value.is_empty() { // 确保值不为空字符串
                where_clauses.push(format!("{} = ?", field));  // 精确查询
                query_params.push(value.clone());  // 将非空值加入查询条件
            }
        }
    }

    // 处理模糊查询(likequery)
    for field in likequery {
        if let Some(value) = _filter.as_ref().and_then(|f| f.get(&field)) {
            if !value.is_empty() { // 确保值不为空字符串
                where_clauses.push(format!("{} LIKE ?", field));  // 模糊查询
                query_params.push(format!("%{}%", value));  // 模糊查询时需要加上 %
            }
        }
    }

    // 处理分页参数,避免空字符串、无效字符串导致解析错误
    let mut page_num = None;
    let mut page_size = None;
    
    // 判断 `page_num` 是否有效,只有有效时才赋值
    if let Some(num_str) = &query.page_num {
        if !num_str.is_empty() {
            page_num = num_str.parse::<u32>().ok(); // 使用 `ok()` 来忽略解析失败的情况
        }
    }
    
    // 判断 `page_size` 是否有效,只有有效时才赋值
    if let Some(size_str) = &query.page_size {
        if !size_str.is_empty() {
            page_size = size_str.parse::<u32>().ok(); // 使用 `ok()` 来忽略解析失败的情况
        }
    }

    // 计算分页偏移量,如果没有有效分页参数,则不应用分页
    let offset = match (page_num, page_size) {
        (Some(num), Some(size)) => (num - 1) * size,
        _ => 0, // 如果没有有效的分页参数,则不添加分页
    };

    // 如果有查询条件,添加 WHERE 子句
    if !where_clauses.is_empty() {
        query_total.push_str(" WHERE ");
        query_total.push_str(&where_clauses.join(" AND "));
    }

    // 创建查询总数
    let mut query = sqlx::query_as::<_, (i64,)>(&query_total);

    // 遍历并逐个绑定参数
    for value in query_params.iter() {
        query = query.bind(value);  // 绑定查询参数
    }

    let total: (i64,) = match query.fetch_one(pool.get_ref()).await {
        Ok(count) => count,
        Err(e) => {
            eprintln!("查询总数失败: {:?}", e);
            return HttpResponse::InternalServerError().body("查询总数失败");
        }
    };

    // 分页查询 SQL
    let mut query = format!("SELECT * FROM {}", table_name);

    // 如果有查询条件,拼接 WHERE 子句
    if !where_clauses.is_empty() {
        query.push_str(" WHERE ");
        query.push_str(&where_clauses.join(" AND "));
    }

    // 仅在有有效分页参数时,添加 LIMIT 和 OFFSET
    if let (Some(page_size), Some(page_num)) = (page_size, page_num) {
        query.push_str(" LIMIT ? OFFSET ?");
        query_params.push(page_size.to_string());  // 添加分页参数
        query_params.push(offset.to_string());    // 添加偏移量
    }

    // 创建查询
    let mut query = sqlx::query_as::<_, User>(&query);
    for value in query_params.iter() {
        query = query.bind(value);  // 绑定分页参数
    }

    // 执行查询
    let users = match query.fetch_all(pool.get_ref()).await {
        Ok(list) => list,
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            return HttpResponse::InternalServerError().body("数据库查询失败");
        }
    };

    // 返回结果
    HttpResponse::Ok().json(ListResponse {
        code: 200,
        msg: "查询成功",
        data: Some(users),
        total: total.0,
    })
}

测试一下,ok,后续想继续抽离的就可以继续进行了。

👉抽离类型(×)

🍎提取类型

接下来我们将公共查询的接口类型进行抽离,手动将我们的类型给拆分出来

javascript
// 优化以前写法
// 创建查询
  let mut query = sqlx::query_as::<_, User>(&query);
  for value in query_params.iter() {
      query = query.bind(value);  // 绑定分页参数
  }

  // 执行查询
  let resultlist = match query.fetch_all(pool.get_ref()).await {
      Ok(list) => list,
      Err(e) => {
          eprintln!("数据库查询失败: {:?}", e);
          return HttpResponse::InternalServerError().body("数据库查询失败");
      }
  };




// 优化以后写法
// 使用 sqlx::query 而不是 query_as,返回原始行数据
    let mut query = sqlx::query(&query);
    for value in query_params.iter() {
        query = query.bind(value);
    }

    // 执行查询并处理结果
    let rows = match query.fetch_all(pool.get_ref()).await {
        Ok(rows) => rows,
        Err(e) => {
            eprintln!("数据库查询失败: {:?}", e);
            return HttpResponse::InternalServerError().body("数据库查询失败");
        }
    };

    // 将行数据转换为 HashMap
    let mut resultlist = Vec::new();
    for row in rows {
        let mut record = HashMap::new();
        for (i, column) in row.columns().iter().enumerate() {
            let column_name = column.name().to_string();
            // 尝试获取不同类型的值
            if let Ok(value) = row.try_get::<String, _>(i) {
                record.insert(column_name, value);
            } else if let Ok(value) = row.try_get::<i32, _>(i) {
                record.insert(column_name, value.to_string());
            } else if let Ok(value) = row.try_get::<i64, _>(i) {
                record.insert(column_name, value.to_string());
            } else {
                // 如果都失败,尝试作为字符串获取
                if let Ok(value) = row.try_get::<String, _>(i) {
                    record.insert(column_name, value);
                } else {
                    record.insert(column_name, "".to_string());
                }
            }
        }
        resultlist.push(record);
    }

🍎重新将返回字段

snake_case(蛇形命名法)改为camelCase(驼峰命名法)

javascript
// 添加一个字段映射函数
fn map_field_name(column_name: &str) -> String {
    match column_name {
        "user_id" => "userId".to_string(),
        "user_height" => "userHeight".to_string(),
        "user_weight" => "userWeight".to_string(),
        "created_at" => "createdAt".to_string(),
        "updated_at" => "updatedAt".to_string(),
        _ => column_name.to_string(),
    }
}


// 将行数据转换为 HashMap
let mut resultlist = Vec::new();
for row in rows {
    let mut record = HashMap::new();
    for (i, column) in row.columns().iter().enumerate() {
        let column_name = column.name().to_string();
        let mapped_name = map_field_name(&column_name);

        // 尝试获取不同类型的值
        if let Ok(value) = row.try_get::<String, _>(i) {
            record.insert(mapped_name, value);
        } else if let Ok(value) = row.try_get::<i32, _>(i) {
            record.insert(mapped_name, value.to_string());
        } else if let Ok(value) = row.try_get::<i64, _>(i) {
            record.insert(mapped_name, value.to_string());
        } else {
            // 如果都失败,尝试作为字符串获取
            if let Ok(value) = row.try_get::<String, _>(i) {
                record.insert(mapped_name, value);
            } else {
                record.insert(mapped_name, "".to_string());
            }
        }
    }
    resultlist.push(record);
}

再次访问,这个时候已经可以更改字段了,并且可以实现我们自定义字段的命名

🍎默认snake_case 转camelCase

现在我们是预定义的字段转化,添加对于未预定义的字段,自动将 snake_case 转换为 camelCase

javascript

// 添加一个字段映射函数
fn map_field_name(column_name: &str) -> String {
    // 首先检查是否有预定义的映射
    match column_name {
        "user_id" => "userId".to_string(),
        "user_height" => "user_height".to_string(),
        "user_weight" => "userWeight".to_string(),
        "created_at" => "createdAt".to_string(),
        "updated_at" => "updatedAt".to_string(),
        _ => {
            // 如果没有预定义映射,则自动转换 snake_case 为 camelCase
            snake_to_camel_case(column_name)
        }
    }
}

// 高级 snake_case 转 camelCase 函数
fn snake_to_camel_case(snake: &str) -> String {
    if snake.is_empty() {
        return String::new();
    }
    
    let mut result = String::new();
    let mut capitalize_next = false;
    let mut first_char = true;
    let mut prev_was_underscore = false;
    
    for ch in snake.chars() {
        if ch == '_' {
            if !prev_was_underscore {
                capitalize_next = true;
            }
            prev_was_underscore = true;
        } else {
            if capitalize_next || first_char {
                result.push(ch.to_ascii_uppercase());
                capitalize_next = false;
                first_char = false;
            } else {
                result.push(ch);
            }
            prev_was_underscore = false;
        }
    }
    
    result
}

测试一下,查看我们的返回,这个时候已经到了我们的期望

javascript
userId: "44"
userWeight: ""
user_height: ""

👉抽离接口软删除条件

javascript
pub async fn get_all_users(
    pool: web::Data<MySqlPool>,
    query: web::Query<QueryParams>,
    filter: Option<web::Query<HashMap<String, String>>>
) -> impl Responder {
    // 1. 定义精确查询字段(exact query)和模糊查询字段(like query)
    let exactquery = vec![
        "age".to_string(),
        "sex".to_string(),
        "is_deleted".to_string()   // 加上这一行
    ];
    let likequery = vec![
        "name".to_string(),
        "phone".to_string()
    ];

    let mut filter_map = filter.map(|f| f.into_inner()).unwrap_or_default();
    filter_map.insert("is_deleted".to_string(), "0".to_string());

   // 调用 list_api_page 传入查询条件
   list_api_page(pool, query, Some(web::Query(filter_map)), "sys_user", exactquery, likequery).await
}

新增优化

接下来我们抽离新增接口

🍎旧的写法

我们现在的新增的写法如下

javascript
// 新增用户
pub async fn post_add_users(
    pool: web::Data<MySqlPool>,
    form: web::Json<AddUserRequest>,
) -> HttpResponse {
    // 1. 检查用户名是否已存在
    let exists: (i64,) = match sqlx::query_as("SELECT COUNT(*) FROM sys_user WHERE username = ?")
        .bind(&form.username)
        .fetch_one(pool.get_ref())
        .await
    {
        Ok(count) => count,
        Err(_) => {
            return HttpResponse::InternalServerError().json(ApiResponse {
                code: 500,
                msg: "数据库错误",
                data: None::<()>,
            })
        }
    };
    if exists.0 > 0 {
        return HttpResponse::Ok().json(ApiResponse {
            code: 400,
            msg: "用户名已存在",
            data: None::<()>,
        });
    }

    // 2. 密码加密
    let hashed_pwd = match hash(&form.password, DEFAULT_COST) {
        Ok(pwd) => pwd,
        Err(_) => {
            return HttpResponse::InternalServerError().json(ApiResponse {
                code: 500,
                msg: "密码加密失败",
                data: None::<()>,
            })
        }
    };

    // 3. 插入新用户
    // 插入新用户 
    let result = sqlx::query(
        "INSERT INTO sys_user (username,password, age, address, sex, phone,disease,name) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
    )
   
    .bind(&form.username)
    .bind(&hashed_pwd)
    .bind(&form.age)
    .bind(&form.address)
   .bind(form.sex)
   .bind(&form.phone)
   .bind(&form.disease)
   .bind(&form.name)
    .execute(pool.get_ref())
    .await;
    match result {
        Ok(_) => HttpResponse::Ok().json(ApiResponse {
            code: 200,
            msg: "新增用户成功",
            data: None::<()>,
        }),
        Err(e) => {
            eprintln!("新增用户失败: {:?}", e);
            HttpResponse::InternalServerError().json(ApiResponse {
                code: 500,
                msg: "新增用户失败",
                data: None::<()>,
            })
        }
    }
}

🍎抽离写法

javascript
// 通用新增公共方法
pub async fn create_api(
    pool: &sqlx::MySqlPool,
    table_name: &str,
    data: &std::collections::HashMap<String, String>,
    unique_fields: &[String],
    password_field: Option<&str>,
) -> Result<HttpResponse, sqlx::Error> {
    // 1. 检查唯一性约束
    for field in unique_fields {
        if let Some(value) = data.get(field) {
            let exists: (i64,) = sqlx::query_as(&format!("SELECT COUNT(*) FROM {} WHERE {} = ?", table_name, field))
                .bind(value)
                .fetch_one(pool)
                .await?;
            
            if exists.0 > 0 {
                return Ok(HttpResponse::Ok().json(BasicResponse {
                    code: 400,
                    msg: "数据已存在",
                }));
            }
        }
    }

    // 2. 处理密码加密(如果存在密码字段)
    let mut processed_data = data.clone();
    if let Some(pwd_field) = password_field {
        if let Some(password) = processed_data.get(pwd_field) {
            let hashed_pwd = match hash(password, DEFAULT_COST) {
                Ok(pwd) => pwd,
                Err(_) => {
                    return Ok(HttpResponse::InternalServerError().json(BasicResponse {
                        code: 500,
                        msg: "密码加密失败",
                    }))
                }
            };
            processed_data.insert(pwd_field.to_string(), hashed_pwd);
        }
    }

    // 3. 构建插入SQL
    let fields: Vec<String> = processed_data.keys().cloned().collect();
    let placeholders: Vec<String> = fields.iter().map(|_| "?".to_string()).collect();
    
    let sql = format!(
        "INSERT INTO {} ({}) VALUES ({})",
        table_name,
        fields.join(", "),
        placeholders.join(", ")
    );

    // 4. 执行插入
    let mut query = sqlx::query(&sql);
    for field in &fields {
        if let Some(value) = processed_data.get(field) {
            query = query.bind(value);
        }
    }

    match query.execute(pool).await {
        Ok(_) => Ok(HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "新增成功",
        })),
        Err(e) => {
            eprintln!("新增失败: {:?}", e);
            Ok(HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "新增失败",
            }))
        }
    }
}

🍎使用方法新增

javascript

// 使用更高级的公共方法
pub async fn post_add_users(
    pool: web::Data<MySqlPool>,
    form: web::Json<AddUserRequest>,
) -> HttpResponse {
    // 将请求数据转换为HashMap
    let mut data = std::collections::HashMap::new();
    data.insert("username".to_string(), form.username.clone());
    data.insert("password".to_string(), form.password.clone());
    data.insert("age".to_string(), form.age.clone());
    data.insert("address".to_string(), form.address.clone());
    data.insert("sex".to_string(), form.sex.to_string());
    data.insert("phone".to_string(), form.phone.clone());
    data.insert("disease".to_string(), form.disease.clone());
    data.insert("name".to_string(), form.name.clone());

    // 使用高级公共方法
    match crate::common::apimethods::create_api(
        pool.get_ref(),
        "sys_user",
        &data,
        &["username".to_string()], // 唯一性检查字段
        Some("password"), // 密码字段
    ).await {
        Ok(response) => response,
        Err(_) => HttpResponse::InternalServerError().json(ApiResponse {
            code: 500,
            msg: "数据库操作失败",
            data: None::<()>,
        }),
    }
}

测试新增接口,新增用户ok

🍎测试接口

我们尝试写一个角色的新增接口测试一下

javascript

// 通用新增
// 使用高级公共方法的新增角色接口
pub async fn post_add(
    pool: web::Data<MySqlPool>,
    form: web::Json<AddRoleRequest>,
) -> HttpResponse {
    // 将请求数据转换为HashMap,注意字段名映射到数据库字段名
    let mut data = std::collections::HashMap::new();
    data.insert("role_name".to_string(), form.roleName.clone());
    data.insert("role_key".to_string(), form.roleKey.clone());
    data.insert("role_sort".to_string(), form.roleSort.to_string());
    data.insert("status".to_string(), form.status.clone());
    data.insert("remark".to_string(), form.remark.clone());
    
    // 处理布尔值字段
    data.insert("menu_check_strictly".to_string(), if form.menuCheckStrictly { "1" } else { "0" }.to_string());
    data.insert("dept_check_strictly".to_string(), if form.deptCheckStrictly { "1" } else { "0" }.to_string());
    
    // 处理数组字段 - 转换为JSON字符串存储
    // data.insert("menu_ids".to_string(), serde_json::to_string(&form.menuIds).unwrap_or_default());
    // data.insert("dept_ids".to_string(), serde_json::to_string(&form.deptIds).unwrap_or_default());

    // 使用高级公共方法(角色不需要密码)
    match crate::common::apimethods::create_api(
        pool.get_ref(),
        "sys_role",
        &data,
        &[
            "role_name".to_string(),
            "role_key".to_string()
        ], // 唯一性检查字段
        None, // 没有密码字段
    ).await {
        Ok(response) => response,
        Err(_) => HttpResponse::InternalServerError().json(ApiResponse {
            code: 500,
            msg: "数据库操作失败",
            data: None::<()>,
        }),
    }
}

测试返回信息ok,说明我们新增成功!

javascript
{
    "code": 200,
    "msg": "新增成功"
}

🍎抽离密码

上面我们密码的方法是在外面传入参数,这就导致我们每次使用密码的时候,都需要外部传入

别的没有密码的新增需要传入data: None::<()>

javascript
pub async fn create_api(
    pool: &sqlx::MySqlPool,
    table_name: &str,
    data: &std::collections::HashMap<String, String>,
    unique_fields: &[String],
) -> Result<HttpResponse, sqlx::Error> {
    // 1. 检查唯一性约束
    for field in unique_fields {
        if let Some(value) = data.get(field) {
            let exists: (i64,) = sqlx::query_as(&format!("SELECT COUNT(*) FROM {} WHERE {} = ?", table_name, field))
                .bind(value)
                .fetch_one(pool)
                .await?;
            if exists.0 > 0 {
                return Ok(HttpResponse::Ok().json(BasicResponse {
                    code: 400,
                    msg: "数据已存在",
                }));
            }
        }
    }

    // 2. 处理密码加密(如果 data 里有 "password" 字段)
    let mut processed_data = data.clone();
    if let Some(password) = processed_data.get("password") {
        let hashed_pwd = match hash(password, DEFAULT_COST) {
            Ok(pwd) => pwd,
            Err(_) => {
                return Ok(HttpResponse::InternalServerError().json(BasicResponse {
                    code: 500,
                    msg: "密码加密失败",
                }))
            }
        };
        processed_data.insert("password".to_string(), hashed_pwd);
    }

    // 3. 构建插入SQL
    let fields: Vec<String> = processed_data.keys().cloned().collect();
    let placeholders: Vec<String> = fields.iter().map(|_| "?".to_string()).collect();
    
    let sql = format!(
        "INSERT INTO {} ({}) VALUES ({})",
        table_name,
        fields.join(", "),
        placeholders.join(", ")
    );

    // 4. 执行插入
    let mut query = sqlx::query(&sql);
    for field in &fields {
        if let Some(value) = processed_data.get(field) {
            query = query.bind(value);
        }
    }

    match query.execute(pool).await {
        Ok(_) => Ok(HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "新增成功",
        })),
        Err(e) => {
            eprintln!("新增失败: {:?}", e);
            Ok(HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "新增失败",
            }))
        }
    }
}

测试我们的接口,接口正常!

🍎进一步优化返回信息

javascript
pub async fn create_api(
    pool: &sqlx::MySqlPool,
    table_name: &str,
    data: &std::collections::HashMap<String, String>,
    unique_fields: &[String],
) -> HttpResponse {
    // 1. 检查唯一性约束
    for field in unique_fields {
        if let Some(value) = data.get(field) {
            let exists: Result<(i64,), _> = sqlx::query_as(&format!("SELECT COUNT(*) FROM {} WHERE {} = ?", table_name, field))
                .bind(value)
                .fetch_one(pool)
                .await;
            match exists {
                Ok((count,)) if count > 0 => {
                    return HttpResponse::Ok().json(BasicResponse {
                        code: 400,
                        msg: "数据已存在",
                    });
                }
                Err(e) => {
                    eprintln!("唯一性检查失败: {:?}", e);
                    return HttpResponse::InternalServerError().json(BasicResponse {
                        code: 500,
                        msg: "唯一性检查失败",
                    });
                }
                _ => {}
            }
        }
    }

    // 2. 处理密码加密(如果 data 里有 "password" 字段)
    let mut processed_data = data.clone();
    if let Some(password) = processed_data.get("password") {
        let hashed_pwd = match hash(password, DEFAULT_COST) {
            Ok(pwd) => pwd,
            Err(_) => {
                return HttpResponse::InternalServerError().json(BasicResponse {
                    code: 500,
                    msg: "密码加密失败",
                });
            }
        };
        processed_data.insert("password".to_string(), hashed_pwd);
    }

    // 3. 构建插入SQL
    let fields: Vec<String> = processed_data.keys().cloned().collect();
    let placeholders: Vec<String> = fields.iter().map(|_| "?".to_string()).collect();
    let sql = format!(
        "INSERT INTO {} ({}) VALUES ({})",
        table_name,
        fields.join(", "),
        placeholders.join(", ")
    );

    // 4. 执行插入
    let mut query = sqlx::query(&sql);
    for field in &fields {
        if let Some(value) = processed_data.get(field) {
            query = query.bind(value);
        }
    }

    match query.execute(pool).await {
        Ok(_) => HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "新增成功",
        }),
        Err(e) => {
            eprintln!("新增失败: {:?}", e);
            HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "新增失败",
            })
        }
    }
}

使用方法

javascript
pub async fn post_add_users(
    pool: web::Data<MySqlPool>,
    form: web::Json<AddUserRequest>,
) -> HttpResponse {
    let mut data = std::collections::HashMap::new();
    data.insert("username".to_string(), form.username.clone());
    data.insert("password".to_string(), form.password.clone());
    data.insert("age".to_string(), form.age.clone());
    data.insert("address".to_string(), form.address.clone());
    data.insert("sex".to_string(), form.sex.to_string());
    data.insert("phone".to_string(), form.phone.clone());
    data.insert("disease".to_string(), form.disease.clone());
    data.insert("name".to_string(), form.name.clone());

    crate::common::apimethods::create_api(
        pool.get_ref(),
        "sys_user",
        &data,
        &["username".to_string()],
    ).await
}

详情优化

接下来我们抽离详情接口

🍎旧的写法

之前我们的写法如下

javascript
pub async fn get_user_detail(
    pool: web::Data<MySqlPool>,
    path: web::Path<i32>, // user_id
) -> HttpResponse {
    let user_id = path.into_inner();

    let result = sqlx::query_as::<_, User>("SELECT * FROM sys_user WHERE user_id = ?")
        .bind(user_id)
        .fetch_one(pool.get_ref())
        .await;

    match result {
        Ok(user) => {
            let response = ApiDetailResponse {
                code: 200,
                msg: "查询成功",
                data: user, // 返回数据格式可根据实际需求进行修改
            };
            HttpResponse::Ok().json(response)
        }
        Err(sqlx::Error::RowNotFound) => {
            let response = ApiDetailResponse {
                code: 404,
                msg: "用户不存在",
                data: (),
            };
            HttpResponse::NotFound().json(response)
        }
        Err(e) => {
            eprintln!("查询用户详情失败: {:?}", e);
            let response = ApiDetailResponse {
                code: 500,
                msg: "查询失败",
                data: (),
            };
            HttpResponse::InternalServerError().json(response)
        }
    }
}

🍎抽离出来详情方法

javascript
// 通用详情
pub async fn detail_api<T>(
    pool: web::Data<MySqlPool>,
    table: &str,
    pk_field: &str,
    id: i32,
) -> HttpResponse
where
    T: for<'r> FromRow<'r, sqlx::mysql::MySqlRow> + serde::Serialize + Unpin + Send,
{
    let query = format!("SELECT * FROM {} WHERE {} = ?", table, pk_field);
    let result = sqlx::query_as::<_, T>(&query)
        .bind(id)
        .fetch_one(pool.get_ref())
        .await;

    match result {
        Ok(data) => HttpResponse::Ok().json(ApiDetailResponse {
            code: 200,
            msg: "查询成功",
            data: data,
        }),
        Err(sqlx::Error::RowNotFound) => HttpResponse::NotFound().json(BasicResponse {
            code: 404,
            msg: "数据不存在"
        }),
        Err(e) => {
            eprintln!("查询详情失败: {:?}", e);
            HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "查询失败"
            })
        }
    }
}

🍎封装使用详情方法

javascript
// 通用详情
pub async fn get_user_detail(
    pool: web::Data<MySqlPool>,
    path: web::Path<i32>,
) -> HttpResponse {
    crate::common::apimethods::detail_api::<User>(pool, "sys_user", "user_id", path.into_inner()).await
}

修改优化

🍎现在的修改接口如下面所示

javascript
//更新
#[derive(Serialize)]
pub struct Response {
    code: i32,
    msg: String,
}
pub async fn put_update_users(
    pool: web::Data<Pool<MySql>>, 
    item: web::Json<UpdateUserRequest>
) -> impl Responder {
    let result = sqlx::query!(
        "UPDATE sys_user SET age = ? WHERE user_id = ?",
        item.age,
        item.user_id
    )
    .execute(pool.get_ref())
    .await;

    match result {
        Ok(_) => HttpResponse::Ok().json(Response {
            code: 200,
            msg: "更新成功!".to_string(),
        }),
        Err(e) => {
            eprintln!("数据库更新失败: {:?}", e);
            HttpResponse::InternalServerError().json(Response {
                code: 500,
                msg: "更新失败!".to_string(),
            })
        }
    }
}

🍎修改接口以后

javascript
pub async fn update_api(
    pool: &MySqlPool,
    table: &str,
    pk_field: &str,
    pk_value: i32,
    data: &HashMap<String, String>,
) -> HttpResponse {
    // 构建 SET 语句
    let sets: Vec<String> = data.keys().map(|k| format!("{} = ?", k)).collect();
    let sql = format!(
        "UPDATE {} SET {} WHERE {} = ?",
        table,
        sets.join(", "),
        pk_field
    );

    let mut query = sqlx::query(&sql);
    for key in data.keys() {
        query = query.bind(data.get(key).unwrap());
    }
    query = query.bind(pk_value);

    match query.execute(pool).await {
        Ok(_) => HttpResponse::Ok().json(Response {
            code: 200,
            msg: "更新成功!".to_string(),
        }),
        Err(e) => {
            eprintln!("数据库更新失败: {:?}", e);
            HttpResponse::InternalServerError().json(Response {
                code: 500,
                msg: "更新失败!".to_string(),
            })
        }
    }
}

🍎测试接口

我们想要的字段可以更改,其他字段无法更改

返回结果如下

javascript
{
    "code": 200,
    "msg": "更新成功!"
}

删除优化

接下来我们抽离删除接口

🍎旧的写法

javascript
pub async fn delete_user(
    pool: web::Data<Pool<MySql>>, 
    id: web::Path<i32>  // Path 中包含了用户的 ID
) -> impl Responder {
    // 执行删除操作
    let result = sqlx::query!(
        "DELETE FROM sys_user WHERE user_id = ?",
        *id  // 解引用 Path 获取具体的值
    )
    .execute(pool.get_ref())
    .await;
    match result {
        Ok(_) => HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "删除成功!",
        }),
        Err(e) => {
            eprintln!("数据库删除失败: {:?}", e);
            HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "删除失败!",
            })
        }
    }
}

🍎抽离出来删除方法

javascript
// 通用删除接口
pub async fn delete_api(
    pool: &MySqlPool,
    table: &str,
    pk_field: &str,
    pk_value: i32,
) -> HttpResponse {
    let sql = format!("DELETE FROM {} WHERE {} = ?", table, pk_field);
    let result = sqlx::query(&sql)
        .bind(pk_value)
        .execute(pool)
        .await;

    match result {
        Ok(_) => HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "删除成功!",
        }),
        Err(e) => {
            eprintln!("数据库删除失败: {:?}", e);
            HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "删除失败!",
            })
        }
    }
}

🍎封装使用删除方法

javascript
// 删除用户
pub async fn delete_user(
    pool: web::Data<MySqlPool>, 
    id: web::Path<i32>
) -> HttpResponse {
    crate::common::apimethods::delete_api(
        pool.get_ref(),
        "sys_user",
        "user_id",
        *id
    ).await
}

🍎测试接口ok

javascript
{
    "code": 200,
    "msg": "删除成功!"
}

🍎软删除

接下来我们将删除更改为软删除。正常我们项目之中都会有假删除和真删除部分

javascript
// 通用删除接口
pub async fn delete_api(
    pool: &MySqlPool,
    table: &str,
    pk_field: &str,
    pk_value: i32,
    soft_delete: bool, // 新增参数,true=软删除,false=真删除
) -> HttpResponse {
    let sql;
    let mut query;

    if soft_delete {
        // 假删除,isDeleted 字段置为 1
        sql = format!("UPDATE {} SET isDeleted = ? WHERE {} = ?", table, pk_field);
        query = sqlx::query(&sql)
            .bind(1) // 1 表示已删除
            .bind(pk_value);
    } else {
        // 真删除
        sql = format!("DELETE FROM {} WHERE {} = ?", table, pk_field);
        query = sqlx::query(&sql)
            .bind(pk_value);
    }

    let result = query.execute(pool).await;

    match result {
        Ok(_) => HttpResponse::Ok().json(BasicResponse {
            code: 200,
            msg: "删除成功!",
        }),
        Err(e) => {
            eprintln!("数据库删除失败: {:?}", e);
            HttpResponse::InternalServerError().json(BasicResponse {
                code: 500,
                msg: "删除失败!",
            })
        }
    }
}

🍎使用软删除

javascript
// 软删除
pub async fn delete_user(
    pool: web::Data<MySqlPool>, 
    id: web::Path<i32>
) -> HttpResponse {
    crate::common::apimethods::delete_api(
        pool.get_ref(),
        "sys_user",
        "user_id",
        *id,
        true, // 软删除,isDeleted=1
    ).await
}

🍎使用真删除

javascript
// 通用真删除
pub async fn del_delete(
    pool: web::Data<MySqlPool>,
    id: web::Path<i32>
) -> HttpResponse {
    crate::common::apimethods::delete_api(
        pool.get_ref(),
        "sys_user",
        "user_id",
        *id,
        false, // 软删除,isDeleted=1
    ).await
}

Released under the MIT License.