feat: 实现数据库迁移、搜索和分页功能
- 添加数据库迁移系统和初始用户表迁移 - 实现搜索功能模块和API - 实现分页功能支持 - 添加相关测试文件 - 更新项目配置和文档
This commit is contained in:
@@ -5,8 +5,10 @@ use sqlx::{SqlitePool, Row};
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::models::user::User;
|
||||
use crate::models::pagination::PaginationParams;
|
||||
use crate::models::search::UserSearchParams;
|
||||
use crate::utils::errors::ApiError;
|
||||
use crate::storage::UserStore;
|
||||
use crate::storage::{UserStore, MigrationManager};
|
||||
|
||||
/// SQLite 用户存储
|
||||
#[derive(Clone)]
|
||||
@@ -26,29 +28,21 @@ impl DatabaseUserStore {
|
||||
.await
|
||||
.map_err(|e| ApiError::InternalError(format!("无法连接到数据库: {}", e)))?;
|
||||
|
||||
let store = Self::new(pool);
|
||||
store.init_tables().await?;
|
||||
let store = Self::new(pool.clone());
|
||||
|
||||
// 使用迁移系统初始化数据库
|
||||
let migration_manager = MigrationManager::new(pool);
|
||||
migration_manager.run_migrations().await?;
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// 初始化数据库表
|
||||
/// 初始化数据库表 (已弃用,现在使用迁移系统)
|
||||
#[allow(dead_code)]
|
||||
pub async fn init_tables(&self) -> Result<(), ApiError> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ApiError::InternalError(format!("数据库初始化错误: {}", e)))?;
|
||||
|
||||
// 这个方法已被迁移系统替代
|
||||
// 保留用于向后兼容,但不再使用
|
||||
tracing::warn!("⚠️ init_tables 方法已弃用,请使用迁移系统");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -173,6 +167,174 @@ impl DatabaseUserStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// 分页获取用户列表
|
||||
async fn list_users_paginated_impl(&self, params: &PaginationParams) -> Result<(Vec<User>, u64), ApiError> {
|
||||
// 首先获取总数
|
||||
let count_result = sqlx::query("SELECT COUNT(*) as count FROM users")
|
||||
.fetch_one(&self.pool)
|
||||
.await;
|
||||
|
||||
let total_count = match count_result {
|
||||
Ok(row) => row.get::<i64, _>("count") as u64,
|
||||
Err(e) => return Err(ApiError::InternalError(format!("获取用户总数失败: {}", e))),
|
||||
};
|
||||
|
||||
// 然后获取分页数据
|
||||
let result = sqlx::query(
|
||||
"SELECT id, username, email, password_hash, created_at, updated_at
|
||||
FROM users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(params.limit() as i64)
|
||||
.bind(params.offset() as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(rows) => {
|
||||
let mut users = Vec::new();
|
||||
for row in rows {
|
||||
let user = User {
|
||||
id: Uuid::parse_str(&row.get::<String, _>("id"))
|
||||
.map_err(|e| ApiError::InternalError(format!("UUID 解析错误: {}", e)))?,
|
||||
username: row.get("username"),
|
||||
email: row.get("email"),
|
||||
password_hash: row.get("password_hash"),
|
||||
created_at: DateTime::parse_from_rfc3339(&row.get::<String, _>("created_at"))
|
||||
.map_err(|e| ApiError::InternalError(format!("时间解析错误: {}", e)))?
|
||||
.with_timezone(&Utc),
|
||||
updated_at: DateTime::parse_from_rfc3339(&row.get::<String, _>("updated_at"))
|
||||
.map_err(|e| ApiError::InternalError(format!("时间解析错误: {}", e)))?
|
||||
.with_timezone(&Utc),
|
||||
};
|
||||
users.push(user);
|
||||
}
|
||||
Ok((users, total_count))
|
||||
}
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 搜索和过滤用户(带分页)
|
||||
async fn search_users_impl(&self, search_params: &UserSearchParams, pagination_params: &PaginationParams) -> Result<(Vec<User>, u64), ApiError> {
|
||||
// 构建 WHERE 子句和参数
|
||||
let mut where_conditions = Vec::new();
|
||||
let mut bind_values: Vec<String> = Vec::new();
|
||||
|
||||
// 通用搜索(在用户名和邮箱中搜索)
|
||||
if let Some(q) = &search_params.q {
|
||||
where_conditions.push("(username LIKE ? OR email LIKE ?)".to_string());
|
||||
let search_pattern = format!("%{}%", q);
|
||||
bind_values.push(search_pattern.clone());
|
||||
bind_values.push(search_pattern);
|
||||
}
|
||||
|
||||
// 用户名过滤
|
||||
if let Some(username) = &search_params.username {
|
||||
where_conditions.push("username LIKE ?".to_string());
|
||||
bind_values.push(format!("%{}%", username));
|
||||
}
|
||||
|
||||
// 邮箱过滤
|
||||
if let Some(email) = &search_params.email {
|
||||
where_conditions.push("email LIKE ?".to_string());
|
||||
bind_values.push(format!("%{}%", email));
|
||||
}
|
||||
|
||||
// 创建时间范围过滤
|
||||
if let Some(created_after) = &search_params.created_after {
|
||||
if DateTime::parse_from_rfc3339(created_after).is_ok() {
|
||||
where_conditions.push("created_at >= ?".to_string());
|
||||
bind_values.push(created_after.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(created_before) = &search_params.created_before {
|
||||
if DateTime::parse_from_rfc3339(created_before).is_ok() {
|
||||
where_conditions.push("created_at <= ?".to_string());
|
||||
bind_values.push(created_before.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 WHERE 子句
|
||||
let where_clause = if where_conditions.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("WHERE {}", where_conditions.join(" AND "))
|
||||
};
|
||||
|
||||
// 构建 ORDER BY 子句
|
||||
let sort_field = match search_params.get_sort_by() {
|
||||
"username" => "username",
|
||||
"email" => "email",
|
||||
_ => "created_at", // 默认按创建时间排序
|
||||
};
|
||||
|
||||
let sort_order = if search_params.get_sort_order() == "asc" { "ASC" } else { "DESC" };
|
||||
let order_clause = format!("ORDER BY {} {}", sort_field, sort_order);
|
||||
|
||||
// 首先获取总数
|
||||
let count_query = format!("SELECT COUNT(*) as count FROM users {}", where_clause);
|
||||
let mut count_query_builder = sqlx::query(&count_query);
|
||||
|
||||
// 绑定参数到计数查询
|
||||
for value in &bind_values {
|
||||
count_query_builder = count_query_builder.bind(value);
|
||||
}
|
||||
|
||||
let count_result = count_query_builder.fetch_one(&self.pool).await;
|
||||
|
||||
let total_count = match count_result {
|
||||
Ok(row) => row.get::<i64, _>("count") as u64,
|
||||
Err(e) => return Err(ApiError::InternalError(format!("获取搜索结果总数失败: {}", e))),
|
||||
};
|
||||
|
||||
// 然后获取分页数据
|
||||
let data_query = format!(
|
||||
"SELECT id, username, email, password_hash, created_at, updated_at FROM users {} {} LIMIT ? OFFSET ?",
|
||||
where_clause, order_clause
|
||||
);
|
||||
|
||||
let mut data_query_builder = sqlx::query(&data_query);
|
||||
|
||||
// 绑定搜索参数
|
||||
for value in &bind_values {
|
||||
data_query_builder = data_query_builder.bind(value);
|
||||
}
|
||||
|
||||
// 绑定分页参数
|
||||
data_query_builder = data_query_builder
|
||||
.bind(pagination_params.limit() as i64)
|
||||
.bind(pagination_params.offset() as i64);
|
||||
|
||||
let result = data_query_builder.fetch_all(&self.pool).await;
|
||||
|
||||
match result {
|
||||
Ok(rows) => {
|
||||
let mut users = Vec::new();
|
||||
for row in rows {
|
||||
let user = User {
|
||||
id: Uuid::parse_str(&row.get::<String, _>("id"))
|
||||
.map_err(|e| ApiError::InternalError(format!("UUID 解析错误: {}", e)))?,
|
||||
username: row.get("username"),
|
||||
email: row.get("email"),
|
||||
password_hash: row.get("password_hash"),
|
||||
created_at: DateTime::parse_from_rfc3339(&row.get::<String, _>("created_at"))
|
||||
.map_err(|e| ApiError::InternalError(format!("时间解析错误: {}", e)))?
|
||||
.with_timezone(&Utc),
|
||||
updated_at: DateTime::parse_from_rfc3339(&row.get::<String, _>("updated_at"))
|
||||
.map_err(|e| ApiError::InternalError(format!("时间解析错误: {}", e)))?
|
||||
.with_timezone(&Utc),
|
||||
};
|
||||
users.push(user);
|
||||
}
|
||||
Ok((users, total_count))
|
||||
}
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库搜索错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新用户
|
||||
async fn update_user_impl(&self, id: &Uuid, updated_user: User) -> Result<Option<User>, ApiError> {
|
||||
let result = sqlx::query(
|
||||
@@ -233,6 +395,14 @@ impl UserStore for DatabaseUserStore {
|
||||
self.list_users_impl().await
|
||||
}
|
||||
|
||||
async fn list_users_paginated(&self, params: &PaginationParams) -> Result<(Vec<User>, u64), ApiError> {
|
||||
self.list_users_paginated_impl(params).await
|
||||
}
|
||||
|
||||
async fn search_users(&self, search_params: &UserSearchParams, pagination_params: &PaginationParams) -> Result<(Vec<User>, u64), ApiError> {
|
||||
self.search_users_impl(search_params, pagination_params).await
|
||||
}
|
||||
|
||||
async fn update_user(&self, id: &Uuid, updated_user: User) -> Result<Option<User>, ApiError> {
|
||||
self.update_user_impl(id, updated_user).await
|
||||
}
|
||||
|
Reference in New Issue
Block a user