Stage 8: 完成 SQLite 数据库存储集成
✨ 新功能: - 实现 DatabaseUserStore 结构体,支持 SQLite 数据库操作 - 创建 UserStore trait 抽象层,支持多种存储后端 - 添加数据库连接和表初始化功能 - 支持通过环境变量配置数据库 URL 🔧 技术改进: - 使用 SQLx 进行异步数据库操作 - 实现 trait-based 存储架构,便于扩展 - 添加完整的 CRUD 数据库操作 - 支持数据库错误处理和类型转换 🧪 测试验证: - 所有 API 端点正常工作 - 数据持久化到 SQLite 文件 - 用户创建、查询、登录功能完整 - 重复用户名检查正常工作 📁 文件变更: - 新增: src/storage/database.rs - SQLite 存储实现 - 新增: src/storage/traits.rs - 存储抽象 trait - 更新: src/storage/mod.rs - 导出新模块 - 更新: src/routes/mod.rs - 支持 trait 对象 - 更新: src/handlers/user.rs - 使用 trait 抽象 - 更新: src/main.rs - 数据库初始化逻辑 - 更新: Cargo.toml - 添加 SQLx 依赖 - 新增: .env - 数据库配置文件 🎯 学习目标达成: - 掌握 Rust 数据库集成 - 理解 trait 抽象设计模式 - 学习异步数据库操作 - 实践配置管理
This commit is contained in:
243
src/storage/database.rs
Normal file
243
src/storage/database.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
//! SQLite 数据库存储实现
|
||||
|
||||
use async_trait::async_trait;
|
||||
use sqlx::{SqlitePool, Row};
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::models::user::User;
|
||||
use crate::utils::errors::ApiError;
|
||||
use crate::storage::UserStore;
|
||||
|
||||
/// SQLite 用户存储
|
||||
#[derive(Clone)]
|
||||
pub struct DatabaseUserStore {
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl DatabaseUserStore {
|
||||
/// 创建新的数据库存储实例
|
||||
pub fn new(pool: SqlitePool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
/// 从数据库 URL 创建新的数据库存储实例
|
||||
pub async fn from_url(database_url: &str) -> Result<Self, ApiError> {
|
||||
let pool = SqlitePool::connect(database_url)
|
||||
.await
|
||||
.map_err(|e| ApiError::InternalError(format!("无法连接到数据库: {}", e)))?;
|
||||
|
||||
let store = Self::new(pool);
|
||||
store.init_tables().await?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// 初始化数据库表
|
||||
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)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建用户
|
||||
async fn create_user_impl(&self, user: User) -> Result<User, ApiError> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO users (id, username, email, password_hash, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(user.id.to_string())
|
||||
.bind(&user.username)
|
||||
.bind(&user.email)
|
||||
.bind(&user.password_hash)
|
||||
.bind(user.created_at.to_rfc3339())
|
||||
.bind(user.updated_at.to_rfc3339())
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(user),
|
||||
Err(sqlx::Error::Database(db_err)) if db_err.is_unique_violation() => {
|
||||
Err(ApiError::Conflict("用户名已存在".to_string()))
|
||||
}
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据 ID 获取用户
|
||||
async fn get_user_impl(&self, id: &Uuid) -> Result<Option<User>, ApiError> {
|
||||
let result = sqlx::query(
|
||||
"SELECT id, username, email, password_hash, created_at, updated_at FROM users WHERE id = ?"
|
||||
)
|
||||
.bind(id.to_string())
|
||||
.fetch_optional(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some(row)) => {
|
||||
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),
|
||||
};
|
||||
Ok(Some(user))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据用户名获取用户
|
||||
async fn get_user_by_username_impl(&self, username: &str) -> Result<Option<User>, ApiError> {
|
||||
let result = sqlx::query(
|
||||
"SELECT id, username, email, password_hash, created_at, updated_at FROM users WHERE username = ?"
|
||||
)
|
||||
.bind(username)
|
||||
.fetch_optional(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some(row)) => {
|
||||
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),
|
||||
};
|
||||
Ok(Some(user))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有用户
|
||||
async fn list_users_impl(&self) -> Result<Vec<User>, ApiError> {
|
||||
let result = sqlx::query(
|
||||
"SELECT id, username, email, password_hash, created_at, updated_at FROM users ORDER BY created_at DESC"
|
||||
)
|
||||
.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)
|
||||
}
|
||||
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(
|
||||
r#"
|
||||
UPDATE users
|
||||
SET username = ?, email = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(&updated_user.username)
|
||||
.bind(&updated_user.email)
|
||||
.bind(updated_user.updated_at.to_rfc3339())
|
||||
.bind(id.to_string())
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(query_result) => {
|
||||
if query_result.rows_affected() > 0 {
|
||||
Ok(Some(updated_user))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除用户
|
||||
async fn delete_user_impl(&self, id: &Uuid) -> Result<bool, ApiError> {
|
||||
let result = sqlx::query("DELETE FROM users WHERE id = ?")
|
||||
.bind(id.to_string())
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(query_result) => Ok(query_result.rows_affected() > 0),
|
||||
Err(e) => Err(ApiError::InternalError(format!("数据库错误: {}", e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserStore for DatabaseUserStore {
|
||||
async fn create_user(&self, user: User) -> Result<User, ApiError> {
|
||||
self.create_user_impl(user).await
|
||||
}
|
||||
|
||||
async fn get_user(&self, id: &Uuid) -> Result<Option<User>, ApiError> {
|
||||
self.get_user_impl(id).await
|
||||
}
|
||||
|
||||
async fn get_user_by_username(&self, username: &str) -> Result<Option<User>, ApiError> {
|
||||
self.get_user_by_username_impl(username).await
|
||||
}
|
||||
|
||||
async fn list_users(&self) -> Result<Vec<User>, ApiError> {
|
||||
self.list_users_impl().await
|
||||
}
|
||||
|
||||
async fn update_user(&self, id: &Uuid, updated_user: User) -> Result<Option<User>, ApiError> {
|
||||
self.update_user_impl(id, updated_user).await
|
||||
}
|
||||
|
||||
async fn delete_user(&self, id: &Uuid) -> Result<bool, ApiError> {
|
||||
self.delete_user_impl(id).await
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user