后端:优化数据库连接确保连接失败及时清理资源,增加数据库关闭方法以确保资源释放;前端:优化http请求错误信息

This commit is contained in:
lsy 2024-12-01 15:45:47 +08:00
parent 610b3b5422
commit 6440c0a719
10 changed files with 122 additions and 117 deletions

View File

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

View File

@ -28,7 +28,7 @@ impl std::fmt::Display for DatabaseType {
#[async_trait]
pub trait DatabaseTrait: Send + Sync {
async fn connect(database: &config::SqlConfig) -> CustomResult<Self>
async fn connect(database: &config::SqlConfig,db:bool) -> CustomResult<Self>
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<Self> {
let db: Box<dyn DatabaseTrait> = 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()),

View File

@ -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<Self> {
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<Self> {
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(())
}
}

View File

@ -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<Self> {
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<Self> {
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(())
}
}

View File

@ -17,7 +17,7 @@ pub struct Sqlite {
#[async_trait]
impl DatabaseTrait for Sqlite {
async fn connect(db_config: &config::SqlConfig) -> CustomResult<Self> {
async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult<Self> {
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(())
}
}

View File

@ -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));
}
}
@tailwind utilities;

View File

@ -180,7 +180,7 @@ const DatabaseConfig: React.FC<StepProps> = ({ 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<StepProps> = ({ onNext }) => {
onNext();
} catch (error: any) {
console.error(error);
message.error(error.message);
message.error(error.message,error.title);
} finally {
setLoading(false);
}

View File

@ -36,22 +36,29 @@ export class HttpClient {
private async handleResponse(response: Response): Promise<any> {
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<number, string> = {
0: "网络连接失败",
400: "请求错误",
401: "未授权访问",
403: "禁止访问",
404: "资源不存在",
405: "方法不允许",
500: "服务器错误",
502: "网关错误",
503: "服务不可用",
504: "网关超时",
};
return messages[status] || `请求失败`;
}
private async request<T>(
endpoint: string,

View File

@ -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",

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"@radix-ui/react-toast": "^1.2.2"
}
}