后端:优化数据库连接确保连接失败及时清理资源,增加数据库关闭方法以确保资源释放;前端:优化http请求错误信息
This commit is contained in:
parent
610b3b5422
commit
6440c0a719
@ -50,6 +50,9 @@ impl AppState {
|
|||||||
|
|
||||||
pub async fn trigger_restart(&self) -> CustomResult<()> {
|
pub async fn trigger_restart(&self) -> CustomResult<()> {
|
||||||
*self.restart_progress.lock().await = true;
|
*self.restart_progress.lock().await = true;
|
||||||
|
if let Ok(db) = self.sql_get().await{
|
||||||
|
db.get_db().close().await?;
|
||||||
|
}
|
||||||
self.shutdown
|
self.shutdown
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
|
@ -28,7 +28,7 @@ impl std::fmt::Display for DatabaseType {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait DatabaseTrait: Send + Sync {
|
pub trait DatabaseTrait: Send + Sync {
|
||||||
async fn connect(database: &config::SqlConfig) -> CustomResult<Self>
|
async fn connect(database: &config::SqlConfig,db:bool) -> CustomResult<Self>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
async fn execute_query<'a>(
|
async fn execute_query<'a>(
|
||||||
@ -38,6 +38,7 @@ pub trait DatabaseTrait: Send + Sync {
|
|||||||
async fn initialization(database: config::SqlConfig) -> CustomResult<()>
|
async fn initialization(database: config::SqlConfig) -> CustomResult<()>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
async fn close(&self) -> CustomResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -66,9 +67,9 @@ impl Database {
|
|||||||
|
|
||||||
pub async fn link(database: &config::SqlConfig) -> CustomResult<Self> {
|
pub async fn link(database: &config::SqlConfig) -> CustomResult<Self> {
|
||||||
let db: Box<dyn DatabaseTrait> = match database.db_type.to_lowercase().as_str() {
|
let db: Box<dyn DatabaseTrait> = match database.db_type.to_lowercase().as_str() {
|
||||||
"postgresql" => Box::new(postgresql::Postgresql::connect(database).await?),
|
"postgresql" => Box::new(postgresql::Postgresql::connect(database,true).await?),
|
||||||
"mysql" => Box::new(mysql::Mysql::connect(database).await?),
|
"mysql" => Box::new(mysql::Mysql::connect(database,true).await?),
|
||||||
"sqllite" => Box::new(sqllite::Sqlite::connect(database).await?),
|
"sqllite" => Box::new(sqllite::Sqlite::connect(database,true).await?),
|
||||||
_ => return Err("unknown database type".into_custom_error()),
|
_ => return Err("unknown database type".into_custom_error()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ impl Database {
|
|||||||
db: Arc::new(db),
|
db: Arc::new(db),
|
||||||
prefix: Arc::new(database.db_prefix.clone()),
|
prefix: Arc::new(database.db_prefix.clone()),
|
||||||
db_type: Arc::new(match database.db_type.to_lowercase().as_str() {
|
db_type: Arc::new(match database.db_type.to_lowercase().as_str() {
|
||||||
"postgresql" => DatabaseType::PostgreSQL,
|
// "postgresql" => DatabaseType::PostgreSQL,
|
||||||
"mysql" => DatabaseType::MySQL,
|
"mysql" => DatabaseType::MySQL,
|
||||||
"sqllite" => DatabaseType::SQLite,
|
"sqllite" => DatabaseType::SQLite,
|
||||||
_ => return Err("unknown database type".into_custom_error()),
|
_ => return Err("unknown database type".into_custom_error()),
|
||||||
|
@ -2,7 +2,7 @@ use super::{
|
|||||||
builder::{self, SafeValue},
|
builder::{self, SafeValue},
|
||||||
schema, DatabaseTrait,
|
schema, DatabaseTrait,
|
||||||
};
|
};
|
||||||
use crate::common::error::CustomResult;
|
use crate::common::error::{CustomResult,CustomErrorInto};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@ -17,13 +17,34 @@ pub struct Mysql {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DatabaseTrait for Mysql {
|
impl DatabaseTrait for Mysql {
|
||||||
async fn connect(db_config: &config::SqlConfig) -> CustomResult<Self> {
|
async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult<Self> {
|
||||||
let connection_str = format!(
|
let connection_str;
|
||||||
"mysql://{}:{}@{}:{}/{}",
|
if db {
|
||||||
db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name
|
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 })
|
Ok(Mysql { pool })
|
||||||
}
|
}
|
||||||
@ -81,12 +102,8 @@ impl DatabaseTrait for Mysql {
|
|||||||
);
|
);
|
||||||
let grammar = schema::generate_schema(super::DatabaseType::MySQL, db_prefix)?;
|
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())
|
pool.execute(format!("CREATE DATABASE `{}`", db_config.db_name).as_str())
|
||||||
.await?;
|
.await?;
|
||||||
@ -99,13 +116,17 @@ impl DatabaseTrait for Mysql {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let new_connection_str = format!(
|
let new_pool = Self::connect(&db_config, true).await?.pool;
|
||||||
"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?;
|
|
||||||
|
|
||||||
new_pool.execute(grammar.as_str()).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use super::{
|
|||||||
builder::{self, SafeValue},
|
builder::{self, SafeValue},
|
||||||
schema, DatabaseTrait,
|
schema, DatabaseTrait,
|
||||||
};
|
};
|
||||||
use crate::common::error::CustomResult;
|
use crate::common::error::{CustomResult,CustomErrorInto};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@ -16,13 +16,34 @@ pub struct Postgresql {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DatabaseTrait for Postgresql {
|
impl DatabaseTrait for Postgresql {
|
||||||
async fn connect(db_config: &config::SqlConfig) -> CustomResult<Self> {
|
async fn connect(db_config: &config::SqlConfig, db: bool) -> CustomResult<Self> {
|
||||||
let connection_str = format!(
|
let connection_str;
|
||||||
"postgres://{}:{}@{}:{}/{}",
|
if db {
|
||||||
db_config.user, db_config.password, db_config.host, db_config.port, db_config.db_name
|
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 })
|
Ok(Postgresql { pool })
|
||||||
}
|
}
|
||||||
@ -81,23 +102,24 @@ impl DatabaseTrait for Postgresql {
|
|||||||
);
|
);
|
||||||
let grammar = schema::generate_schema(super::DatabaseType::PostgreSQL, db_prefix)?;
|
let grammar = schema::generate_schema(super::DatabaseType::PostgreSQL, db_prefix)?;
|
||||||
|
|
||||||
let connection_str = format!(
|
let pool = Self::connect(&db_config, false).await?.pool;
|
||||||
"postgres://{}:{}@{}:{}",
|
|
||||||
db_config.user, db_config.password, db_config.host, db_config.port
|
|
||||||
);
|
|
||||||
let pool = PgPool::connect(&connection_str).await?;
|
|
||||||
|
|
||||||
pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str())
|
pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let new_connection_str = format!(
|
let new_pool = Self::connect(&db_config, true).await?.pool;
|
||||||
"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?;
|
|
||||||
|
|
||||||
new_pool.execute(grammar.as_str()).await?;
|
new_pool.execute(grammar.as_str()).await?;
|
||||||
|
new_pool.close().await;
|
||||||
|
|
||||||
Ok(())
|
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]
|
#[async_trait]
|
||||||
impl DatabaseTrait for Sqlite {
|
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()?
|
let db_file = env::current_dir()?
|
||||||
.join("assets")
|
.join("assets")
|
||||||
.join("sqllite")
|
.join("sqllite")
|
||||||
@ -31,7 +31,17 @@ impl DatabaseTrait for Sqlite {
|
|||||||
.to_str()
|
.to_str()
|
||||||
.ok_or("无法获取SQLite路径".into_custom_error())?;
|
.ok_or("无法获取SQLite路径".into_custom_error())?;
|
||||||
let connection_str = format!("sqlite:///{}", path);
|
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 })
|
Ok(Sqlite { pool })
|
||||||
}
|
}
|
||||||
@ -95,18 +105,20 @@ impl DatabaseTrait for Sqlite {
|
|||||||
let db_file = sqlite_dir.join(&db_config.db_name);
|
let db_file = sqlite_dir.join(&db_config.db_name);
|
||||||
std::fs::File::create(&db_file)?;
|
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)?;
|
let grammar = schema::generate_schema(super::DatabaseType::SQLite, db_prefix)?;
|
||||||
|
|
||||||
println!("\n{}\n", grammar);
|
let pool = Self::connect(&db_config, false).await?.pool;
|
||||||
|
|
||||||
let connection_str = format!("sqlite:///{}", path);
|
|
||||||
let pool = SqlitePool::connect(&connection_str).await?;
|
|
||||||
|
|
||||||
pool.execute(grammar.as_str()).await?;
|
pool.execute(grammar.as_str()).await?;
|
||||||
|
pool.close();
|
||||||
|
|
||||||
Ok(())
|
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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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);
|
setTimeout(() => onNext(), 1000);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error( error);
|
console.error( error);
|
||||||
message.error(error.message );
|
message.error(error.message,error.title);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -369,7 +369,7 @@ const AdminConfig: React.FC<StepProps> = ({ onNext }) => {
|
|||||||
onNext();
|
onNext();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
message.error(error.message);
|
message.error(error.message,error.title);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -36,22 +36,29 @@ export class HttpClient {
|
|||||||
private async handleResponse(response: Response): Promise<any> {
|
private async handleResponse(response: Response): Promise<any> {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get("content-type");
|
||||||
let message = `${response.statusText}`;
|
let message;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (contentType?.includes("application/json")) {
|
if (contentType?.includes("application/json")) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
message = error.message || message;
|
message = error.message || "";
|
||||||
} else {
|
} else {
|
||||||
const textError = await response.text();
|
const textError = await response.text();
|
||||||
message = (response.status != 404 && textError) || message;
|
message = textError || "";
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("解析响应错误:", e);
|
console.error("解析响应错误:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (response.status){
|
||||||
|
case 404:
|
||||||
|
message="请求的资源不存在";
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const errorResponse: ErrorResponse = {
|
const errorResponse: ErrorResponse = {
|
||||||
title: this.getErrorMessage(response.status),
|
title: `${response.status} ${response.statusText}`,
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,21 +71,6 @@ export class HttpClient {
|
|||||||
: response.text();
|
: 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>(
|
private async request<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
"types": ["@remix-run/node", "vite/client", "@radix-ui/react-toast"],
|
"types": ["@remix-run/node", "vite/client"],
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-toast": "^1.2.2"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user