增加readme,删除了注释

前端:完善了插件和主题的契约
This commit is contained in:
lsy 2024-11-19 00:20:31 +08:00
parent c19d21ce7d
commit 7e4d9c1b48
22 changed files with 226 additions and 689 deletions

View File

@ -1,2 +1,17 @@
能力是主题和系统向插件暴露的接口,契约则规定了开发者在开发主题或插件时的限制和模板。 ## 项目概述
系统使用 Remix后端采用 Rust 的 Rocket。
本项目是一个基于 Rust 和 TypeScript 的博客系统,旨在提供一个高效、可扩展的博客平台。系统分为前端和后端,前端使用 React 框架构建,后端使用 Rocket 框架实现 RESTful API。
## 技术栈
- **后端**: Rust, Rocket, SQLx, PostgreSQL
- **前端**: TypeScript, React, Axios, Tailwind CSS, Vite, Remix
- **身份验证**: JWT (JSON Web Tokens)
## 功能特性
- **用户管理**: 支持用户注册、登录和角色管理。
- **文章管理**: 支持文章的创建、编辑、删除和查看,支持标签和分类管理。
- **主题与插件支持**: 允许用户自定义主题和插件,增强系统的灵活性。
- **API 服务**: 提供 RESTful API 接口,供前端调用,实现数据交互。
- **错误处理与加载状态管理**: 提升用户体验。

View File

@ -1,47 +1,39 @@
// config/mod.rs
/*
*/
use serde::Deserialize; use serde::Deserialize;
use std::{ env, fs}; use std::{ env, fs};
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Debug,Clone)]
pub struct Config { pub struct Config {
pub info: Info, // 配置信息 pub info: Info,
pub sql_config: SqlConfig, // 关系型数据库配置 pub sql_config: SqlConfig,
// pub no_sql_config:NoSqlConfig, 非关系型数据库配置
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Debug,Clone)]
pub struct Info { pub struct Info {
pub install: bool, // 是否安装 pub install: bool,
pub non_relational: bool, // 是否非关系型 pub non_relational: bool,
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Debug,Clone)]
pub struct SqlConfig { pub struct SqlConfig {
pub db_type: String, // 数据库类型 pub db_type: String,
pub address: String, // 地址 pub address: String,
pub port: u32, // 端口 pub port: u32,
pub user: String, // 用户名 pub user: String,
pub password: String, // 密码 pub password: String,
pub db_name: String, // 数据库名称 pub db_name: String,
} }
#[derive(Deserialize,Debug,Clone)] #[derive(Deserialize,Debug,Clone)]
pub struct NoSqlConfig { pub struct NoSqlConfig {
pub db_type: String, // 数据库类型 pub db_type: String,
pub address: String, // 地址 pub address: String,
pub port: u32, // 端口 pub port: u32,
pub user: String, // 用户名 pub user: String,
pub password: String, // 密码 pub password: String,
pub db_name: String, // 数据库名称 pub db_name: String,
} }
impl Config { impl Config {
/// 读取配置文件
pub fn read() -> Result<Self, Box<dyn std::error::Error>> { pub fn read() -> Result<Self, Box<dyn std::error::Error>> {
let path = env::current_dir()? let path = env::current_dir()?
.join("assets") .join("assets")

View File

@ -1,10 +1,3 @@
// File path: src/database/relational/mod.rs
/**
PostgreSQL
*/
mod postgresql; mod postgresql;
use std::collections::HashMap; use std::collections::HashMap;
use crate::config; use crate::config;
@ -14,70 +7,42 @@ use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum SqlOperation { pub enum SqlOperation {
Select, // 查询操作 Select,
Insert, // 插入操作 Insert,
Update, // 更新操作 Update,
Delete, // 删除操作 Delete,
} }
/// 查询构建器结构
pub struct QueryBuilder { pub struct QueryBuilder {
operation: SqlOperation, // SQL操作类型 operation: SqlOperation,
table: String, // 表名 table: String,
fields: Vec<String>, // 查询字段 fields: Vec<String>,
params: HashMap<String, String>, // 插入或更新的参数 params: HashMap<String, String>,
where_conditions: HashMap<String, String>, // WHERE条件 where_conditions: HashMap<String, String>,
order_by: Option<String>, // 排序字段 order_by: Option<String>,
limit: Option<i32>, // 限制返回的记录数 limit: Option<i32>,
} }
#[async_trait] #[async_trait]
pub trait DatabaseTrait: Send + Sync { pub trait DatabaseTrait: Send + Sync {
/**
@param database
@return Result<Self, Box<dyn Error>>
*/
async fn connect(database: config::SqlConfig) -> Result<Self, Box<dyn Error>> where Self: Sized; async fn connect(database: config::SqlConfig) -> Result<Self, Box<dyn Error>> where Self: Sized;
/**
@param query SQL查询语句
@return Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>
*/
async fn execute_query<'a>( async fn execute_query<'a>(
&'a self, &'a self,
builder: &QueryBuilder, builder: &QueryBuilder,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>; ) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>;
/**
@param database
@return Result<(), Box<dyn Error>>
*/
async fn initialization(database: config::SqlConfig) -> Result<(), Box<dyn Error>> where Self: Sized; async fn initialization(database: config::SqlConfig) -> Result<(), Box<dyn Error>> where Self: Sized;
} }
#[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 {
/**
@return &Box<dyn DatabaseTrait>
*/
pub fn get_db(&self) -> &Box<dyn DatabaseTrait> { pub fn get_db(&self) -> &Box<dyn DatabaseTrait> {
&self.db &self.db
} }
/**
@param database
@return Result<Self, Box<dyn Error>>
*/
pub async fn link(database: config::SqlConfig) -> Result<Self, Box<dyn Error>> { pub async fn link(database: config::SqlConfig) -> Result<Self, Box<dyn Error>> {
let db = match database.db_type.as_str() { let db = match database.db_type.as_str() {
"postgresql" => postgresql::Postgresql::connect(database).await?, "postgresql" => postgresql::Postgresql::connect(database).await?,
@ -87,11 +52,6 @@ impl Database {
Ok(Self { db: Arc::new(Box::new(db)) }) Ok(Self { db: Arc::new(Box::new(db)) })
} }
/**
@param database
@return Result<(), Box<dyn Error>>
*/
pub async fn initial_setup(database: config::SqlConfig) -> Result<(), Box<dyn Error>> { pub async fn initial_setup(database: config::SqlConfig) -> Result<(), Box<dyn Error>> {
match database.db_type.as_str() { match database.db_type.as_str() {
"postgresql" => postgresql::Postgresql::initialization(database).await?, "postgresql" => postgresql::Postgresql::initialization(database).await?,
@ -102,12 +62,6 @@ impl Database {
} }
impl QueryBuilder { impl QueryBuilder {
/**
@param operation SQL操作类型
@param table
@return Self
*/
pub fn new(operation: SqlOperation, table: &str) -> Self { pub fn new(operation: SqlOperation, table: &str) -> Self {
QueryBuilder { QueryBuilder {
operation, operation,
@ -120,10 +74,6 @@ impl QueryBuilder {
} }
} }
/**
SQL语句和参数
@return (String, Vec<String>) SQL语句和参数值
*/
pub fn build(&self) -> (String, Vec<String>) { pub fn build(&self) -> (String, Vec<String>) {
let mut query = String::new(); let mut query = String::new();
let mut values = Vec::new(); let mut values = Vec::new();
@ -131,7 +81,6 @@ impl QueryBuilder {
match self.operation { match self.operation {
SqlOperation::Select => { SqlOperation::Select => {
// SELECT 操作
let fields = if self.fields.is_empty() { let fields = if self.fields.is_empty() {
"*".to_string() "*".to_string()
} else { } else {
@ -140,7 +89,6 @@ impl QueryBuilder {
query.push_str(&format!("SELECT {} FROM {}", fields, self.table)); query.push_str(&format!("SELECT {} FROM {}", fields, self.table));
// 添加 WHERE 条件
if !self.where_conditions.is_empty() { if !self.where_conditions.is_empty() {
let conditions: Vec<String> = self.where_conditions let conditions: Vec<String> = self.where_conditions
.iter() .iter()
@ -156,7 +104,6 @@ impl QueryBuilder {
} }
}, },
SqlOperation::Insert => { SqlOperation::Insert => {
// INSERT 操作
let fields: Vec<String> = self.params.keys().cloned().collect(); let fields: Vec<String> = self.params.keys().cloned().collect();
let placeholders: Vec<String> = (1..=self.params.len()) let placeholders: Vec<String> = (1..=self.params.len())
.map(|i| format!("${}", i)) .map(|i| format!("${}", i))
@ -169,13 +116,11 @@ impl QueryBuilder {
placeholders.join(", ") placeholders.join(", ")
)); ));
// 收集参数值
for field in fields { for field in fields {
values.push(self.params[&field].clone()); values.push(self.params[&field].clone());
} }
}, },
SqlOperation::Update => { SqlOperation::Update => {
// UPDATE 操作
query.push_str(&format!("UPDATE {}", self.table)); query.push_str(&format!("UPDATE {}", self.table));
let set_clauses: Vec<String> = self.params let set_clauses: Vec<String> = self.params
@ -191,7 +136,6 @@ impl QueryBuilder {
query.push_str(" SET "); query.push_str(" SET ");
query.push_str(&set_clauses.join(", ")); query.push_str(&set_clauses.join(", "));
// 添加 WHERE 条件
if !self.where_conditions.is_empty() { if !self.where_conditions.is_empty() {
let conditions: Vec<String> = self.where_conditions let conditions: Vec<String> = self.where_conditions
.iter() .iter()
@ -207,10 +151,8 @@ impl QueryBuilder {
} }
}, },
SqlOperation::Delete => { SqlOperation::Delete => {
// DELETE 操作
query.push_str(&format!("DELETE FROM {}", self.table)); query.push_str(&format!("DELETE FROM {}", self.table));
// 添加 WHERE 条件
if !self.where_conditions.is_empty() { if !self.where_conditions.is_empty() {
let conditions: Vec<String> = self.where_conditions let conditions: Vec<String> = self.where_conditions
.iter() .iter()
@ -227,12 +169,10 @@ impl QueryBuilder {
} }
} }
// 添加 ORDER BY
if let Some(order) = &self.order_by { if let Some(order) = &self.order_by {
query.push_str(&format!(" ORDER BY {}", order)); query.push_str(&format!(" ORDER BY {}", order));
} }
// 添加 LIMIT
if let Some(limit) = self.limit { if let Some(limit) = self.limit {
query.push_str(&format!(" LIMIT {}", limit)); query.push_str(&format!(" LIMIT {}", limit));
} }
@ -240,51 +180,26 @@ impl QueryBuilder {
(query, values) (query, values)
} }
/**
@param fields
@return &mut Self 便
*/
pub fn fields(&mut self, fields: Vec<String>) -> &mut Self { pub fn fields(&mut self, fields: Vec<String>) -> &mut Self {
self.fields = fields; self.fields = fields;
self self
} }
/**
@param params
@return &mut Self 便
*/
pub fn params(&mut self, params: HashMap<String, String>) -> &mut Self { pub fn params(&mut self, params: HashMap<String, String>) -> &mut Self {
self.params = params; self.params = params;
self self
} }
/**
WHERE条件
@param conditions
@return &mut Self 便
*/
pub fn where_conditions(&mut self, conditions: HashMap<String, String>) -> &mut Self { pub fn where_conditions(&mut self, conditions: HashMap<String, String>) -> &mut Self {
self.where_conditions = conditions; self.where_conditions = conditions;
self self
} }
/**
@param order
@return &mut Self 便
*/
pub fn order_by(&mut self, order: &str) -> &mut Self { pub fn order_by(&mut self, order: &str) -> &mut Self {
self.order_by = Some(order.to_string()); self.order_by = Some(order.to_string());
self self
} }
/**
@param limit
@return &mut Self 便
*/
pub fn limit(&mut self, limit: i32) -> &mut Self { pub fn limit(&mut self, limit: i32) -> &mut Self {
self.limit = Some(limit); self.limit = Some(limit);
self self

View File

@ -1,99 +1,84 @@
--- 安装自动生成uuid插件
CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE EXTENSION IF NOT EXISTS pgcrypto;
--- 用户权限枚举
CREATE TYPE privilege_level AS ENUM ( 'contributor', 'administrators'); CREATE TYPE privilege_level AS ENUM ( 'contributor', 'administrators');
--- 用户信息表
CREATE TABLE persons CREATE TABLE persons
( (
person_name VARCHAR(100) PRIMARY KEY, --- 用户名 person_name VARCHAR(100) PRIMARY KEY,
person_email VARCHAR(255) UNIQUE NOT NULL, --- 用户邮箱 person_email VARCHAR(255) UNIQUE NOT NULL,
person_icon VARCHAR(255), --- 用户头像 person_icon VARCHAR(255),
person_password VARCHAR(255) NOT NULL, --- 用户密码 person_password VARCHAR(255) NOT NULL,
person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户创建时间 person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户更新时间 person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_avatar VARCHAR(255), --- 用户头像URL person_avatar VARCHAR(255),
person_role VARCHAR(50), --- 用户角色 person_role VARCHAR(50),
person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 最后登录时间 person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
person_level privilege_level NOT NULL DEFAULT 'contributor' --- 用户权限 person_level privilege_level NOT NULL DEFAULT 'contributor'
); );
--- 页面状态枚举
CREATE TYPE publication_status AS ENUM ('draft', 'published', 'private','hide'); CREATE TYPE publication_status AS ENUM ('draft', 'published', 'private','hide');
--- 独立页面表
CREATE TABLE pages CREATE TABLE pages
( (
page_id SERIAL PRIMARY KEY, --- 独立页面唯一id主键 page_id SERIAL PRIMARY KEY,
page_meta_keywords VARCHAR(255) NOT NULL, ---mata关键字 page_meta_keywords VARCHAR(255) NOT NULL,
page_meta_description VARCHAR(255) NOT NULL, ---mata描述 page_meta_description VARCHAR(255) NOT NULL,
page_title VARCHAR(255) NOT NULL, --- 文章标题 page_title VARCHAR(255) NOT NULL,
page_content TEXT NOT NULL, --- 独立页面内容 page_content TEXT NOT NULL,
page_mould VARCHAR(50), --- 独立页面模板名称 page_mould VARCHAR(50),
page_fields JSON, --- 自定义字段 page_fields JSON,
page_status publication_status DEFAULT 'draft' --- 文章状态 page_status publication_status DEFAULT 'draft'
); );
--- 文章表
CREATE TABLE posts CREATE TABLE posts
( (
post_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE, --- 文章作者 post_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), --- 文章主键 post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_picture VARCHAR(255), --- 文章头图 post_picture VARCHAR(255),
post_title VARCHAR(255) NOT NULL, --- 文章标题 post_title VARCHAR(255) NOT NULL,
post_meta_keywords VARCHAR(255) NOT NULL, ---mata关键字 post_meta_keywords VARCHAR(255) NOT NULL,
post_meta_description VARCHAR(255) NOT NULL, ---mata描述 post_meta_description VARCHAR(255) NOT NULL,
post_content TEXT NOT NULL, --- 文章内容 post_content TEXT NOT NULL,
post_status publication_status DEFAULT 'draft', --- 文章状态 post_status publication_status DEFAULT 'draft',
post_editor BOOLEAN DEFAULT FALSE, --- 文章是否编辑未保存 post_editor BOOLEAN DEFAULT FALSE,
post_unsaved_content TEXT, --- 未保存的文章 post_unsaved_content TEXT,
post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章创建时间 post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章更新时间 post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
post_published_at TIMESTAMP, --- 文章发布时间 post_published_at TIMESTAMP,
CONSTRAINT post_updated_at_check CHECK (post_updated_at >= post_created_at) --- 文章时间约束 CONSTRAINT post_updated_at_check CHECK (post_updated_at >= post_created_at)
); );
--- 标签表
CREATE TABLE tags CREATE TABLE tags
( (
tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name), --- 标签名称主键 tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name),
tag_icon VARCHAR(255) --- 标签图标 tag_icon VARCHAR(255)
); );
--- 文章与标签的关系表
CREATE TABLE post_tags CREATE TABLE post_tags
( (
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE, --- 外键约束引用posts的主键 post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
tag_name VARCHAR(50) REFERENCES tags (tag_name) ON DELETE CASCADE, --- 外键约束引用tag的主键 tag_name VARCHAR(50) REFERENCES tags (tag_name) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_name) --- 复合主键 PRIMARY KEY (post_id, tag_name)
); );
--- 分类表
CREATE TABLE categories CREATE TABLE categories
( (
category_name VARCHAR(50) PRIMARY KEY, --- category_name VARCHAR(50) PRIMARY KEY,
parent_category_name VARCHAR(50), -- 父类分类 ID可以为空 parent_category_name VARCHAR(50),
FOREIGN KEY (parent_category_name) REFERENCES categories (category_name) -- 外键约束,引用同一表的 category_id FOREIGN KEY (parent_category_name) REFERENCES categories (category_name)
); );
--- 文章与分类的关系表
CREATE TABLE post_categories CREATE TABLE post_categories
( (
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE, --- 外键约束引用posts的主键 post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
category_id VARCHAR(50) REFERENCES categories (category_name) ON DELETE CASCADE, --- 外键约束引用categories的主键 category_id VARCHAR(50) REFERENCES categories (category_name) ON DELETE CASCADE,
PRIMARY KEY (post_id, category_id) --- 复合主键 PRIMARY KEY (post_id, category_id)
); );
--- 资源库
CREATE TABLE library CREATE TABLE library
( (
library_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE, --- 资源作者 library_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
library_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), --- 资源ID library_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_name VARCHAR(255) NOT NULL, --- 资源名称 library_name VARCHAR(255) NOT NULL,
library_size BIGINT NOT NULL, --- 大小 library_size BIGINT NOT NULL,
library_storage_path VARCHAR(255) NOT NULL UNIQUE, --- 储存路径 library_storage_path VARCHAR(255) NOT NULL UNIQUE,
library_type VARCHAR(50) NOT NULL, --- 资源类别 library_type VARCHAR(50) NOT NULL,
library_class VARCHAR(50), --- 资源类 library_class VARCHAR(50),
library_description VARCHAR(255), --- 资源描述 library_description VARCHAR(255),
library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP --- 创建时间 library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
--- 配置文件库
CREATE TABLE config CREATE TABLE config
( (
config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name), --- 配置文件名称 config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name),
config_config JSON --- 配置文件 config_config JSON
) );

View File

@ -1,9 +1,3 @@
// File path: src/database/relational/postgresql/mod.rs
/**
* PostgreSQL数据库的交互功能
*
*/
use super::{DatabaseTrait, QueryBuilder}; use super::{DatabaseTrait, QueryBuilder};
use crate::config; use crate::config;
use async_trait::async_trait; use async_trait::async_trait;
@ -12,23 +6,12 @@ use std::{collections::HashMap, error::Error};
use std::{env, fs}; use std::{env, fs};
#[derive(Clone)] #[derive(Clone)]
/// PostgreSQL数据库连接池结构体
pub struct Postgresql { pub struct Postgresql {
/// 数据库连接池
pool: PgPool, pool: PgPool,
} }
#[async_trait] #[async_trait]
impl DatabaseTrait for Postgresql { impl DatabaseTrait for Postgresql {
/**
*
*
* #
* - `db_config`:
*
* #
* - `Result<(), Box<dyn Error>>`:
*/
async fn initialization(db_config: config::SqlConfig) -> Result<(), Box<dyn Error>> { async fn initialization(db_config: config::SqlConfig) -> Result<(), Box<dyn Error>> {
let path = env::current_dir()? let path = env::current_dir()?
.join("src") .join("src")
@ -38,77 +21,49 @@ impl DatabaseTrait for Postgresql {
.join("init.sql"); .join("init.sql");
let grammar = fs::read_to_string(&path)?; let grammar = fs::read_to_string(&path)?;
// 创建初始连接(不指定数据库)
let connection_str = format!("postgres://{}:{}@{}:{}", db_config.user, db_config.password, db_config.address, db_config.port); let connection_str = format!("postgres://{}:{}@{}:{}", db_config.user, db_config.password, db_config.address, db_config.port);
let pool = PgPool::connect(&connection_str).await?; let pool = PgPool::connect(&connection_str).await?;
// 创建数据库
pool.execute(format!("CREATE DATABASE {}", db_config.db_name).as_str()).await?; 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.address, db_config.port, db_config.db_name); let new_connection_str = format!("postgres://{}:{}@{}:{}/{}", db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name);
let new_pool = PgPool::connect(&new_connection_str).await?; let new_pool = PgPool::connect(&new_connection_str).await?;
// 执行初始化脚本
new_pool.execute(grammar.as_str()).await?; new_pool.execute(grammar.as_str()).await?;
Ok(()) Ok(())
} }
/**
* PostgreSQL数据库并返回Postgresql实例
*
* #
* - `db_config`:
*
* #
* - `Result<Self, Box<dyn Error>>`:
*/
async fn connect(db_config: config::SqlConfig) -> Result<Self, Box<dyn Error>> { async fn connect(db_config: config::SqlConfig) -> Result<Self, Box<dyn Error>> {
let connection_str = format!( let connection_str = format!(
"postgres://{}:{}@{}:{}/{}", "postgres://{}:{}@{}:{}/{}",
db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name
); );
// 连接到数据库池
let pool = PgPool::connect(&connection_str) let pool = PgPool::connect(&connection_str)
.await .await
.map_err(|e| Box::new(e) as Box<dyn Error>)?; .map_err(|e| Box::new(e) as Box<dyn Error>)?;
// 返回Postgresql实例
Ok(Postgresql { pool }) Ok(Postgresql { pool })
} }
/**
*
*
* #
* - `builder`:
*
* #
* - `Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>`:
*/
async fn execute_query<'a>( async fn execute_query<'a>(
&'a self, &'a self,
builder: &QueryBuilder, builder: &QueryBuilder,
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> { ) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> {
let (query, values) = builder.build(); let (query, values) = builder.build();
// 构建查询
let mut sqlx_query = sqlx::query(&query); let mut sqlx_query = sqlx::query(&query);
// 绑定参数
for value in values { for value in values {
sqlx_query = sqlx_query.bind(value); sqlx_query = sqlx_query.bind(value);
} }
// 执行查询
let rows = sqlx_query let rows = sqlx_query
.fetch_all(&self.pool) .fetch_all(&self.pool)
.await .await
.map_err(|e| Box::new(e) as Box<dyn Error>)?; .map_err(|e| Box::new(e) as Box<dyn Error>)?;
// 处理结果
let mut results = Vec::new(); let mut results = Vec::new();
for row in rows { for row in rows {
let mut map = HashMap::new(); let mut map = HashMap::new();

View File

@ -1,71 +1,36 @@
// File path: /d:/data/echoes/backend/src/main.rs mod config;
mod database;
mod secret;
/** use chrono::Duration;
* API接口 use database::relational;
* use once_cell::sync::Lazy;
* use rocket::{get, post, http::Status, launch, response::status, routes, serde::json::Json};
* - GET /api/install: use std::sync::Arc;
* - GET /api/sql: SQL查询并返回结果 use tokio::sync::Mutex;
*/
mod config; // 配置模块
mod database; // 数据库模块
mod secret; // 密钥模块
use chrono::Duration; // 引入时间持续时间
use database::relational; // 引入关系型数据库
use once_cell::sync::Lazy; // 用于延迟初始化
use rocket::{get, post, http::Status, launch, response::status, routes, serde::json::Json}; // 引入Rocket框架相关功能
use std::sync::Arc; // 引入Arc用于线程安全的引用计数
use tokio::sync::Mutex; // 引入Mutex用于异步锁
// 全局数据库连接变量
static SQL: Lazy<Arc<Mutex<Option<relational::Database>>>> = static SQL: Lazy<Arc<Mutex<Option<relational::Database>>>> =
Lazy::new(|| Arc::new(Mutex::new(None))); Lazy::new(|| Arc::new(Mutex::new(None)));
/**
*
*
* #
* - `database`:
*
* #
* - `Result<(), Box<dyn std::error::Error>>`:
*/
async fn init_sql(database: config::SqlConfig) -> Result<(), Box<dyn std::error::Error>> { async fn init_sql(database: config::SqlConfig) -> Result<(), Box<dyn std::error::Error>> {
let database = relational::Database::link(database).await?; // 初始化数据库 let database = relational::Database::link(database).await?;
*SQL.lock().await = Some(database); // 保存数据库实例 *SQL.lock().await = Some(database);
Ok(()) Ok(())
} }
/**
*
*
* #
* - `Result<relational::Database, Box<dyn std::error::Error>>`:
*/
async fn get_sql() -> Result<relational::Database, Box<dyn std::error::Error>> { async fn get_sql() -> Result<relational::Database, Box<dyn std::error::Error>> {
SQL.lock() SQL.lock()
.await .await
.clone() .clone()
.ok_or_else(|| "Database not initialized".into()) // 返回错误信息 .ok_or_else(|| "Database not initialized".into())
} }
/**
*
*
* #
* - `data`:
*
* #
* - `Result<status::Custom<String>, status::Custom<String>>`:
*/
#[post("/install", format = "json", data = "<data>")] #[post("/install", format = "json", data = "<data>")]
async fn install(data: Json<config::SqlConfig>) -> Result<status::Custom<String>, status::Custom<String>> { async fn install(data: Json<config::SqlConfig>) -> Result<status::Custom<String>, status::Custom<String>> {
relational::Database::initial_setup(data.into_inner()).await.map_err(|e| { relational::Database::initial_setup(data.into_inner()).await.map_err(|e| {
status::Custom( status::Custom(
Status::InternalServerError, Status::InternalServerError,
format!("Database initialization failed: {}", e), // 连接失败信息 format!("Database initialization failed: {}", e),
) )
})?; })?;
@ -75,48 +40,34 @@ async fn install(data: Json<config::SqlConfig>) -> Result<status::Custom<String>
)) ))
} }
/**
* JWT接口
*
* #
* - `Result<status::Custom<String>, status::Custom<String>>`: JWT令牌或错误信息
*/
#[get("/system")] #[get("/system")]
async fn token_system() -> Result<status::Custom<String>, status::Custom<String>> { async fn token_system() -> Result<status::Custom<String>, status::Custom<String>> {
// 创建 Claims
let claims = secret::CustomClaims { let claims = secret::CustomClaims {
user_id: String::from("system"), // 用户ID user_id: String::from("system"),
device_ua: String::from("system"), // 设备用户代理 device_ua: String::from("system"),
}; };
// 生成JWT
let token = secret::generate_jwt(claims, Duration::seconds(1)).map_err(|e| { let token = secret::generate_jwt(claims, Duration::seconds(1)).map_err(|e| {
status::Custom( status::Custom(
Status::InternalServerError, Status::InternalServerError,
format!("JWT generation failed: {}", e), // JWT生成失败信息 format!("JWT generation failed: {}", e),
) )
})?; })?;
Ok(status::Custom(Status::Ok, token)) // 返回JWT令牌 Ok(status::Custom(Status::Ok, token))
} }
/**
* Rocket应用
*
* #
* - `rocket::Rocket`: Rocket应用实例
*/
#[launch] #[launch]
async fn rocket() -> _ { async fn rocket() -> _ {
let config = config::Config::read().expect("Failed to read config"); // 读取配置 let config = config::Config::read().expect("Failed to read config");
if config.info.install { if config.info.install {
init_sql(config.sql_config) init_sql(config.sql_config)
.await .await
.expect("Failed to connect to database"); // 初始化数据库连接 .expect("Failed to connect to database");
rocket::build() rocket::build()
.mount("/auth/token", routes![token_system]) .mount("/auth/token", routes![token_system])
} else { } else {
rocket::build() rocket::build()
.mount("/", routes![install]) // 挂载API路由 .mount("/", routes![install])
} }
} }

View File

@ -1,10 +1,3 @@
// File path: src/secret.rs
/**
* JWT的生成和验证功能
* JWTJWT的相关函数
*/
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, Token, UntrustedToken, TimeOptions}; use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, Token, UntrustedToken, TimeOptions};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
@ -15,16 +8,15 @@ use std::{env, fs};
use std::error::Error; use std::error::Error;
use rand::{SeedableRng, RngCore}; use rand::{SeedableRng, RngCore};
// 定义JWT的Claims结构体有效载荷
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CustomClaims { pub struct CustomClaims {
pub user_id: String, // 用户ID pub user_id: String,
pub device_ua: String, // 用户UA pub device_ua: String,
} }
pub enum SecretKey { pub enum SecretKey {
Signing, // 签名密钥 Signing,
Verifying, // 验证密钥 Verifying,
} }
impl SecretKey { impl SecretKey {
@ -36,84 +28,72 @@ impl SecretKey {
} }
} }
/**
*
*/
pub fn generate_key() -> Result<(), Box<dyn Error>> { pub fn generate_key() -> Result<(), Box<dyn Error>> {
let mut csprng = rand::rngs::StdRng::from_entropy(); // 使用系统熵创建随机数生成器 let mut csprng = rand::rngs::StdRng::from_entropy();
let mut private_key_bytes = [0u8; 32]; // 存储签名密钥的字节数组 let mut private_key_bytes = [0u8; 32];
csprng.fill_bytes(&mut private_key_bytes); // 生成签名密钥 csprng.fill_bytes(&mut private_key_bytes);
let signing_key = SigningKey::from_bytes(&private_key_bytes); // 从签名密钥获取SigningKey let signing_key = SigningKey::from_bytes(&private_key_bytes);
let verifying_key = signing_key.verifying_key(); // 获取验证密钥 let verifying_key = signing_key.verifying_key();
let base_path = env::current_dir()? let base_path = env::current_dir()?
.join("assets") .join("assets")
.join("key"); .join("key");
fs::create_dir_all(&base_path)?; // 创建目录 fs::create_dir_all(&base_path)?;
File::create(base_path.join(SecretKey::Signing.as_string()))? File::create(base_path.join(SecretKey::Signing.as_string()))?
.write_all(signing_key.as_bytes())?; // 保存签名密钥 .write_all(signing_key.as_bytes())?;
File::create(base_path.join(SecretKey::Verifying.as_string()))? File::create(base_path.join(SecretKey::Verifying.as_string()))?
.write_all(verifying_key.as_bytes())?; // 保存验证密钥 .write_all(verifying_key.as_bytes())?;
Ok(()) Ok(())
} }
/**
*
*/
pub fn get_key(key_type: SecretKey) -> Result<[u8; 32], Box<dyn Error>> { pub fn get_key(key_type: SecretKey) -> Result<[u8; 32], Box<dyn Error>> {
let path = env::current_dir()? let path = env::current_dir()?
.join("assets") .join("assets")
.join("key") .join("key")
.join(key_type.as_string()); .join(key_type.as_string());
let key_bytes = fs::read(path)?; // 读取密钥文件 let key_bytes = fs::read(path)?;
let mut key = [0u8; 32]; // 固定长度的数组 let mut key = [0u8; 32];
key.copy_from_slice(&key_bytes[..32]); // 拷贝前32个字节 key.copy_from_slice(&key_bytes[..32]);
Ok(key) Ok(key)
} }
/**
* JWT
*/
pub fn generate_jwt(claims: CustomClaims, duration: Duration) -> Result<String, Box<dyn Error>> { pub fn generate_jwt(claims: CustomClaims, duration: Duration) -> Result<String, Box<dyn Error>> {
let key_bytes = get_key(SecretKey::Signing)?; // 从文件读取私钥 let key_bytes = get_key(SecretKey::Signing)?;
let signing_key = SigningKey::from_bytes(&key_bytes); // 创建SigningKey let signing_key = SigningKey::from_bytes(&key_bytes);
let time_options = TimeOptions::new( let time_options = TimeOptions::new(
Duration::seconds(0), // 设置时间容差为0 Duration::seconds(0),
Utc::now // 使用当前UTC时间作为时钟函数 Utc::now
); // 设置时间容差为); // 默认时间选项 );
let claims = jwt_compact::Claims::new(claims) // 创建JWT的有效载荷 let claims = jwt_compact::Claims::new(claims)
.set_duration_and_issuance(&time_options, duration) .set_duration_and_issuance(&time_options, duration)
.set_not_before(Utc::now()); // 设置不早于时间 .set_not_before(Utc::now());
let header = Header::empty(); // 创建自定义的头部 let header = Header::empty();
let token = Ed25519.token(&header, &claims, &signing_key)?; // 使用Ed25519签名JWT let token = Ed25519.token(&header, &claims, &signing_key)?;
Ok(token) Ok(token)
} }
/**
* JWT并返回自定义声明
*/
pub fn validate_jwt(token: &str) -> Result<CustomClaims, Box<dyn Error>> { pub fn validate_jwt(token: &str) -> Result<CustomClaims, Box<dyn Error>> {
let key_bytes = get_key(SecretKey::Verifying)?; // 从文件读取验证密钥 let key_bytes = get_key(SecretKey::Verifying)?;
let verifying = VerifyingKey::from_bytes(&key_bytes)?; // 创建VerifyingKey let verifying = VerifyingKey::from_bytes(&key_bytes)?;
let token = UntrustedToken::new(token)?; // 创建未受信任的Token let token = UntrustedToken::new(token)?;
let token: Token<CustomClaims> = Ed25519.validator(&verifying).validate(&token)?; // 验证Token let token: Token<CustomClaims> = Ed25519.validator(&verifying).validate(&token)?;
let time_options = TimeOptions::new( let time_options = TimeOptions::new(
Duration::seconds(0), // 设置时间容差为0 Duration::seconds(0),
Utc::now // 使用当前UTC时间作为时钟函数 Utc::now
); // 设置时间容差为); // 默认时间选项 );
token.claims() token.claims()
.validate_expiration(&time_options)? // 验证过期时间 .validate_expiration(&time_options)?
.validate_maturity(&time_options)?; // 验证成熟时间 .validate_maturity(&time_options)?;
let claims = token.claims().custom.clone(); // 获取自定义声明 let claims = token.claims().custom.clone();
Ok(claims) Ok(claims)
} }

View File

@ -1,4 +1,3 @@
// File path: components/ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react'; import React, { Component, ErrorInfo, ReactNode } from 'react';
import { ThemeService } from '../services/themeService'; import { ThemeService } from '../services/themeService';
@ -28,11 +27,9 @@ export default class ErrorBoundary extends Component<Props, State> {
if (this.state.hasError) { if (this.state.hasError) {
const themeService = ThemeService.getInstance(); const themeService = ThemeService.getInstance();
try { try {
// 尝试使用主题的错误模板
const errorTemplate = themeService.getTemplate('error'); const errorTemplate = themeService.getTemplate('error');
return <div dangerouslySetInnerHTML={{ __html: errorTemplate }} />; return <div dangerouslySetInnerHTML={{ __html: errorTemplate }} />;
} catch (e) { } catch (e) {
// 如果无法获取主题模板,显示默认错误页面
return ( return (
<div className="error-page"> <div className="error-page">
<h1>Something went wrong</h1> <h1>Something went wrong</h1>

View File

@ -1,4 +1,3 @@
// File path: components/LoadingBoundary.tsx
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
interface LoadingBoundaryProps { interface LoadingBoundaryProps {

View File

@ -1,13 +1,5 @@
// src/contracts/CapabilityContract.ts
/**
*
*/
export interface CapabilityProps<T> { export interface CapabilityProps<T> {
// 能力名称
name: string; name: string;
// 能力描述
description?: string; description?: string;
// 能力执行函数
execute: (...args: any[]) => Promise<T>; execute: (...args: any[]) => Promise<T>;
} }

View File

@ -1,12 +1,9 @@
// File path: contracts\generalContract.ts
/**
*
*
* - null
* - number
* - string
* - boolean
* -
* -
*/
export type SerializeType = null | number | string | boolean | { [key: string]: SerializeType } | Array<SerializeType>; export type SerializeType = null | number | string | boolean | { [key: string]: SerializeType } | Array<SerializeType>;
export interface Configuration {
[key: string]: {
title: string;
description?: string;
data: SerializeType;
};
}

View File

@ -1,40 +1,18 @@
// File path: contracts\pluginContract.ts import { Configuration } from "contracts/generalContract";
/**
*
*
*
*
*/
import { SerializeType } from "contracts/generalContract";
export interface PluginConfig { export interface PluginConfig {
name: string; // 插件名称 name: string;
version: string; // 插件版本 version: string;
displayName: string; // 插件显示名称 displayName: string;
description?: string; // 插件描述(可选) description?: string;
author?: string; // 插件作者(可选) author?: string;
enabled: boolean; // 插件是否启用 enabled: boolean;
icon?: string; // 插件图标URL可选 icon?: string;
managePath?: string; // 插件管理页面路径(可选) managePath?: string;
configuration?: PluginConfiguration; // 插件配置 configuration?: Configuration;
routs: Set<{ routs: Set<{
description?: string; // 路由描述(可选) description?: string;
path: string; // 路由路径 path: string;
}>; }>;
} }
/**
*
*
*
*/
export interface PluginConfiguration {
[key: string]: {
title: string; // 属性标题
description?: string; // 属性描述(可选)
data: SerializeType; // 额外数据(可选),支持序列化
};
}

View File

@ -1,17 +1,11 @@
export interface TemplateContract { export interface TemplateContract {
// 模板名称
name: string; name: string;
// 模板描述
description?: string; description?: string;
// 模板配置
config: { config: {
// 模板布局
layout?: string; layout?: string;
// 模板样式
styles?: string[]; styles?: string[];
// 模板脚本
scripts?: string[]; scripts?: string[];
}; };
// 渲染函数 loader: () => Promise<void>;
render: (props: any) => React.ReactNode; element: () => React.ReactNode;
} }

View File

@ -1,50 +1,29 @@
// File path: contracts\themeTypeContract.ts import { Configuration } from "contracts/generalContract";
/**
*
*
*/
/**
*
*
*/
import { SerializeType } from "contracts/generalContract";
export interface ThemeConfig { export interface ThemeConfig {
name: string; // 主题的名称 name: string;
displayName: string; // 主题的显示名称 displayName: string;
icon?: string; // 主题图标URL可选 icon?: string;
version: string; // 主题的版本号 version: string;
description?: string; // 主题的描述信息 description?: string;
author?: string; // 主题的作者信息 author?: string;
templates: Map<string, ThemeTemplate>; // 主题模板的映射表 templates: Map<string, ThemeTemplate>;
/** 主题全局配置 */
globalSettings?: { globalSettings?: {
layout?: string; // 主题的布局配置 layout?: string;
css?: string; // 主题的CSS配置 css?: string;
}; };
/** 主题配置文件 */ configuration: Configuration;
settingsSchema: Record<string, {
name: string; // 属性的名称
description?: string; // 属性的描述信息
data: SerializeType; // 属性的默认数据
}>;
/** 路由 */
routes: { routes: {
index: string; // 首页使用的模板 index: string;
post: string; // 文章使用的模板 post: string;
tag: string; // 标签使用的模板 tag: string;
category: string; // 分类使用的模板 category: string;
error: string; // 错误页面用的模板 error: string;
page: Map<string, string>; // 独立页面模板 page: Map<string, string>;
} }
} }
/**
*
*
*/
export interface ThemeTemplate { export interface ThemeTemplate {
path: string; // 模板文件的路径 path: string;
name: string; // 模板的名称 name: string;
description?: string; // 模板的描述信息 description?: string;
} }

View File

@ -1,40 +1,23 @@
// File path: /hooks/createServiceContext.tsx
import { createContext, useContext, ReactNode, FC } from 'react'; import { createContext, useContext, ReactNode, FC } from 'react';
/**
*
*/
type ServiceContextReturn<N extends string,T> = { type ServiceContextReturn<N extends string,T> = {
[K in `${N}Provider`]: FC<{ children: ReactNode }>; [K in `${N}Provider`]: FC<{ children: ReactNode }>;
} & { } & {
[K in `use${N}`]: () => T; [K in `use${N}`]: () => T;
}; };
/**
*
*
* @param serviceName -
* @param ServiceClass - getInstance
*/
export function createServiceContext<T, N extends string>( export function createServiceContext<T, N extends string>(
serviceName: N, serviceName: N,
getServiceInstance: () => T getServiceInstance: () => T
): ServiceContextReturn<N,T> { ): ServiceContextReturn<N,T> {
const ServiceContext = createContext<T | undefined>(undefined); const ServiceContext = createContext<T | undefined>(undefined);
/**
*
*/
const Provider: FC<{ children: ReactNode }> = ({ children }) => ( const Provider: FC<{ children: ReactNode }> = ({ children }) => (
<ServiceContext.Provider value={getServiceInstance()}> <ServiceContext.Provider value={getServiceInstance()}>
{children} {children}
</ServiceContext.Provider> </ServiceContext.Provider>
); );
/**
*
*/
const useService = (): T => { const useService = (): T => {
const context = useContext(ServiceContext); const context = useContext(ServiceContext);
if (context === undefined) { if (context === undefined) {

View File

@ -16,13 +16,6 @@ export const { ApiProvider, useApi } = createServiceContext(
"Api", () => ThemeService.getInstance(), "Api", () => ThemeService.getInstance(),
); );
// File path:hooks/servicesProvider.tsx
/**
* ServiceProvider
*
* @param children -
*/
export const ServiceProvider = ({ children }: { children: ReactNode }) => ( export const ServiceProvider = ({ children }: { children: ReactNode }) => (
<ApiProvider> <ApiProvider>
<CapabilityProvider> <CapabilityProvider>

View File

@ -1,15 +0,0 @@
// File path: hooks/useAsyncError.tsx
import { useState, useCallback } from 'react';
export function useAsyncError() {
const [, setError] = useState();
return useCallback(
(e: Error) => {
setError(() => {
throw e;
});
},
[setError],
);
}

View File

@ -1,32 +1,18 @@
// File path: /d:/data/echoes/frontend/services/apiService.ts
/**
* ApiConfig接口用于配置API服务的基本信息
*/
interface ApiConfig { interface ApiConfig {
baseURL: string; // API的基础URL baseURL: string;
timeout?: number; // 请求超时时间(可选) timeout?: number;
} }
export class ApiService { export class ApiService {
private static instance: ApiService; // ApiService的单例实例 private static instance: ApiService;
private baseURL: string; // API的基础URL private baseURL: string;
private timeout: number; // 请求超时时间 private timeout: number;
/**
* ApiService实例
* @param config ApiConfig配置对象
*/
private constructor(config: ApiConfig) { private constructor(config: ApiConfig) {
this.baseURL = config.baseURL; this.baseURL = config.baseURL;
this.timeout = config.timeout || 10000; // 默认超时时间为10000毫秒 this.timeout = config.timeout || 10000;
} }
/**
* ApiService的单例实例
* @param config ApiConfig配置对象
* @returns ApiService实例
*/
public static getInstance(config?: ApiConfig): ApiService { public static getInstance(config?: ApiConfig): ApiService {
if (!this.instance && config) { if (!this.instance && config) {
this.instance = new ApiService(config); this.instance = new ApiService(config);
@ -34,11 +20,6 @@ export class ApiService {
return this.instance; return this.instance;
} }
/**
*
* @returns Promise<string>
* @throws Error
*/
private async getSystemToken(): Promise<string> { private async getSystemToken(): Promise<string> {
const username = import.meta.env.VITE_SYSTEM_USERNAME; const username = import.meta.env.VITE_SYSTEM_USERNAME;
const password = import.meta.env.VITE_SYSTEM_PASSWORD; const password = import.meta.env.VITE_SYSTEM_PASSWORD;
@ -63,25 +44,17 @@ export class ApiService {
} }
const data = await response.text(); const data = await response.text();
return data; // Assuming the token is in the 'token' field of the response return data;
} catch (error) { } catch (error) {
console.error('Error getting system token:', error); console.error('Error getting system token:', error);
throw error; throw error;
} }
} }
/**
* API请求
* @param endpoint API端点
* @param options
* @param requiresAuth true
* @returns Promise<T> API响应数据
* @throws Error
*/
public async request<T>( public async request<T>(
endpoint: string, endpoint: string,
options: RequestInit = {}, options: RequestInit = {},
auth ?: string toekn ?: string
): Promise<T> { ): Promise<T> {
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout); const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@ -89,8 +62,8 @@ export class ApiService {
try { try {
const headers = new Headers(options.headers); const headers = new Headers(options.headers);
if (auth) { if (toekn) {
headers.set('Authorization', `Bearer ${auth}`); headers.set('Authorization', `Bearer ${toekn}`);
} }
const response = await fetch(`${this.baseURL}${endpoint}`, { const response = await fetch(`${this.baseURL}${endpoint}`, {
@ -118,4 +91,4 @@ export class ApiService {
export default ApiService.getInstance({ export default ApiService.getInstance({
baseURL: import.meta.env.VITE_API_BASE_URL, baseURL: import.meta.env.VITE_API_BASE_URL,
}); });

View File

@ -1,29 +1,14 @@
// File path: services/capabilityService.ts
/**
* CapabilityService
*
*/
import { CapabilityProps } from "contracts/capabilityContract"; import { CapabilityProps } from "contracts/capabilityContract";
export class CapabilityService { export class CapabilityService {
// 存储能力的映射,键为能力名称,值为能力源和能力属性的集合
private capabilities: Map<string, Set<{ private capabilities: Map<string, Set<{
source: string; source: string;
capability: CapabilityProps<any>}>> = new Map(); capability: CapabilityProps<any>}>> = new Map();
// CapabilityService 的唯一实例
private static instance: CapabilityService; private static instance: CapabilityService;
/**
*
*/
private constructor() { } private constructor() { }
/**
* CapabilityService
* @returns {CapabilityService} CapabilityService
*/
public static getInstance(): CapabilityService { public static getInstance(): CapabilityService {
if (!this.instance) { if (!this.instance) {
this.instance = new CapabilityService(); this.instance = new CapabilityService();
@ -31,23 +16,11 @@ export class CapabilityService {
return this.instance; return this.instance;
} }
/**
*
* @param capabilityName
* @param source
* @param capability
*/
private register(capabilityName: string, source: string, capability: CapabilityProps<any>) { private register(capabilityName: string, source: string, capability: CapabilityProps<any>) {
const handlers = this.capabilities.get(capabilityName) || new Set(); const handlers = this.capabilities.get(capabilityName) || new Set();
handlers.add({ source, capability }); handlers.add({ source, capability });
} }
/**
*
* @param capabilityName
* @param args
* @returns {Set<T>}
*/
private executeCapabilityMethod<T>(capabilityName: string, ...args: any[]): Set<T> { private executeCapabilityMethod<T>(capabilityName: string, ...args: any[]): Set<T> {
const results = new Set<T>(); const results = new Set<T>();
const handlers = this.capabilities.get(capabilityName); const handlers = this.capabilities.get(capabilityName);
@ -65,10 +38,6 @@ export class CapabilityService {
return results; return results;
} }
/**
*
* @param source
*/
private removeCapability(source: string) { private removeCapability(source: string) {
this.capabilities.forEach((capability_s, capabilityName) => { this.capabilities.forEach((capability_s, capabilityName) => {
const newHandlers = new Set( const newHandlers = new Set(
@ -78,28 +47,20 @@ export class CapabilityService {
}); });
} }
/**
*
* @param capability
*/
private removeCapabilitys(capability: string) { private removeCapabilitys(capability: string) {
this.capabilities.delete(capability); this.capabilities.delete(capability);
} }
public validateCapability(capability: CapabilityProps<any>): boolean { public validateCapability(capability: CapabilityProps<any>): boolean {
// 验证能力是否符合基本要求
if (!capability.name || !capability.execute) { if (!capability.name || !capability.execute) {
return false; return false;
} }
// 验证能力名称格式
const namePattern = /^[a-z][a-zA-Z0-9_]*$/; const namePattern = /^[a-z][a-zA-Z0-9_]*$/;
if (!namePattern.test(capability.name)) { if (!namePattern.test(capability.name)) {
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -1,4 +1,3 @@
// src/core/PluginManager.ts
import { PluginConfiguration } from 'types/pluginRequirement'; import { PluginConfiguration } from 'types/pluginRequirement';
import { Contracts } from 'contracts/capabilityContract'; import { Contracts } from 'contracts/capabilityContract';
@ -8,7 +7,6 @@ export class PluginManager {
private extensions: Map<string, ExtensionProps> = new Map(); private extensions: Map<string, ExtensionProps> = new Map();
async loadPlugins() { async loadPlugins() {
// 扫描插件目录
const pluginDirs = await this.scanPluginDirectory(); const pluginDirs = await this.scanPluginDirectory();
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
@ -16,22 +14,18 @@ export class PluginManager {
const config = await import(`@/plugins/${dir}/plugin.config.ts`); const config = await import(`@/plugins/${dir}/plugin.config.ts`);
const plugin: PluginProps = config.default; const plugin: PluginProps = config.default;
// 注册插件
this.plugins.set(plugin.name, plugin); this.plugins.set(plugin.name, plugin);
// 加载默认配置
if (plugin.settingsSchema) { if (plugin.settingsSchema) {
this.configurations.set(plugin.name, plugin.settingsSchema); this.configurations.set(plugin.name, plugin.settingsSchema);
} }
// 注册扩展
if (plugin.extensions) { if (plugin.extensions) {
Object.entries(plugin.extensions).forEach(([key, value]) => { Object.entries(plugin.extensions).forEach(([key, value]) => {
this.extensions.set(`${plugin.name}.${key}`, value.extension); this.extensions.set(`${plugin.name}.${key}`, value.extension);
}); });
} }
// 执行安装钩子
if (plugin.hooks?.onInstall) { if (plugin.hooks?.onInstall) {
await plugin.hooks.onInstall({}); await plugin.hooks.onInstall({});
} }
@ -41,24 +35,19 @@ export class PluginManager {
} }
} }
// 获取插件配置
async getPluginConfig(pluginName: string): Promise<PluginConfiguration | undefined> { async getPluginConfig(pluginName: string): Promise<PluginConfiguration | undefined> {
// 先尝试从数据库获取
const dbConfig = await this.fetchConfigFromDB(pluginName); const dbConfig = await this.fetchConfigFromDB(pluginName);
if (dbConfig) { if (dbConfig) {
return dbConfig; return dbConfig;
} }
// 返回默认配置
return this.configurations.get(pluginName); return this.configurations.get(pluginName);
} }
private async fetchConfigFromDB(pluginName: string) { private async fetchConfigFromDB(pluginName: string) {
// 实现数据库查询逻辑
return null; return null;
} }
private async scanPluginDirectory(): Promise<string[]> { private async scanPluginDirectory(): Promise<string[]> {
// 实现插件目录扫描逻辑
return []; return [];
} }
} }

View File

@ -1,88 +1,40 @@
// File path: services/routeManager.ts import React from 'react'; // Import React
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRoutes, RouteObject } from 'react-router-dom'; import { LoaderFunction, RouteObject } from 'react-router-dom';
import { ThemeService } from './themeService';
import ErrorBoundary from '../components/ErrorBoundary';
export class RouteManager { export class RouteManager {
private static instance: RouteManager; private static instance: RouteManager;
private themeService: ThemeService;
private routes: RouteObject[] = []; private routes: RouteObject[] = [];
private constructor(themeService: ThemeService) { private constructor() {}
this.themeService = themeService;
}
public static getInstance(themeService?: ThemeService): RouteManager { public static getInstance(): RouteManager {
if (!RouteManager.instance && themeService) { if (!RouteManager.instance) {
RouteManager.instance = new RouteManager(themeService); RouteManager.instance = new RouteManager();
} }
return RouteManager.instance; return RouteManager.instance;
} }
/** private register(path:string, element: React.ReactNode) {
* this.routes.push({
*/ path,
public async initialize(): Promise<void> { element,
const themeConfig = this.themeService.getThemeConfig();
if (!themeConfig) {
throw new Error('Theme configuration not loaded');
}
this.routes = [
{
path: '/',
element: this.createRouteElement(themeConfig.routes.index),
errorElement: <ErrorBoundary />,
},
{
path: '/post/:id',
element: this.createRouteElement(themeConfig.routes.post),
},
{
path: '/tag/:tag',
element: this.createRouteElement(themeConfig.routes.tag),
},
{
path: '/category/:category',
element: this.createRouteElement(themeConfig.routes.category),
},
{
path: '*',
element: this.createRouteElement(themeConfig.routes.error),
},
];
// 添加自定义页面路由
themeConfig.routes.page.forEach((template, path) => {
this.routes.push({
path,
element: this.createRouteElement(template),
});
}); });
} }
/**
* private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction) {
*/ this.routes.push({
private createRouteElement(templateName: string) { path,
return (props: any) => { element,
const template = this.themeService.getTemplate(templateName); loader?: loader
// 这里可以添加模板渲染逻辑 })
return <div dangerouslySetInnerHTML={{ __html: template }} />;
};
} }
/**
*
*/
public getRoutes(): RouteObject[] { public getRoutes(): RouteObject[] {
return this.routes; return this.routes;
} }
/**
*
*/
public addRoute(path: string, templateName: string): void { public addRoute(path: string, templateName: string): void {
this.routes.push({ this.routes.push({
path, path,

View File

@ -1,4 +1,3 @@
// File path: services/themeService.ts
import { ThemeConfig, ThemeTemplate } from "contracts/themeContract"; import { ThemeConfig, ThemeTemplate } from "contracts/themeContract";
import { ApiService } from "./apiService"; import { ApiService } from "./apiService";
@ -19,18 +18,12 @@ export class ThemeService {
return ThemeService.instance; return ThemeService.instance;
} }
/**
*
*/
public async initialize(): Promise<void> { public async initialize(): Promise<void> {
try { try {
// 从API获取当前主题配置
const themeConfig = await this.api.request<ThemeConfig>( const themeConfig = await this.api.request<ThemeConfig>(
'/theme/current', '/theme/current',
{ method: 'GET' } { method: 'GET' }
); );
// 加载主题配置
await this.loadTheme(themeConfig); await this.loadTheme(themeConfig);
} catch (error) { } catch (error) {
console.error('Failed to initialize theme:', error); console.error('Failed to initialize theme:', error);
@ -38,9 +31,6 @@ export class ThemeService {
} }
} }
/**
*
*/
private async loadTheme(config: ThemeConfig): Promise<void> { private async loadTheme(config: ThemeConfig): Promise<void> {
try { try {
this.currentTheme = config; this.currentTheme = config;
@ -51,9 +41,6 @@ export class ThemeService {
} }
} }
/**
*
*/
private async loadTemplates(): Promise<void> { private async loadTemplates(): Promise<void> {
if (!this.currentTheme) { if (!this.currentTheme) {
throw new Error('No theme configuration loaded'); throw new Error('No theme configuration loaded');
@ -70,23 +57,16 @@ export class ThemeService {
} }
}; };
// 并行加载所有模板
const loadPromises = Array.from(this.currentTheme.templates.values()) const loadPromises = Array.from(this.currentTheme.templates.values())
.map(template => loadTemplate(template)); .map(template => loadTemplate(template));
await Promise.all(loadPromises); await Promise.all(loadPromises);
} }
/**
*
*/
public getThemeConfig(): ThemeConfig | undefined { public getThemeConfig(): ThemeConfig | undefined {
return this.currentTheme; return this.currentTheme;
} }
/**
*
*/
public getTemplate(templateName: string): string { public getTemplate(templateName: string): string {
const template = this.templates.get(templateName); const template = this.templates.get(templateName);
if (!template) { if (!template) {
@ -95,9 +75,6 @@ export class ThemeService {
return template; return template;
} }
/**
*
*/
public getTemplateByRoute(route: string): string { public getTemplateByRoute(route: string): string {
if (!this.currentTheme) { if (!this.currentTheme) {
throw new Error('No theme configuration loaded'); throw new Error('No theme configuration loaded');
@ -105,7 +82,6 @@ export class ThemeService {
let templateName: string | undefined; let templateName: string | undefined;
// 检查是否是预定义路由
if (route === '/') { if (route === '/') {
templateName = this.currentTheme.routes.index; templateName = this.currentTheme.routes.index;
} else if (route.startsWith('/post/')) { } else if (route.startsWith('/post/')) {
@ -115,7 +91,6 @@ export class ThemeService {
} else if (route.startsWith('/category/')) { } else if (route.startsWith('/category/')) {
templateName = this.currentTheme.routes.category; templateName = this.currentTheme.routes.category;
} else { } else {
// 检查自定义页面路由
templateName = this.currentTheme.routes.page.get(route); templateName = this.currentTheme.routes.page.get(route);
} }
@ -126,9 +101,6 @@ export class ThemeService {
return this.getTemplate(templateName); return this.getTemplate(templateName);
} }
/**
*
*/
public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> { public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> {
try { try {
const updatedConfig = await this.api.request<ThemeConfig>( const updatedConfig = await this.api.request<ThemeConfig>(