后端:重构postgre数据结构

前端:定义好主题,插件,上下文参数的接口
This commit is contained in:
lsy 2024-11-11 19:31:40 +08:00
parent ddbb770923
commit 7f2a02dc61
15 changed files with 272 additions and 396 deletions

View File

@ -1,36 +1,64 @@
// main.rs
mod config;
mod sql;
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;
// /d:/data/echoes/backend/src/main.rs
/* 修改全局变量的类型定义 */
/**
* API接口
*
*
* - GET /api/install:
* - GET /api/sql: SQL查询并返回结果
*/
mod config; // 配置模块
mod sql; // SQL模块
use crate::sql::Database; // 引入数据库结构
use once_cell::sync::Lazy; // 用于延迟初始化
use rocket::{get, http::Status, launch, response::status, routes, serde::json::Json}; // 引入Rocket框架相关功能
use std::sync::Arc; // 引入Arc用于线程安全的引用计数
use tokio::sync::Mutex; // 引入Mutex用于异步锁
// 全局数据库连接变量
static DB: Lazy<Arc<Mutex<Option<Database>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
/* 数据库连接函数 */
/**
*
*
* #
* - `database`:
*
* #
* - `Result<(), Box<dyn std::error::Error>>`:
*/
async fn init_db(database: config::Database) -> Result<(), Box<dyn std::error::Error>> {
let database = Database::init(database).await?;
*DB.lock().await = Some(database);
let database = Database::init(database).await?; // 初始化数据库
*DB.lock().await = Some(database); // 保存数据库实例
Ok(())
}
/* 获取数据库的引用 */
/**
*
*
* #
* - `Result<Database, Box<dyn std::error::Error>>`:
*/
async fn get_db() -> Result<Database, Box<dyn std::error::Error>> {
DB.lock()
.await
.clone()
.ok_or_else(|| "Database not initialized".into())
.ok_or_else(|| "Database not initialized".into()) // 返回错误信息
}
/* 用于测试数据库 */
/**
*
*
* #
* - `Result<Json<Vec<std::collections::HashMap<String, String>>>, status::Custom<String>>`:
*/
#[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| {
status::Custom(
Status::InternalServerError,
format!("Database error: {}", e),
format!("Database error: {}", e), // 数据库错误信息
)
})?;
@ -40,27 +68,39 @@ async fn ssql(
.await
.map_err(|e| status::Custom(Status::InternalServerError, format!("Query error: {}", e)))?;
Ok(Json(query_result))
Ok(Json(query_result)) // 返回查询结果
}
/* 安装接口 */
/**
*
*
* #
* - `status::Custom<String>`:
*/
#[get("/install")]
async fn install() -> status::Custom<String> {
get_db()
.await
.map(|_| status::Custom(Status::Ok, "Database connected successfully".into()))
.map(|_| status::Custom(Status::Ok, "Database connected successfully".into())) // 连接成功
.unwrap_or_else(|e| {
status::Custom(
Status::InternalServerError,
format!("Failed to connect: {}", e),
format!("Failed to connect: {}", e), // 连接失败信息
)
})
}
/* 启动函数 */
/**
* Rocket应用
*
* #
* - `rocket::Rocket`: Rocket实例
*/
#[launch]
async fn rocket() -> _ {
let config = config::Config::read("./src/config/config.toml").expect("Failed to read config");
let config = config::Config::read("./src/config/config.toml").expect("Failed to read config"); // 读取配置
init_db(config.database)
.await
.expect("Failed to connect to database");
rocket::build().mount("/api", routes![install, ssql])
.expect("Failed to connect to database"); // 初始化数据库连接
rocket::build().mount("/api", routes![install, ssql]) // 挂载API路由
}

View File

@ -0,0 +1,90 @@
--- 安装自动生成uuid插件
CREATE EXTENSION IF NOT EXISTS pgcrypto;
--- 网站信息表
CREATE TABLE settings
(
settings_keys VARCHAR(255) PRIMARY KEY,
settings_values TEXT NOT NULL
);
--- 页面状态枚举
CREATE TYPE publication_status AS ENUM ('draft', 'published', 'private','hide');
--- 独立页面表
CREATE TABLE pages
(
page_id SERIAL PRIMARY KEY, --- 独立页面唯一id主键
page_meta_keywords VARCHAR(255) NOT NULL, ---mata关键字
page_meta_description VARCHAR(255) NOT NULL, ---mata描述
post_title VARCHAR(255) NOT NULL, --- 文章标题
page_content TEXT NOT NULL, --- 独立页面内容
page_mould VARCHAR(50), --- 独立页面模板名称
page_fields JSON, --- 自定义字段
page_path VARCHAR(255), --- 文章路径
page_status publication_status DEFAULT 'draft' --- 文章状态
);
--- 文章表
CREATE TABLE posts
(
post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), --- 文章主键
post_title VARCHAR(255) NOT NULL, --- 文章标题
post_meta_keywords VARCHAR(255) NOT NULL, ---mata关键字
post_meta_description VARCHAR(255) NOT NULL, ---mata描述
post_content TEXT NOT NULL, --- 文章内容
post_status publication_status DEFAULT 'draft', --- 文章状态
post_editor BOOLEAN DEFAULT FALSE, --- 文章是否编辑未保存
posts_unsaved_content TEXT, --- 未保存的文章
posts_path VARCHAR(255), --- 文章路径
post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章创建时间
post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章更新时间
post_published_at TIMESTAMP, --- 文章发布时间
CONSTRAINT post_updated_at_check CHECK (post_updated_at >= post_created_at) --- 文章时间约束
);
--- 标签表
CREATE TABLE tags
(
tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name) --- 标签名称主键
);
--- 文章与标签的关系表
CREATE TABLE post_tags
(
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE, --- 外键约束引用posts的主键
tag_name VARCHAR(50) REFERENCES tags (tag_name) ON DELETE CASCADE, --- 外键约束引用tag的主键
PRIMARY KEY (post_id, tag_name) --- 复合主键
);
--- 分类表
CREATE TABLE categories
(
category_name VARCHAR(50) PRIMARY KEY, ---
parent_category_name VARCHAR(50), -- 父类分类 ID可以为空
FOREIGN KEY (parent_category_name) REFERENCES categories (category_name) -- 外键约束,引用同一表的 category_id
);
--- 文章与分类的关系表
CREATE TABLE post_categories
(
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE, --- 外键约束引用posts的主键
category_id VARCHAR(50) REFERENCES categories (category_name) ON DELETE CASCADE, --- 外键约束引用categories的主键
PRIMARY KEY (post_id, category_id) --- 复合主键
);
--- 资源库
CREATE TABLE library
(
library_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), --- 资源ID
library_name VARCHAR(255) NOT NULL, --- 资源名称
library_size BIGINT NOT NULL, --- 大小
library_storage_path VARCHAR(255) NOT NULL UNIQUE, --- 储存路径
library_type VARCHAR(50) NOT NULL REFERENCES library_type (library_type) ON DELETE CASCADE, --- 资源类别
library_description VARCHAR(255), --- 资源描述
library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP --- 创建时间
);
--- 资源库类别
CREATE TABLE library_type
(
library_type VARCHAR(50) PRIMARY KEY, --- 资源类别
library_description VARCHAR(200) --- 资源类别描述
);
--- 配置文件库
CREATE TABLE config
(
config_name VARCHAR(50) PRIMARY KEY, --- 配置文件名称
config_config JSON --- 配置文件
)

View File

@ -1,5 +1,5 @@
// sql/psotgresql.rs
/*
/*
postgresql数据库实现具体的方法
*/
use super::DatabaseTrait;
@ -18,39 +18,46 @@ impl DatabaseTrait for Postgresql {
async fn connect(database: config::Database) -> Result<Self, Box<dyn Error>> {
let connection_str = format!(
"postgres://{}:{}@{}:{}/{}",
database.user,
database.password,
database.address,
database.prot,
database.db_name
database.user, database.password, database.address, database.prot, database.db_name
);
// 连接到数据库池
let pool = PgPool::connect(&connection_str)
.await
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
// 返回Postgresql实例
Ok(Postgresql { pool })
}
/**
*
*/
async fn query<'a>(
&'a self,
query: String,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> {
// 执行查询并获取所有行
let rows = sqlx::query(&query)
.fetch_all(&self.pool)
.await
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
// 存储查询结果
let mut results = Vec::new();
// 遍历每一行并构建结果映射
for row in rows {
let mut map = HashMap::new();
for column in row.columns() {
// 获取列的值,若失败则使用默认值
let value: String = row.try_get(column.name()).unwrap_or_default();
map.insert(column.name().to_string(), value);
}
results.push(map);
}
// 返回查询结果
Ok(results)
}
}

View File

@ -1,61 +0,0 @@
// 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

@ -1,33 +0,0 @@
// 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

@ -1,27 +0,0 @@
// 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

@ -1,66 +0,0 @@
// 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 };
}

13
frontend/index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -1,41 +0,0 @@
// 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

@ -1,54 +0,0 @@
// 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

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

12
frontend/types/context.ts Normal file
View File

@ -0,0 +1,12 @@
// File path: /d:/data/echoes/frontend/types/context.ts
/**
*
*
* API基础URL
*/
export interface AppContext {
apiBaseUrl: string; // 用于访问API的基础URL
themesPath: string; // 存储主题文件的目录路径
pluginsPath: string; // 存储插件文件的目录路径
assetsPath: string; // 存储静态资源的目录路径
}

View File

@ -1,23 +1,41 @@
// types/plugin.ts
import {Dependency} from "./common";
// File path: /d:/data/echoes/frontend/types/plugin.ts
/**
*
*
*
*
*/
export interface PluginConfig {
id: string; // 插件唯一标识符
name: string; // 插件名称
version: string; // 插件版本
displayName: string; // 插件显示名称
description?: string; // 插件描述
author?: string; // 插件作者
description?: string; // 插件描述(可选)
author?: string; // 插件作者(可选)
enabled: boolean; // 插件是否启用
icon?: string; // 插件图标URL
adminPath?: string; // 插件管理页面路径
icon?: string; // 插件图标URL(可选)
managePath?: string; // 插件管理页面路径(可选)
entry: string; // 插件入口组件路径
dependencies?: Dependency[]; // 插件依赖项
performance?: {
maxLoadTime?: number; // 最大加载时间(毫秒)
maxMemoryUsage?: number; // 最大内存使用量bytes
// 主题配置
settingsSchema?: {
type: string; // 配置类型
properties: Record<string, {
type: string; // 属性类型
title: string; // 属性标题
description?: string; // 属性描述(可选)
data?: any; // 额外数据(可选)
}>;
};
// 依赖
dependencies?: {
plugins?: string[]; // 依赖的插件列表(可选)
themes?: string[]; // 依赖的主题列表(可选)
};
// 插件生命周期钩子
hooks?: {
onInstall?: string; // 安装时调用的钩子(可选)
onUninstall?: string; // 卸载时调用的钩子(可选)
onEnable?: string; // 启用时调用的钩子(可选)
onDisable?: string; // 禁用时调用的钩子(可选)
};
}

View File

@ -1,20 +1,55 @@
// types/theme.ts
import {Dependency} from "./common.ts";
// /d:/data/echoes/frontend/types/theme.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
name: string; // 主题的唯一标识符
displayName: string; // 主题的显示名称
version: string; // 主题的版本号
description?: string; // 主题描述信息
author?: string; // 主题作者信息
entry: string; // 主题的入口组件路径
templates: Map<string, ThemeTemplate>; // 主题模板的映射表
/** 主题全局配置 */
globalSettings?: {
layout?: string; // 主题的布局配置
css?: string; // 主题的CSS配置
};
}
/** 主题配置文件 */
settingsSchema?: {
type: string; // 配置文件的类型
/** 属性定义 */
properties: Record<string, {
type: string; // 属性的数据类型
title: string; // 属性的标题
description?: string; // 属性的描述信息
data?: any; // 属性的默认数据
}>;
};
/** 依赖 */
dependencies?: {
plugins?: string[]; // 主题所依赖的插件列表
assets?: string[]; // 主题所依赖的资源列表
};
/** 钩子 */
hooks?: {
beforeRender?: string; // 渲染前执行的钩子
afterRender?: string; // 渲染后执行的钩子
onActivate?: string; // 主题激活时执行的钩子
onDeactivate?: string; // 主题停用时执行的钩子
};
}
/**
*
*/
export interface ThemeTemplate {
path: string; // 模板文件的路径
name: string; // 模板的名称
description?: string; // 模板的描述信息
}