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