后端:优化数据库连接确保连接失败及时清理资源,增加数据库关闭方法以确保资源释放;前端:优化http请求错误信息
This commit is contained in:
parent
610b3b5422
commit
6440c0a719
@ -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
|
||||
|
@ -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()),
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@radix-ui/react-toast": "^1.2.2"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user