feat: 完善生产级 Rust Web 模板

This commit is contained in:
2026-07-21 16:00:03 +08:00
parent fb30d5da1a
commit ce89569bf3
58 changed files with 2042 additions and 349 deletions
+62 -9
View File
@@ -1,7 +1,7 @@
use crate::config::database::{DatabaseConfig, DatabaseType};
use sea_orm::{
ConnectionTrait, Database, DatabaseConnection, DbBackend, EntityName, EntityTrait, ConnectOptions, Schema,
Statement,
ConnectOptions, ConnectionTrait, Database, DatabaseConnection, DbBackend, EntityName,
EntityTrait, Schema, Statement,
};
use std::time::Duration;
@@ -75,13 +75,48 @@ pub async fn init_database(config: &DatabaseConfig) -> anyhow::Result<DatabaseCo
// 创建表
create_tables(&pool).await?;
migrate_existing_tables(&pool).await?;
create_indexes(&pool).await?;
Ok(pool)
}
async fn create_indexes(db: &DatabaseConnection) -> anyhow::Result<()> {
let backend = db.get_database_backend();
let statements = match backend {
DbBackend::MySql => vec!["CREATE INDEX idx_users_email ON users(email)", "CREATE INDEX idx_email_logs_user_created ON email_logs(user_id, created_at)"],
_ => vec!["CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)", "CREATE INDEX IF NOT EXISTS idx_email_logs_user_created ON email_logs(user_id, created_at)"],
};
for statement in statements {
if let Err(error) = db.execute(Statement::from_string(backend, statement)).await {
let message = error.to_string().to_lowercase();
if !message.contains("duplicate") && !message.contains("already exists") {
return Err(anyhow::anyhow!("创建索引失败: {error}"));
}
}
}
Ok(())
}
async fn migrate_existing_tables(db: &DatabaseConnection) -> anyhow::Result<()> {
let backend = db.get_database_backend();
let statement = match backend {
DbBackend::MySql => "ALTER TABLE users ADD COLUMN deleted_at DATETIME NULL",
DbBackend::Postgres => "ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL",
DbBackend::Sqlite => "ALTER TABLE users ADD COLUMN deleted_at DATETIME NULL",
};
if let Err(error) = db.execute(Statement::from_string(backend, statement)).await {
let message = error.to_string().to_lowercase();
if !message.contains("duplicate") && !message.contains("already exists") {
return Err(anyhow::anyhow!("迁移 users.deleted_at 失败: {error}"));
}
}
Ok(())
}
/// 获取端口号(根据数据库类型返回默认值)
fn get_database_port(config: &DatabaseConfig) -> u16 {
config.port.unwrap_or_else(|| match config.database_type {
config.port.unwrap_or(match config.database_type {
DatabaseType::MySQL => 3306,
DatabaseType::PostgreSQL => 5432,
DatabaseType::SQLite => 0,
@@ -94,6 +129,7 @@ async fn init_mysql_database(config: &DatabaseConfig) -> anyhow::Result<()> {
.database
.as_ref()
.ok_or_else(|| anyhow::anyhow!("MySQL 需要配置 database.database"))?;
validate_database_name(database_name)?;
let host = config
.host
@@ -150,6 +186,7 @@ async fn init_postgresql_database(config: &DatabaseConfig) -> anyhow::Result<()>
.database
.as_ref()
.ok_or_else(|| anyhow::anyhow!("PostgreSQL 需要配置 database.database"))?;
validate_database_name(database_name)?;
let host = config
.host
@@ -190,17 +227,18 @@ async fn init_postgresql_database(config: &DatabaseConfig) -> anyhow::Result<()>
);
let result = conn
.execute(Statement::from_string(
.query_one(Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
check_query,
))
.await;
.await
.map_err(|e| anyhow::anyhow!("检查 PostgreSQL 数据库失败: {e}"))?;
match result {
Ok(_) => {
Some(_) => {
tracing::info!("PostgreSQL 数据库 '{}' 已存在", database_name);
}
Err(_) => {
None => {
// 数据库不存在,创建它
let create_query = format!(
"CREATE DATABASE {} WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8'",
@@ -221,6 +259,17 @@ async fn init_postgresql_database(config: &DatabaseConfig) -> anyhow::Result<()>
Ok(())
}
fn validate_database_name(name: &str) -> anyhow::Result<()> {
if name.is_empty()
|| !name
.bytes()
.all(|value| value.is_ascii_alphanumeric() || value == b'_')
{
anyhow::bail!("数据库名称只能包含字母、数字和下划线");
}
Ok(())
}
/// 为 SQLite 确保数据库文件目录存在
async fn init_sqlite_database(config: &DatabaseConfig) -> anyhow::Result<()> {
let path = config
@@ -299,7 +348,9 @@ where
}
Err(e) => {
let err_msg = e.to_string();
if err_msg.contains("already exists") || (err_msg.contains("table") && err_msg.contains("exists")) {
if err_msg.contains("already exists")
|| (err_msg.contains("table") && err_msg.contains("exists"))
{
tracing::info!("✅ {}已存在", table_name);
} else {
return Err(anyhow::anyhow!("创建{}失败: {}", table_name, e));
@@ -318,10 +369,12 @@ async fn create_tables(db: &DatabaseConnection) -> anyhow::Result<()> {
let schema = Schema::new(builder);
// 导入所有 entities
use crate::domain::entities::users;
use crate::domain::entities::{email_logs, user_profiles, users};
// 创建所有表(添加新表只需一行!)
create_single_table(db, &schema, &builder, users::Entity, "用户表").await?;
create_single_table(db, &schema, &builder, user_profiles::Entity, "用户资料表").await?;
create_single_table(db, &schema, &builder, email_logs::Entity, "邮件日志表").await?;
tracing::info!("✅ 数据库表结构检查完成");