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
}