diff --git a/backend/src/main.rs b/backend/src/main.rs index b00063b..1062afc 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -50,6 +50,9 @@ impl AppState { pub async fn trigger_restart(&self) -> CustomResult<()> { *self.restart_progress.lock().await = true; + if let Ok(db) = self.sql_get().await{ + db.get_db().close().await?; + } self.shutdown .lock() .await diff --git a/backend/src/storage/sql/mod.rs b/backend/src/storage/sql/mod.rs index d09ff9c..9b44004 100644 --- a/backend/src/storage/sql/mod.rs +++ b/backend/src/storage/sql/mod.rs @@ -28,7 +28,7 @@ impl std::fmt::Display for DatabaseType { #[async_trait] pub trait DatabaseTrait: Send + Sync { - async fn connect(database: &config::SqlConfig) -> CustomResult + async fn connect(database: &config::SqlConfig,db:bool) -> CustomResult where Self: Sized; async fn execute_query<'a>( @@ -38,6 +38,7 @@ pub trait DatabaseTrait: Send + Sync { async fn initialization(database: config::SqlConfig) -> CustomResult<()> where Self: Sized; + async fn close(&self) -> CustomResult<()>; } #[derive(Clone)] @@ -66,9 +67,9 @@ impl Database { pub async fn link(database: &config::SqlConfig) -> CustomResult { let db: Box = match database.db_type.to_lowercase().as_str() { - "postgresql" => Box::new(postgresql::Postgresql::connect(database).await?), - "mysql" => Box::new(mysql::Mysql::connect(database).await?), - "sqllite" => Box::new(sqllite::Sqlite::connect(database).await?), + "postgresql" => Box::new(postgresql::Postgresql::connect(database,true).await?), + "mysql" => Box::new(mysql::Mysql::connect(database,true).await?), + "sqllite" => Box::new(sqllite::Sqlite::connect(database,true).await?), _ => return Err("unknown database type".into_custom_error()), }; @@ -76,7 +77,7 @@ impl Database { db: Arc::new(db), prefix: Arc::new(database.db_prefix.clone()), db_type: Arc::new(match database.db_type.to_lowercase().as_str() { - "postgresql" => DatabaseType::PostgreSQL, + // "postgresql" => DatabaseType::PostgreSQL, "mysql" => DatabaseType::MySQL, "sqllite" => DatabaseType::SQLite, _ => return Err("unknown database type".into_custom_error()), diff --git a/backend/src/storage/sql/mysql.rs b/backend/src/storage/sql/mysql.rs index b6087bb..419105f 100644 --- a/backend/src/storage/sql/mysql.rs +++ b/backend/src/storage/sql/mysql.rs @@ -2,7 +2,7 @@ use super::{ builder::{self, SafeValue}, schema, DatabaseTrait, }; -use crate::common::error::CustomResult; +use crate::common::error::{CustomResult,CustomErrorInto}; use crate::config; use async_trait::async_trait; use serde_json::Value; @@ -17,13 +17,34 @@ pub struct Mysql { #[async_trait] impl DatabaseTrait for Mysql { - async fn connect(db_config: &config::SqlConfig) -> CustomResult { - let connection_str = format!( - "mysql://{}:{}@{}:{}/{}", - db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name - ); + async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult { + let connection_str; + if db { + connection_str = format!( + "mysql://{}:{}@{}:{}/{}", + db_config.user, + db_config.password, + db_config.host, + db_config.port, + db_config.db_name + ); + } else { + connection_str = format!( + "mysql://{}:{}@{}:{}", + db_config.user, db_config.password, db_config.host, db_config.port + ); + } + + let pool = tokio::time::timeout( + std::time::Duration::from_secs(5), + MySqlPool::connect(&connection_str) + ).await.map_err(|_| "连接超时".into_custom_error())??; + + if let Err(e) = pool.acquire().await{ + pool.close().await; + return Err(format!("数据库连接测试失败: {}", e).into_custom_error()); + } - let pool = MySqlPool::connect(&connection_str).await?; Ok(Mysql { pool }) } @@ -81,12 +102,8 @@ impl DatabaseTrait for Mysql { ); let grammar = schema::generate_schema(super::DatabaseType::MySQL, db_prefix)?; - let connection_str = format!( - "mysql://{}:{}@{}:{}", - db_config.user, db_config.password, db_config.host, db_config.port - ); - let pool = MySqlPool::connect(&connection_str).await?; + let pool = Self::connect(&db_config, false).await?.pool; pool.execute(format!("CREATE DATABASE `{}`", db_config.db_name).as_str()) .await?; @@ -99,13 +116,17 @@ impl DatabaseTrait for Mysql { ) .await?; - let new_connection_str = format!( - "mysql://{}:{}@{}:{}/{}", - db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name - ); - let new_pool = MySqlPool::connect(&new_connection_str).await?; + let new_pool = Self::connect(&db_config, true).await?.pool; new_pool.execute(grammar.as_str()).await?; + new_pool.close(); + Ok(()) + } + async fn close(&self) -> CustomResult<()> { + self.pool.close().await; + while !self.pool.is_closed() { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } Ok(()) } } diff --git a/backend/src/storage/sql/postgresql.rs b/backend/src/storage/sql/postgresql.rs index 5b96f94..50d5627 100644 --- a/backend/src/storage/sql/postgresql.rs +++ b/backend/src/storage/sql/postgresql.rs @@ -2,7 +2,7 @@ use super::{ builder::{self, SafeValue}, schema, DatabaseTrait, }; -use crate::common::error::CustomResult; +use crate::common::error::{CustomResult,CustomErrorInto}; use crate::config; use async_trait::async_trait; use serde_json::Value; @@ -16,13 +16,34 @@ pub struct Postgresql { #[async_trait] impl DatabaseTrait for Postgresql { - async fn connect(db_config: &config::SqlConfig) -> CustomResult { - let connection_str = format!( - "postgres://{}:{}@{}:{}/{}", - db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name - ); + async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult { + let connection_str; + if db { + connection_str = format!( + "postgres://{}:{}@{}:{}/{}", + db_config.user, + db_config.password, + db_config.host, + db_config.port, + db_config.db_name + ); + } else { + connection_str = format!( + "postgres://{}:{}@{}:{}", + db_config.user, db_config.password, db_config.host, db_config.port + ); + } - let pool = PgPool::connect(&connection_str).await?; + + let pool = tokio::time::timeout( + std::time::Duration::from_secs(5), + PgPool::connect(&connection_str) + ).await.map_err(|_| "连接超时".into_custom_error())??; + + if let Err(e) = pool.acquire().await{ + pool.close().await; + return Err(format!("数据库连接测试失败: {}", e).into_custom_error()); + } Ok(Postgresql { pool }) } @@ -81,23 +102,24 @@ impl DatabaseTrait for Postgresql { ); let grammar = schema::generate_schema(super::DatabaseType::PostgreSQL, db_prefix)?; - let connection_str = format!( - "postgres://{}:{}@{}:{}", - db_config.user, db_config.password, db_config.host, db_config.port - ); - let pool = PgPool::connect(&connection_str).await?; + let pool = Self::connect(&db_config, false).await?.pool; pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str()) .await?; - let new_connection_str = format!( - "postgres://{}:{}@{}:{}/{}", - db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name - ); - let new_pool = PgPool::connect(&new_connection_str).await?; + let new_pool = Self::connect(&db_config, true).await?.pool; new_pool.execute(grammar.as_str()).await?; + new_pool.close().await; Ok(()) } + + async fn close(&self) -> CustomResult<()> { + self.pool.close().await; + while !self.pool.is_closed() { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + Ok(()) + } } diff --git a/backend/src/storage/sql/sqllite.rs b/backend/src/storage/sql/sqllite.rs index 4d06e5d..49d1778 100644 --- a/backend/src/storage/sql/sqllite.rs +++ b/backend/src/storage/sql/sqllite.rs @@ -17,7 +17,7 @@ pub struct Sqlite { #[async_trait] impl DatabaseTrait for Sqlite { - async fn connect(db_config: &config::SqlConfig) -> CustomResult { + async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult { let db_file = env::current_dir()? .join("assets") .join("sqllite") @@ -31,7 +31,17 @@ impl DatabaseTrait for Sqlite { .to_str() .ok_or("无法获取SQLite路径".into_custom_error())?; let connection_str = format!("sqlite:///{}", path); - let pool = SqlitePool::connect(&connection_str).await?; + + let pool = tokio::time::timeout( + std::time::Duration::from_secs(5), + SqlitePool::connect(&connection_str) + ).await.map_err(|_| "连接超时".into_custom_error())??; + + if let Err(e) = pool.acquire().await{ + pool.close().await; + return Err(format!("数据库连接测试失败: {}", e).into_custom_error()); + } + Ok(Sqlite { pool }) } @@ -95,18 +105,20 @@ impl DatabaseTrait for Sqlite { let db_file = sqlite_dir.join(&db_config.db_name); std::fs::File::create(&db_file)?; - let path = db_file - .to_str() - .ok_or("Unable to get sqllite path".into_custom_error())?; let grammar = schema::generate_schema(super::DatabaseType::SQLite, db_prefix)?; - println!("\n{}\n", grammar); - - let connection_str = format!("sqlite:///{}", path); - let pool = SqlitePool::connect(&connection_str).await?; + let pool = Self::connect(&db_config, false).await?.pool; pool.execute(grammar.as_str()).await?; + pool.close(); Ok(()) } + async fn close(&self) -> CustomResult<()> { + self.pool.close().await; + while !self.pool.is_closed() { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + Ok(()) + } } diff --git a/frontend/app/index.css b/frontend/app/index.css index e825148..bd6213e 100644 --- a/frontend/app/index.css +++ b/frontend/app/index.css @@ -1,44 +1,3 @@ @tailwind base; @tailwind components; -@tailwind utilities; - -@layer utilities { - .animate-slideIn { - animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); - } - - .animate-hide { - animation: hide 100ms ease-in; - } - - .animate-swipeOut { - animation: swipeOut 100ms ease-out; - } -} - -@keyframes hide { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -@keyframes slideIn { - from { - transform: translateX(calc(100% + 1rem)); - } - to { - transform: translateX(0); - } -} - -@keyframes swipeOut { - from { - transform: translateX(var(--radix-toast-swipe-end-x)); - } - to { - transform: translateX(calc(100% + 1rem)); - } -} \ No newline at end of file +@tailwind utilities; \ No newline at end of file diff --git a/frontend/app/init.tsx b/frontend/app/init.tsx index 939e894..afed4bd 100644 --- a/frontend/app/init.tsx +++ b/frontend/app/init.tsx @@ -180,7 +180,7 @@ const DatabaseConfig: React.FC = ({ onNext }) => { setTimeout(() => onNext(), 1000); } catch (error: any) { console.error( error); - message.error(error.message ); + message.error(error.message,error.title); } finally { setLoading(false); } @@ -369,7 +369,7 @@ const AdminConfig: React.FC = ({ onNext }) => { onNext(); } catch (error: any) { console.error(error); - message.error(error.message); + message.error(error.message,error.title); } finally { setLoading(false); } diff --git a/frontend/core/http.ts b/frontend/core/http.ts index 088bcd3..e99a099 100644 --- a/frontend/core/http.ts +++ b/frontend/core/http.ts @@ -36,22 +36,29 @@ export class HttpClient { private async handleResponse(response: Response): Promise { if (!response.ok) { const contentType = response.headers.get("content-type"); - let message = `${response.statusText}`; + let message; try { if (contentType?.includes("application/json")) { const error = await response.json(); - message = error.message || message; + message = error.message || ""; } else { const textError = await response.text(); - message = (response.status != 404 && textError) || message; + message = textError || ""; } } catch (e) { console.error("解析响应错误:", e); } + switch (response.status){ + case 404: + message="请求的资源不存在"; + break + } + + const errorResponse: ErrorResponse = { - title: this.getErrorMessage(response.status), + title: `${response.status} ${response.statusText}`, message: message }; @@ -64,21 +71,6 @@ export class HttpClient { : response.text(); } - private getErrorMessage(status: number): string { - const messages: Record = { - 0: "网络连接失败", - 400: "请求错误", - 401: "未授权访问", - 403: "禁止访问", - 404: "资源不存在", - 405: "方法不允许", - 500: "服务器错误", - 502: "网关错误", - 503: "服务不可用", - 504: "网关超时", - }; - return messages[status] || `请求失败`; - } private async request( endpoint: string, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index f8c3e4d..9d87dd3 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -9,7 +9,7 @@ ], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["@remix-run/node", "vite/client", "@radix-ui/react-toast"], + "types": ["@remix-run/node", "vite/client"], "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", diff --git a/package.json b/package.json deleted file mode 100644 index 56475d5..0000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "@radix-ui/react-toast": "^1.2.2" - } -}