feat: 实现数据库迁移、搜索和分页功能

- 添加数据库迁移系统和初始用户表迁移
- 实现搜索功能模块和API
- 实现分页功能支持
- 添加相关测试文件
- 更新项目配置和文档
This commit is contained in:
2025-08-05 23:41:40 +08:00
parent c18f345475
commit cf01d557b9
26 changed files with 3578 additions and 27 deletions

View File

@@ -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
}