From b1854b4fb8233d34f0c29aeb84b59ac79a84ddbb Mon Sep 17 00:00:00 2001 From: lsy Date: Mon, 25 Nov 2024 17:59:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=9A=E4=BF=AE?= =?UTF-8?q?=E6=94=B9config=E5=AD=97=E6=AE=B5=20=E5=90=8E=E7=AB=AF=EF=BC=9A?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=AE=89=E8=A3=85=E6=97=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E7=B3=BB=E7=BB=9F=E7=9A=84=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=88=96=E8=80=85?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=20=E5=89=8D=E7=AB=AF=EF=BC=9A=E4=B8=BA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=AE=89=E8=A3=85=E7=8A=B6=E6=80=81=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/config.rs | 49 +++++++++++ backend/src/database/relational/builder.rs | 4 +- .../database/relational/postgresql/init.sql | 2 +- backend/src/main.rs | 22 ++--- backend/src/routes/auth/token.rs | 7 +- backend/src/routes/configure.rs | 81 ++++++++++++++++++- backend/src/routes/install.rs | 16 +++- backend/src/routes/mod.rs | 19 ++--- frontend/app/env.d.ts | 1 + 9 files changed, 166 insertions(+), 35 deletions(-) diff --git a/backend/src/config.rs b/backend/src/config.rs index 0523104..20e1915 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -5,16 +5,39 @@ use std::{env, fs}; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Config { + pub address: String, + pub port: u32, pub info: Info, pub sql_config: SqlConfig, } + +impl Default for Config { + fn default() -> Self { + Self { + address: "0.0.0.0".to_string(), + port: 22000, + info: Info::default(), + sql_config: SqlConfig::default(), + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Info { pub install: bool, pub non_relational: bool, } +impl Default for Info { + fn default() -> Self { + Self { + install: false, + non_relational: false, + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct SqlConfig { pub db_type: String, @@ -25,6 +48,19 @@ pub struct SqlConfig { pub db_name: String, } +impl Default for SqlConfig { + fn default() -> Self { + Self { + db_type: "postgresql".to_string(), + address: "localhost".to_string(), + port: 5432, + user: "postgres".to_string(), + password: "postgres".to_string(), + db_name: "echoes".to_string(), + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct NoSqlConfig { pub db_type: String, @@ -35,6 +71,19 @@ pub struct NoSqlConfig { pub db_name: String, } +impl Default for NoSqlConfig { + fn default() -> Self { + Self { + db_type: "postgresql".to_string(), + address: "localhost".to_string(), + port: 5432, + user: "postgres".to_string(), + password: "postgres".to_string(), + db_name: "echoes".to_string(), + } + } +} + impl Config { pub fn read() -> CustomResult { let path = Self::get_path()?; diff --git a/backend/src/database/relational/builder.rs b/backend/src/database/relational/builder.rs index 805cc52..ba5515e 100644 --- a/backend/src/database/relational/builder.rs +++ b/backend/src/database/relational/builder.rs @@ -77,13 +77,12 @@ impl TextValidator { let max_length = self .level_max_lengths .get(&level) - .ok_or_else(|| "Invalid validation level".into_custom_error())?; + .ok_or( "Invalid validation level".into_custom_error())?; if text.len() > *max_length { return Err("Text exceeds maximum length".into_custom_error()); } - // 简化验证逻辑 if level == ValidationLevel::Relaxed { return self.validate_sql_patterns(text); } @@ -514,7 +513,6 @@ impl QueryBuilder { Ok((sql, params)) } - // 添加新的辅助方法 fn build_where_clause_with_index( &self, clause: &WhereClause, diff --git a/backend/src/database/relational/postgresql/init.sql b/backend/src/database/relational/postgresql/init.sql index 43a5201..5b42ad4 100644 --- a/backend/src/database/relational/postgresql/init.sql +++ b/backend/src/database/relational/postgresql/init.sql @@ -80,5 +80,5 @@ CREATE TABLE library CREATE TABLE config ( config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name), - config_config JSON + config_data JSON ); diff --git a/backend/src/main.rs b/backend/src/main.rs index a21adbb..7a18b84 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -12,16 +12,14 @@ use error::{CustomErrorInto, CustomResult}; pub struct AppState { db: Arc>>, - configure: Arc>, shutdown: Arc>>, restart_progress: Arc>, } impl AppState { - pub fn new(config: config::Config) -> Self { + pub fn new() -> Self { Self { db: Arc::new(Mutex::new(None)), - configure: Arc::new(Mutex::new(config)), shutdown: Arc::new(Mutex::new(None)), restart_progress: Arc::new(Mutex::new(false)), } @@ -61,22 +59,24 @@ impl AppState { #[rocket::main] async fn main() -> CustomResult<()> { - let config = config::Config::read()?; + let config = config::Config::read().unwrap_or_default(); - let state = AppState::new(config.clone()); - - if config.info.install { - state.sql_link(&config.sql_config).await?; - } + let rocket_config = rocket::Config::figment() + .merge(("address", config.address.clone())) + .merge(("port", config.port)); + let state = AppState::new(); let state = Arc::new(state); - let rocket_builder = rocket::build().manage(state.clone()); + let rocket_builder = rocket::build().configure(rocket_config).manage(state.clone()); let rocket_builder = if !config.info.install { rocket_builder.mount("/", rocket::routes![routes::install::install]) } else { - rocket_builder.mount("/auth/token", routes::jwt_routes()) + state.sql_link(&config.sql_config).await?; + rocket_builder + .mount("/auth/token", routes::jwt_routes()) + .mount("/config", routes::configure_routes()) }; let rocket = rocket_builder.ignite().await?; diff --git a/backend/src/routes/auth/token.rs b/backend/src/routes/auth/token.rs index 336e3de..aa90dd6 100644 --- a/backend/src/routes/auth/token.rs +++ b/backend/src/routes/auth/token.rs @@ -3,7 +3,6 @@ use crate::database::relational::builder; use crate::error::{AppResult, AppResultInto}; use crate::AppState; use chrono::Duration; -use jwt_compact::Token; use rocket::{ http::Status, post, @@ -91,12 +90,14 @@ pub async fn token_system( String::from("该用户密码丢失"), ))?; - auth::bcrypt::verify_hash(&data.password, password).into_app_result()?; + auth::bcrypt::verify_hash(&data.password, password).map_err(|_| { + status::Custom(Status::Forbidden, String::from("密码错误")) + })?; let claims = auth::jwt::CustomClaims { name: "system".into(), }; - let token = auth::jwt::generate_jwt(claims, Duration::seconds(1)).into_app_result()?; + let token = auth::jwt::generate_jwt(claims, Duration::minutes(1)).into_app_result()?; Ok(token) } diff --git a/backend/src/routes/configure.rs b/backend/src/routes/configure.rs index c358d25..388a5d5 100644 --- a/backend/src/routes/configure.rs +++ b/backend/src/routes/configure.rs @@ -1,10 +1,85 @@ use super::SystemToken; -use crate::error::AppResult; +use crate::database::{relational, relational::builder}; +use crate::error::{AppResult, AppResultInto, CustomResult}; +use crate::AppState; use rocket::{ get, http::Status, - post, response::status, serde::json::{Json, Value}, - Request, + State, }; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::Arc; + +#[derive(Deserialize, Serialize)] +pub struct SystemConfigure { + pub author_name: String, + pub current_theme: String, + pub site_keyword: Vec, + pub site_description: String, + pub admin_path: String, +} + +impl Default for SystemConfigure { + fn default() -> Self { + Self { + author_name: "lsy".to_string(), + current_theme: "default".to_string(), + site_keyword: vec!["echoes".to_string()], + site_description: "echoes是一个高效、可扩展的博客平台".to_string(), + admin_path: "admin".to_string(), + } + } +} + +pub async fn get_configure( + sql: &relational::Database, + comfig_type: String, + name: String, +) -> CustomResult> { + let mut sql_builder = + builder::QueryBuilder::new(builder::SqlOperation::Select, "config".to_string())?; + sql_builder.set_value( + "config_name".to_string(), + builder::SafeValue::Text( + format!("{}_{}", comfig_type, name).to_string(), + builder::ValidationLevel::Strict, + ), + )?; + let result = sql.get_db().execute_query(&sql_builder).await?; + Ok(Json(json!(result))) +} + +pub async fn insert_configure( + sql: &relational::Database, + comfig_type: String, + name: String, + data: Json, +) -> CustomResult<()> { + let mut builder = + builder::QueryBuilder::new(builder::SqlOperation::Insert, "config".to_string())?; + builder.set_value( + "config_name".to_string(), + builder::SafeValue::Text( + format!("{}_{}", comfig_type, name).to_string(), + builder::ValidationLevel::Strict, + ), + )?; + builder.set_value( + "config_data".to_string(), + builder::SafeValue::Json(data.into_inner()), + )?; + sql.get_db().execute_query(&builder).await?; + Ok(()) +} + +#[get("/system")] +pub async fn system_config_get(state: &State>,token: SystemToken) -> AppResult> { + let sql = state.sql_get().await.into_app_result()?; + let configure = get_configure(&sql, "system".to_string(), "configure".to_string()) + .await + .into_app_result()?; + Ok(configure) +} diff --git a/backend/src/routes/install.rs b/backend/src/routes/install.rs index 38f0608..78629bf 100644 --- a/backend/src/routes/install.rs +++ b/backend/src/routes/install.rs @@ -1,13 +1,14 @@ use crate::auth; use crate::database::relational; use crate::error::{AppResult, AppResultInto}; -use crate::routes::person; +use super::{person, configure}; use crate::AppState; use crate::{config, utils}; use chrono::Duration; use rocket::{http::Status, post, response::status, serde::json::Json, State}; use serde::{Deserialize, Serialize}; use std::sync::Arc; +use serde_json::json; #[derive(Deserialize, Serialize)] pub struct InstallData { @@ -28,7 +29,7 @@ pub async fn install( data: Json, state: &State>, ) -> AppResult>> { - let mut config = state.configure.lock().await; + let mut config = config::Config::read().unwrap_or_default(); if config.info.install { return Err(status::Custom( Status::BadRequest, @@ -36,6 +37,9 @@ pub async fn install( )); } + config.info.install = true; + config.sql_config = data.sql_config.clone(); + let data = data.into_inner(); relational::Database::initial_setup(data.sql_config.clone()) @@ -44,7 +48,7 @@ pub async fn install( let _ = auth::jwt::generate_key(); - config.info.install = true; + state.sql_link(&data.sql_config).await.into_app_result()?; let sql = state.sql_get().await.into_app_result()?; @@ -76,6 +80,12 @@ pub async fn install( .await .into_app_result()?; + let mut system_configure = configure::SystemConfigure::default(); + system_configure.author_name = data.name.clone(); + + configure::insert_configure(&sql, "system".to_string(), "configure".to_string(), Json(json!(system_configure))).await.into_app_result()?; + + let token = auth::jwt::generate_jwt( auth::jwt::CustomClaims { name: data.name.clone(), diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 26b62ce..e012625 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -2,6 +2,7 @@ pub mod auth; pub mod configure; pub mod install; pub mod person; +use crate::auth::jwt; use rocket::http::Status; use rocket::request::{FromRequest, Outcome, Request}; use rocket::routes; @@ -30,22 +31,14 @@ pub struct SystemToken(String); #[rocket::async_trait] impl<'r> FromRequest<'r> for SystemToken { type Error = (); - async fn from_request(request: &'r Request<'_>) -> Outcome { let token = request .headers() .get_one("Authorization") .map(|value| value.replace("Bearer ", "")); - - match token { - Some(token) => { - if token == "system" { - Outcome::Success(SystemToken(token)) - } else { - Outcome::Error((Status::Unauthorized, ())) - } - } - None => Outcome::Error((Status::Unauthorized, ())), + match token.and_then(|t| jwt::validate_jwt(&t).ok()) { + Some(claims) if claims.name == "system" => Outcome::Success(SystemToken(claims.name)), + _ => Outcome::Error((Status::Unauthorized, ())), } } } @@ -53,3 +46,7 @@ impl<'r> FromRequest<'r> for SystemToken { pub fn jwt_routes() -> Vec { routes![auth::token::token_system] } + +pub fn configure_routes() -> Vec { + routes![configure::system_config_get] +} diff --git a/frontend/app/env.d.ts b/frontend/app/env.d.ts index 925cbdb..71ad528 100644 --- a/frontend/app/env.d.ts +++ b/frontend/app/env.d.ts @@ -15,6 +15,7 @@ interface ImportMetaEnv { readonly VITE_ASSETS_PATH: string; // 存储静态资源的目录路径 VITE_SYSTEM_USERNAME: string; // 前端账号名称 VITE_SYSTEM_PASSWORD: string; // 前端账号密码 + VITE_SYSTEM_STATUS: boolean; // 系统是否进行安装 } interface ImportMeta {