feat: 完善生产级 Rust Web 模板
This commit is contained in:
@@ -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!("✅ 数据库表结构检查完成");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user