后端数据库接口统一使用数据库配置文件来传输配置信息,给后端代码加上注释,前端正在实现动态加载主题和插件

This commit is contained in:
lsy 2024-11-11 13:45:02 +08:00
parent c2bb2d21d9
commit ddbb770923
19 changed files with 540 additions and 125 deletions

View File

@ -1,22 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Config {
pub info: Info,
pub database: Database,
}
#[derive(Deserialize)]
pub struct Info {
pub install: bool,
}
#[derive(Deserialize)]
pub struct Database {
pub db_type : String,
pub address : String,
pub prot : u32,
pub user : String,
pub password : String,
pub db_name : String,
}

View File

@ -1,3 +1,6 @@
# config/config.toml
# 配置文件
[info] [info]
install = false install = false

34
backend/src/config/mod.rs Normal file
View File

@ -0,0 +1,34 @@
// config/mod.rs
/*
*/
use std::fs;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Config {
pub info: Info,
pub database: Database,
}
#[derive(Deserialize)]
pub struct Info {
pub install: bool,
}
#[derive(Deserialize)]
pub struct Database {
pub db_type: String,
pub address: String,
pub prot: u32,
pub user: String,
pub password: String,
pub db_name: String,
}
impl Config {
/// 读取配置文件
pub fn read(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
Ok(toml::from_str(&fs::read_to_string(path)?)?)
}
}

View File

@ -1,68 +1,66 @@
// main.rs // main.rs
mod sql;
mod config; mod config;
use rocket::{ get, launch, routes}; mod sql;
use rocket::serde::json::Json; // Added import for Json
use once_cell::sync::Lazy;
use rocket::http::Status;
use rocket::response::status;
use std::sync::Arc; // Added import for Arc and Mutex
use tokio::sync::Mutex;
use crate::sql::Database; use crate::sql::Database;
use once_cell::sync::Lazy;
use rocket::{get, http::Status, launch, response::status, routes, serde::json::Json};
use std::sync::Arc;
use tokio::sync::Mutex;
// 修改全局变量的类型定义 /* 修改全局变量的类型定义 */
static GLOBAL_SQL: Lazy<Arc<Mutex<Option<Database>>>> = Lazy::new(|| { static DB: Lazy<Arc<Mutex<Option<Database>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
Arc::new(Mutex::new(None))
});
// 修改数据库连接函数 /* 数据库连接函数 */
async fn connect_database() -> Result<(), Box<dyn std::error::Error>> { async fn init_db(database: config::Database) -> Result<(), Box<dyn std::error::Error>> {
let database = sql::Database::init().await?; let database = Database::init(database).await?;
let mut lock = GLOBAL_SQL.lock().await; *DB.lock().await = Some(database);
*lock = Some(database);
Ok(()) Ok(())
} }
/* 获取数据库的引用 */
async fn get_db() -> Result<Database, Box<dyn std::error::Error>> { async fn get_db() -> Result<Database, Box<dyn std::error::Error>> {
let lock = GLOBAL_SQL.lock().await; DB.lock()
match &*lock { .await
Some(db) => Ok(db.clone()), .clone()
None => Err("Database not initialized".into()) .ok_or_else(|| "Database not initialized".into())
}
} }
/* 用于测试数据库 */
#[get("/sql")] #[get("/sql")]
async fn ssql() -> Result<Json<Vec<std::collections::HashMap<String, String>>>, status::Custom<String>> { async fn ssql(
) -> Result<Json<Vec<std::collections::HashMap<String, String>>>, status::Custom<String>> {
let db = get_db().await.map_err(|e| { let db = get_db().await.map_err(|e| {
eprintln!("Database error: {}", e); status::Custom(
status::Custom(Status::InternalServerError, format!("Database error: {}", e)) Status::InternalServerError,
format!("Database error: {}", e),
)
})?; })?;
let query_result = db.get_db() let query_result = db
.query("SELECT * FROM info".to_string()) // 确保这里是正确的表名 .get_db()
.query("SELECT * FROM info".to_string())
.await .await
.map_err(|e| { .map_err(|e| status::Custom(Status::InternalServerError, format!("Query error: {}", e)))?;
eprintln!("Query error: {}", e);
status::Custom(Status::InternalServerError, format!("Query error: {}", e))
})?;
Ok(Json(query_result)) Ok(Json(query_result))
} }
/* 安装接口 */
#[get("/install")] #[get("/install")]
async fn install() -> status::Custom<String> { async fn install() -> status::Custom<String> {
match connect_database().await { get_db()
Ok(_) => status::Custom(Status::Ok, "Database connected successfully".to_string()), .await
Err(e) => status::Custom(Status::InternalServerError, format!("Failed to connect: {}", e)) .map(|_| status::Custom(Status::Ok, "Database connected successfully".into()))
} .unwrap_or_else(|e| {
status::Custom(
Status::InternalServerError,
format!("Failed to connect: {}", e),
)
})
} }
/* 启动函数 */
#[launch] #[launch]
async fn rocket() -> _ { async fn rocket() -> _ {
connect_database().await.expect("Failed to connect to database"); let config = config::Config::read("./src/config/config.toml").expect("Failed to read config");
rocket::build() init_db(config.database)
.mount("/api", routes![install,ssql]) .await
} .expect("Failed to connect to database");
rocket::build().mount("/api", routes![install, ssql])
}

View File

@ -1,57 +1,42 @@
// mod.rs // sql/mod.rs
/*
*/
mod postgresql; mod postgresql;
use std::{collections::HashMap, fs}; use std::collections::HashMap;
use toml; use super::config;
use crate::config::Config;
use async_trait::async_trait; use async_trait::async_trait;
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
#[async_trait] #[async_trait]
pub trait Databasetrait: Send + Sync { pub trait DatabaseTrait: Send + Sync {
async fn connect( // 连接数据库
address: String, async fn connect(database: config::Database) -> Result<Self, Box<dyn Error>> where Self: Sized;
port: u32, // 执行查询
user: String,
password: String,
dbname: String,
) -> Result<Self, Box<dyn Error>> where Self: Sized;
async fn query<'a>(&'a self, query: String) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>; async fn query<'a>(&'a self, query: String) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>;
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {
pub db: Arc<Box<dyn Databasetrait>>, // 数据库实例
pub db: Arc<Box<dyn DatabaseTrait>>,
} }
impl Database { impl Database {
pub fn get_db(&self) -> &Box<dyn Databasetrait> { // 获取当前数据库实例
pub fn get_db(&self) -> &Box<dyn DatabaseTrait> {
&self.db &self.db
} }
}
// 初始化数据库
pub async fn init(database: config::Database) -> Result<Self, Box<dyn Error>> {
let db = match database.db_type.as_str() {
"postgresql" => postgresql::Postgresql::connect(database).await?,
_ => return Err("unknown database type".into()),
};
Ok(Self { db: Arc::new(Box::new(db)) })
impl Database {
pub async fn init() -> Result<Database, Box<dyn Error>> {
let config_string = fs::read_to_string("./src/config.toml")
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
let config: Config = toml::from_str(&config_string)
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
match config.database.db_type.as_str() {
"postgresql" => {
let db = postgresql::Postgresql::connect(
config.database.address,
config.database.prot,
config.database.user,
config.database.password,
config.database.db_name,
).await?;
Ok(Database {
db: Arc::new(Box::new(db))
})
}
_ => Err(anyhow::anyhow!("unknown database type").into()),
}
} }
} }

View File

@ -1,27 +1,28 @@
// sql/psotgresql.rs
/*
postgresql数据库实现具体的方法
*/
use super::DatabaseTrait;
use crate::config;
use async_trait::async_trait; use async_trait::async_trait;
use sqlx::{PgPool, Row,Column}; use sqlx::{Column, PgPool, Row};
use std::{collections::HashMap, error::Error}; use std::{collections::HashMap, error::Error};
use super::Databasetrait;
#[derive(Clone)] #[derive(Clone)]
pub struct Postgresql { pub struct Postgresql {
pool: PgPool, pool: PgPool,
} }
#[async_trait] #[async_trait]
impl Databasetrait for Postgresql { impl DatabaseTrait for Postgresql {
async fn connect( async fn connect(database: config::Database) -> Result<Self, Box<dyn Error>> {
address: String,
port: u32,
user: String,
password: String,
dbname: String,
) -> Result<Self, Box<dyn Error>> {
let connection_str = format!( let connection_str = format!(
"postgres://{}:{}@{}:{}/{}", "postgres://{}:{}@{}:{}/{}",
user, password, address, port, dbname database.user,
database.password,
database.address,
database.prot,
database.db_name
); );
let pool = PgPool::connect(&connection_str) let pool = PgPool::connect(&connection_str)
@ -30,7 +31,10 @@ impl Databasetrait for Postgresql {
Ok(Postgresql { pool }) Ok(Postgresql { pool })
} }
async fn query<'a>(&'a self, query: String) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> { async fn query<'a>(
&'a self,
query: String,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> {
let rows = sqlx::query(&query) let rows = sqlx::query(&query)
.fetch_all(&self.pool) .fetch_all(&self.pool)
.await .await
@ -49,4 +53,4 @@ impl Databasetrait for Postgresql {
Ok(results) Ok(results)
} }
} }

View File

@ -0,0 +1,61 @@
// hooks/usePluginLoader.ts
import { useState, useEffect } from 'react';
import { DependencyChecker } from '../../services/dependency-checker';
import { PerformanceMonitor } from '../../services/performance-monitor';
import { CacheManager } from '../../services/cache-manager';
/**
* Hook
* @param pluginId ID
* @returns
*/
export function usePluginLoader(pluginId: string) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadPlugin = async () => {
try {
// 检查缓存
const cached = CacheManager.get(`plugin:${pluginId}`);
if (cached) {
return cached;
}
// 开始性能监控
const monitor = PerformanceMonitor.startMonitoring(pluginId);
// 获取插件配置
const plugin = await fetch(`/api/admin/plugins/${pluginId}`).then(r => r.json());
// 检查依赖
if (plugin.dependencies) {
const dependenciesOk = await DependencyChecker.checkDependencies(plugin.dependencies);
if (!dependenciesOk) {
throw new Error('依赖检查失败');
}
}
// 加载插件
const component = await import(/* @vite-ignore */plugin.entry);
// 缓存结果
CacheManager.set(`plugin:${pluginId}`, component);
// 结束监控
monitor.end();
return component;
} catch (err) {
setError(err instanceof Error ? err.message : '插件加载失败');
throw err;
} finally {
setLoading(false);
}
};
loadPlugin();
}, [pluginId]);
return { loading, error };
}

View File

@ -0,0 +1,33 @@
// components/theme/themeManager.tsx
import React, { useEffect } from 'react';
import { useThemeLoader } from './useThemeLoader';
interface ThemeManagerProps {
themeName: string;
children: React.ReactNode;
}
export const ThemeManager: React.FC<ThemeManagerProps> = ({
themeName,
children
}) => {
const { theme, loading, error } = useThemeLoader(themeName);
if (loading) {
return <div>...</div>;
}
if (error) {
return <div>: {error}</div>;
}
if (!theme) {
return <div></div>;
}
return (
<div className={`theme-${themeName}`}>
{children}
</div>
);
};

View File

@ -0,0 +1,27 @@
// components/theme/themeSwitch.tsx
import React from 'react';
interface ThemeSwitchProps {
currentTheme: string;
availableThemes: string[];
onThemeChange: (themeName: string) => void;
}
export const ThemeSwitch: React.FC<ThemeSwitchProps> = ({
currentTheme,
availableThemes,
onThemeChange
}) => {
return (
<select
value={currentTheme}
onChange={(e) => onThemeChange(e.target.value)}
>
{availableThemes.map(theme => (
<option key={theme} value={theme}>
{theme}
</option>
))}
</select>
);
};

View File

@ -0,0 +1,66 @@
// hooks/theme/useThemeLoader.ts
import { useState, useEffect } from 'react';
import { ThemeConfig } from '../../types/theme';
import { DependencyChecker } from '../../services/dependency-checker';
import { PerformanceMonitor } from '../../services/performance-monitor';
import { CacheManager } from '../../services/cache-manager';
/**
* Hook
*/
export function useThemeLoader(themeName: string) {
const [theme, setTheme] = useState<ThemeConfig | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadTheme = async () => {
const monitor = PerformanceMonitor.startMonitoring(`theme:${themeName}`);
try {
// 1. 检查缓存
const cached = CacheManager.get(`theme:${themeName}`);
if (cached) {
setTheme(cached);
setLoading(false);
return;
}
// 2. 获取主题配置
const response = await fetch(`/api/themes/${themeName}`);
const themeConfig: ThemeConfig = await response.json();
// 3. 检查依赖
if (themeConfig.dependencies) {
const dependenciesOk = await DependencyChecker.checkDependencies(
themeConfig.dependencies
);
if (!dependenciesOk) {
throw new Error('主题依赖检查失败');
}
}
// 4. 加载主题样式
if (themeConfig.globalStyles) {
const styleElement = document.createElement('style');
styleElement.innerHTML = themeConfig.globalStyles;
document.head.appendChild(styleElement);
}
// 5. 缓存主题配置
CacheManager.set(`theme:${themeName}`, themeConfig, 3600000); // 1小时缓存
setTheme(themeConfig);
} catch (err) {
setError(err instanceof Error ? err.message : '加载主题失败');
} finally {
setLoading(false);
monitor.end();
}
};
loadTheme();
}, [themeName]);
return { theme, loading, error };
}

View File

@ -0,0 +1,41 @@
// services/cache-manager.ts
/**
*
*/
export class CacheManager {
private static cache = new Map<string, any>();
private static ttl = new Map<string, number>();
/**
*
* @param key
* @param value
* @param expiresIn
*/
static set(key: string, value: any, expiresIn: number = 3600000) {
this.cache.set(key, value);
this.ttl.set(key, Date.now() + expiresIn);
}
/**
*
* @param key
* @returns null
*/
static get(key: string): any {
if (this.ttl.has(key) && Date.now() > (this.ttl.get(key) || 0)) {
this.cache.delete(key);
this.ttl.delete(key);
return null;
}
return this.cache.get(key);
}
/**
*
*/
static clear() {
this.cache.clear();
this.ttl.clear();
}
}

View File

@ -0,0 +1,29 @@
// services/dependency-checker.ts
import { Dependency } from "../types/common";
/**
*
*/
export class DependencyChecker {
/**
*
* @param dependencies
* @returns
*/
static async checkDependencies(dependencies: Dependency[]): Promise<boolean> {
for (const dep of dependencies) {
const response = await fetch(`/api/dependencies/check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dep)
});
if (!response.ok) {
console.error(`依赖检查失败: ${dep.id}`);
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,54 @@
// services/performance-monitor.ts
/**
*
*/
interface PerformanceMetrics {
loadTime: number; // 加载时间(毫秒)
memoryUsage: number; // 内存使用量bytes
errors: string[]; // 错误信息列表
}
/**
*
*/
export class PerformanceMonitor {
private static metrics: Map<string, PerformanceMetrics> = new Map();
/**
*
* @param id
*/
static startMonitoring(id: string) {
const startTime = performance.now();
const startMemory = (performance as any).memory?.usedJSHeapSize || 0;
return {
// 结束监控并记录指标
end: () => {
const loadTime = performance.now() - startTime;
const memoryUsage = ((performance as any).memory?.usedJSHeapSize || 0) - startMemory;
this.metrics.set(id, {
loadTime,
memoryUsage,
errors: []
});
},
// 记录错误信息
error: (err: string) => {
const metrics = this.metrics.get(id) || { loadTime: 0, memoryUsage: 0, errors: [] };
metrics.errors.push(err);
this.metrics.set(id, metrics);
}
};
}
/**
*
* @param id
*/
static getMetrics(id: string): PerformanceMetrics | undefined {
return this.metrics.get(id);
}
}

View File

@ -0,0 +1,39 @@
// services/plugin-communication.ts
/**
*
*/
type MessageHandler = (data: any) => void;
/**
*
*/
export class PluginMessenger {
// 存储所有消息处理器
private static handlers: Map<string, Set<MessageHandler>> = new Map();
/**
*
* @param channel
* @param handler
* @returns
*/
static subscribe(channel: string, handler: MessageHandler) {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, new Set());
}
this.handlers.get(channel)?.add(handler);
return () => {
this.handlers.get(channel)?.delete(handler);
};
}
/**
*
* @param channel
* @param data
*/
static publish(channel: string, data: any) {
this.handlers.get(channel)?.forEach(handler => handler(data));
}
}

View File

@ -1,3 +1,4 @@
//main.tsx
import * as React from "react"; import * as React from "react";
import "./main.css" import "./main.css"
import DynamicPage from "./page/page.tsx" import DynamicPage from "./page/page.tsx"
@ -7,7 +8,7 @@ import {createContext} from "react";
export const serverAddressContext=createContext("localhost:8080") export const serverAddressContext=createContext("localhost:8080")
// 动态路由 // 动态路由
const RouterListener: React.FC = () => { const RouterListener: React.FC = () => {
let pathname = location.pathname.split("/"); const pathname = location.pathname.split("/");
console.log(pathname) console.log(pathname)
return ( return (
<serverAddressContext.Provider value={"localhost:8080"}> <serverAddressContext.Provider value={"localhost:8080"}>
@ -17,7 +18,7 @@ const RouterListener: React.FC = () => {
} }
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<RouterListener/> <RouterListener/>
</React.StrictMode> </React.StrictMode>

View File

@ -1,3 +1,4 @@
// page/page.tsx
import React from "react"; import React from "react";
const THEMEPATH= "../../themes" const THEMEPATH= "../../themes"
import {serverAddressContext} from "../main.tsx"; import {serverAddressContext} from "../main.tsx";

18
frontend/types/common.ts Normal file
View File

@ -0,0 +1,18 @@
// types/common.ts
/**
*
*/
export interface VersionInfo {
major: number; // 主版本号 - 不兼容的API修改
minor: number; // 次版本号 - 向下兼容的功能性新增
patch: number; // 修订号 - 向下兼容的问题修正
}
/**
*
*/
export interface Dependency {
id: string; // 依赖项的唯一标识符
version: string; // 依赖项的版本要求
type: 'plugin' | 'theme'; // 依赖项类型:插件或主题
}

23
frontend/types/plugin.ts Normal file
View File

@ -0,0 +1,23 @@
// types/plugin.ts
import {Dependency} from "./common";
/**
*
*/
export interface PluginConfig {
id: string; // 插件唯一标识符
name: string; // 插件名称
version: string; // 插件版本
displayName: string; // 插件显示名称
description?: string; // 插件描述
author?: string; // 插件作者
enabled: boolean; // 插件是否启用
icon?: string; // 插件图标URL
adminPath?: string; // 插件管理页面路径
entry: string; // 插件入口组件路径
dependencies?: Dependency[]; // 插件依赖项
performance?: {
maxLoadTime?: number; // 最大加载时间(毫秒)
maxMemoryUsage?: number; // 最大内存使用量bytes
};
}

20
frontend/types/theme.ts Normal file
View File

@ -0,0 +1,20 @@
// types/theme.ts
import {Dependency} from "./common.ts";
/**
*
*/
export interface ThemeConfig {
name: string; // 主题的唯一名称标识符
version: string; // 主题版本
displayName: string; // 主题显示名称
description?: string; // 主题描述
author?: string; // 主题作者
preview?: string; // 主题预览图URL
globalStyles?: string; // 主题全局样式
dependencies?: Dependency[]; // 主题依赖项
performance?: {
maxLoadTime?: number; // 最大加载时间(毫秒)
maxMemoryUsage?: number; // 最大内存使用量bytes
};
}