Rust搜索优化

Rust搜索优化

👉接口分页

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

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

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,后续想继续抽离的就可以继续进行了。

相关推荐
不爱说话郭德纲5 分钟前
别再花冤枉钱!手把手教你免费生成iOS证书(.p12) + 打包IPA(超详细)
前端·ios·app
代码的余温6 分钟前
Vue多请求并行处理实战指南
前端·javascript·vue.js
lifallen8 分钟前
Disruptor高性能基石:Sequence并发优化解析
java·数据结构·后端·算法
JohnYan20 分钟前
工作笔记 - NATS的Nkey认证
javascript·后端·rabbitmq
guojl1 小时前
MyBatis插件机制
后端
SimonKing1 小时前
营销级二维码生成术:Java如何打造专属标识
java·后端·程序员
_风不会停息1 小时前
JDK21 虚拟线程的实现原理和应用
java·后端
余杭子曰1 小时前
组件设计模式:聪明组件还是傻瓜组件?
前端
菜鸟的迷茫1 小时前
分布式唯一 ID 生成方案对比(Snowflake、Leaf、Redis、数据库自增)
后端
小塔猫1 小时前
RabbitMQ相关信息及使用指南
后端