增加readme,删除了注释
前端:完善了插件和主题的契约
This commit is contained in:
parent
c19d21ce7d
commit
7e4d9c1b48
19
README.md
19
README.md
@ -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 接口,供前端调用,实现数据交互。
|
||||
- **错误处理与加载状态管理**: 提升用户体验。
|
||||
|
@ -1,47 +1,39 @@
|
||||
// config/mod.rs
|
||||
/*
|
||||
配置文件结构和操作
|
||||
*/
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::{ env, fs};
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct Config {
|
||||
pub info: Info, // 配置信息
|
||||
pub sql_config: SqlConfig, // 关系型数据库配置
|
||||
// pub no_sql_config:NoSqlConfig, 非关系型数据库配置
|
||||
pub info: Info,
|
||||
pub sql_config: SqlConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct Info {
|
||||
pub install: bool, // 是否安装
|
||||
pub non_relational: bool, // 是否非关系型
|
||||
pub install: bool,
|
||||
pub non_relational: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct SqlConfig {
|
||||
pub db_type: String, // 数据库类型
|
||||
pub address: String, // 地址
|
||||
pub port: u32, // 端口
|
||||
pub user: String, // 用户名
|
||||
pub password: String, // 密码
|
||||
pub db_name: String, // 数据库名称
|
||||
pub db_type: String,
|
||||
pub address: String,
|
||||
pub port: u32,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
pub db_name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct NoSqlConfig {
|
||||
pub db_type: String, // 数据库类型
|
||||
pub address: String, // 地址
|
||||
pub port: u32, // 端口
|
||||
pub user: String, // 用户名
|
||||
pub password: String, // 密码
|
||||
pub db_name: String, // 数据库名称
|
||||
pub db_type: String,
|
||||
pub address: String,
|
||||
pub port: u32,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
pub db_name: String,
|
||||
}
|
||||
|
||||
|
||||
impl Config {
|
||||
/// 读取配置文件
|
||||
pub fn read() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let path = env::current_dir()?
|
||||
.join("assets")
|
||||
|
@ -1,10 +1,3 @@
|
||||
// File path: src/database/relational/mod.rs
|
||||
|
||||
/**
|
||||
本模块定义了数据库的特征和方法,包括查询构建器和数据库连接。
|
||||
提供了对不同类型数据库的支持,如PostgreSQL。
|
||||
*/
|
||||
|
||||
mod postgresql;
|
||||
use std::collections::HashMap;
|
||||
use crate::config;
|
||||
@ -14,70 +7,42 @@ use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SqlOperation {
|
||||
Select, // 查询操作
|
||||
Insert, // 插入操作
|
||||
Update, // 更新操作
|
||||
Delete, // 删除操作
|
||||
Select,
|
||||
Insert,
|
||||
Update,
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// 查询构建器结构
|
||||
pub struct QueryBuilder {
|
||||
operation: SqlOperation, // SQL操作类型
|
||||
table: String, // 表名
|
||||
fields: Vec<String>, // 查询字段
|
||||
params: HashMap<String, String>, // 插入或更新的参数
|
||||
where_conditions: HashMap<String, String>, // WHERE条件
|
||||
order_by: Option<String>, // 排序字段
|
||||
limit: Option<i32>, // 限制返回的记录数
|
||||
operation: SqlOperation,
|
||||
table: String,
|
||||
fields: Vec<String>,
|
||||
params: HashMap<String, String>,
|
||||
where_conditions: HashMap<String, String>,
|
||||
order_by: Option<String>,
|
||||
limit: Option<i32>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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;
|
||||
|
||||
/**
|
||||
执行查询
|
||||
@param query SQL查询语句
|
||||
@return Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> 返回查询结果或错误
|
||||
*/
|
||||
async fn execute_query<'a>(
|
||||
&'a self,
|
||||
builder: &QueryBuilder,
|
||||
) -> 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;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database {
|
||||
// 数据库实例
|
||||
pub db: Arc<Box<dyn DatabaseTrait>>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/**
|
||||
获取当前数据库实例
|
||||
@return &Box<dyn DatabaseTrait> 返回数据库实例的引用
|
||||
*/
|
||||
pub fn get_db(&self) -> &Box<dyn DatabaseTrait> {
|
||||
&self.db
|
||||
}
|
||||
|
||||
/**
|
||||
初始化数据库
|
||||
@param database 数据库配置
|
||||
@return 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() {
|
||||
"postgresql" => postgresql::Postgresql::connect(database).await?,
|
||||
@ -87,11 +52,6 @@ impl Database {
|
||||
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>> {
|
||||
match database.db_type.as_str() {
|
||||
"postgresql" => postgresql::Postgresql::initialization(database).await?,
|
||||
@ -102,12 +62,6 @@ impl Database {
|
||||
}
|
||||
|
||||
impl QueryBuilder {
|
||||
/**
|
||||
创建新的查询构建器
|
||||
@param operation SQL操作类型
|
||||
@param table 表名
|
||||
@return Self 返回新的查询构建器实例
|
||||
*/
|
||||
pub fn new(operation: SqlOperation, table: &str) -> Self {
|
||||
QueryBuilder {
|
||||
operation,
|
||||
@ -120,10 +74,6 @@ impl QueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
构建SQL语句和参数
|
||||
@return (String, Vec<String>) 返回构建的SQL语句和参数值
|
||||
*/
|
||||
pub fn build(&self) -> (String, Vec<String>) {
|
||||
let mut query = String::new();
|
||||
let mut values = Vec::new();
|
||||
@ -131,7 +81,6 @@ impl QueryBuilder {
|
||||
|
||||
match self.operation {
|
||||
SqlOperation::Select => {
|
||||
// SELECT 操作
|
||||
let fields = if self.fields.is_empty() {
|
||||
"*".to_string()
|
||||
} else {
|
||||
@ -140,7 +89,6 @@ impl QueryBuilder {
|
||||
|
||||
query.push_str(&format!("SELECT {} FROM {}", fields, self.table));
|
||||
|
||||
// 添加 WHERE 条件
|
||||
if !self.where_conditions.is_empty() {
|
||||
let conditions: Vec<String> = self.where_conditions
|
||||
.iter()
|
||||
@ -156,7 +104,6 @@ impl QueryBuilder {
|
||||
}
|
||||
},
|
||||
SqlOperation::Insert => {
|
||||
// INSERT 操作
|
||||
let fields: Vec<String> = self.params.keys().cloned().collect();
|
||||
let placeholders: Vec<String> = (1..=self.params.len())
|
||||
.map(|i| format!("${}", i))
|
||||
@ -169,13 +116,11 @@ impl QueryBuilder {
|
||||
placeholders.join(", ")
|
||||
));
|
||||
|
||||
// 收集参数值
|
||||
for field in fields {
|
||||
values.push(self.params[&field].clone());
|
||||
}
|
||||
},
|
||||
SqlOperation::Update => {
|
||||
// UPDATE 操作
|
||||
query.push_str(&format!("UPDATE {}", self.table));
|
||||
|
||||
let set_clauses: Vec<String> = self.params
|
||||
@ -191,7 +136,6 @@ impl QueryBuilder {
|
||||
query.push_str(" SET ");
|
||||
query.push_str(&set_clauses.join(", "));
|
||||
|
||||
// 添加 WHERE 条件
|
||||
if !self.where_conditions.is_empty() {
|
||||
let conditions: Vec<String> = self.where_conditions
|
||||
.iter()
|
||||
@ -207,10 +151,8 @@ impl QueryBuilder {
|
||||
}
|
||||
},
|
||||
SqlOperation::Delete => {
|
||||
// DELETE 操作
|
||||
query.push_str(&format!("DELETE FROM {}", self.table));
|
||||
|
||||
// 添加 WHERE 条件
|
||||
if !self.where_conditions.is_empty() {
|
||||
let conditions: Vec<String> = self.where_conditions
|
||||
.iter()
|
||||
@ -227,12 +169,10 @@ impl QueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 ORDER BY
|
||||
if let Some(order) = &self.order_by {
|
||||
query.push_str(&format!(" ORDER BY {}", order));
|
||||
}
|
||||
|
||||
// 添加 LIMIT
|
||||
if let Some(limit) = self.limit {
|
||||
query.push_str(&format!(" LIMIT {}", limit));
|
||||
}
|
||||
@ -240,51 +180,26 @@ impl QueryBuilder {
|
||||
(query, values)
|
||||
}
|
||||
|
||||
/**
|
||||
设置查询字段
|
||||
@param fields 字段列表
|
||||
@return &mut Self 返回可变引用以便链式调用
|
||||
*/
|
||||
pub fn fields(&mut self, fields: Vec<String>) -> &mut Self {
|
||||
self.fields = fields;
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
设置参数
|
||||
@param params 参数键值对
|
||||
@return &mut Self 返回可变引用以便链式调用
|
||||
*/
|
||||
pub fn params(&mut self, params: HashMap<String, String>) -> &mut Self {
|
||||
self.params = params;
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
设置WHERE条件
|
||||
@param conditions 条件键值对
|
||||
@return &mut Self 返回可变引用以便链式调用
|
||||
*/
|
||||
pub fn where_conditions(&mut self, conditions: HashMap<String, String>) -> &mut Self {
|
||||
self.where_conditions = conditions;
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
设置排序
|
||||
@param order 排序字段
|
||||
@return &mut Self 返回可变引用以便链式调用
|
||||
*/
|
||||
pub fn order_by(&mut self, order: &str) -> &mut Self {
|
||||
self.order_by = Some(order.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/**
|
||||
设置限制
|
||||
@param limit 限制记录数
|
||||
@return &mut Self 返回可变引用以便链式调用
|
||||
*/
|
||||
pub fn limit(&mut self, limit: i32) -> &mut Self {
|
||||
self.limit = Some(limit);
|
||||
self
|
||||
|
@ -1,99 +1,84 @@
|
||||
--- 安装自动生成uuid插件
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
--- 用户权限枚举
|
||||
CREATE TYPE privilege_level AS ENUM ( 'contributor', 'administrators');
|
||||
--- 用户信息表
|
||||
CREATE TABLE persons
|
||||
(
|
||||
person_name VARCHAR(100) PRIMARY KEY, --- 用户名
|
||||
person_email VARCHAR(255) UNIQUE NOT NULL, --- 用户邮箱
|
||||
person_icon VARCHAR(255), --- 用户头像
|
||||
person_password VARCHAR(255) NOT NULL, --- 用户密码
|
||||
person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户创建时间
|
||||
person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户更新时间
|
||||
person_avatar VARCHAR(255), --- 用户头像URL
|
||||
person_role VARCHAR(50), --- 用户角色
|
||||
person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 最后登录时间
|
||||
person_level privilege_level NOT NULL DEFAULT 'contributor' --- 用户权限
|
||||
person_name VARCHAR(100) PRIMARY KEY,
|
||||
person_email VARCHAR(255) UNIQUE NOT NULL,
|
||||
person_icon VARCHAR(255),
|
||||
person_password VARCHAR(255) NOT NULL,
|
||||
person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
person_avatar VARCHAR(255),
|
||||
person_role VARCHAR(50),
|
||||
person_last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
person_level privilege_level NOT NULL DEFAULT 'contributor'
|
||||
);
|
||||
--- 页面状态枚举
|
||||
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描述
|
||||
page_title VARCHAR(255) NOT NULL, --- 文章标题
|
||||
page_content TEXT NOT NULL, --- 独立页面内容
|
||||
page_mould VARCHAR(50), --- 独立页面模板名称
|
||||
page_fields JSON, --- 自定义字段
|
||||
page_status publication_status DEFAULT 'draft' --- 文章状态
|
||||
page_id SERIAL PRIMARY KEY,
|
||||
page_meta_keywords VARCHAR(255) NOT NULL,
|
||||
page_meta_description VARCHAR(255) NOT NULL,
|
||||
page_title VARCHAR(255) NOT NULL,
|
||||
page_content TEXT NOT NULL,
|
||||
page_mould VARCHAR(50),
|
||||
page_fields JSON,
|
||||
page_status publication_status DEFAULT 'draft'
|
||||
);
|
||||
--- 文章表
|
||||
CREATE TABLE posts
|
||||
(
|
||||
post_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE, --- 文章作者
|
||||
post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), --- 文章主键
|
||||
post_picture VARCHAR(255), --- 文章头图
|
||||
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, --- 文章是否编辑未保存
|
||||
post_unsaved_content TEXT, --- 未保存的文章
|
||||
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) --- 文章时间约束
|
||||
post_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
|
||||
post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
post_picture VARCHAR(255),
|
||||
post_title VARCHAR(255) NOT NULL,
|
||||
post_meta_keywords VARCHAR(255) NOT NULL,
|
||||
post_meta_description VARCHAR(255) NOT NULL,
|
||||
post_content TEXT NOT NULL,
|
||||
post_status publication_status DEFAULT 'draft',
|
||||
post_editor BOOLEAN DEFAULT FALSE,
|
||||
post_unsaved_content TEXT,
|
||||
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), --- 标签名称主键
|
||||
tag_icon VARCHAR(255) --- 标签图标
|
||||
tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name),
|
||||
tag_icon VARCHAR(255)
|
||||
);
|
||||
--- 文章与标签的关系表
|
||||
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) --- 复合主键
|
||||
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
|
||||
tag_name VARCHAR(50) REFERENCES tags (tag_name) ON DELETE CASCADE,
|
||||
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
|
||||
category_name VARCHAR(50) PRIMARY KEY,
|
||||
parent_category_name VARCHAR(50),
|
||||
FOREIGN KEY (parent_category_name) REFERENCES categories (category_name)
|
||||
);
|
||||
--- 文章与分类的关系表
|
||||
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) --- 复合主键
|
||||
post_id UUID REFERENCES posts (post_id) ON DELETE CASCADE,
|
||||
category_id VARCHAR(50) REFERENCES categories (category_name) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, category_id)
|
||||
);
|
||||
|
||||
--- 资源库
|
||||
CREATE TABLE library
|
||||
(
|
||||
library_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE, --- 资源作者
|
||||
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, --- 资源类别
|
||||
library_class VARCHAR(50), --- 资源类
|
||||
library_description VARCHAR(255), --- 资源描述
|
||||
library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP --- 创建时间
|
||||
|
||||
library_author VARCHAR(100) NOT NULL REFERENCES persons (person_name) ON DELETE CASCADE,
|
||||
library_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
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,
|
||||
library_class VARCHAR(50),
|
||||
library_description VARCHAR(255),
|
||||
library_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
--- 配置文件库
|
||||
CREATE TABLE config
|
||||
(
|
||||
config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name), --- 配置文件名称
|
||||
config_config JSON --- 配置文件
|
||||
)
|
||||
config_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(config_name) = config_name),
|
||||
config_config JSON
|
||||
);
|
||||
|
@ -1,9 +1,3 @@
|
||||
// File path: src/database/relational/postgresql/mod.rs
|
||||
/**
|
||||
* 该模块实现了与PostgreSQL数据库的交互功能。
|
||||
* 包含连接池的结构体定义,提供数据库操作的基础。
|
||||
*/
|
||||
|
||||
use super::{DatabaseTrait, QueryBuilder};
|
||||
use crate::config;
|
||||
use async_trait::async_trait;
|
||||
@ -12,23 +6,12 @@ use std::{collections::HashMap, error::Error};
|
||||
use std::{env, fs};
|
||||
|
||||
#[derive(Clone)]
|
||||
/// PostgreSQL数据库连接池结构体
|
||||
pub struct Postgresql {
|
||||
/// 数据库连接池
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DatabaseTrait for Postgresql {
|
||||
/**
|
||||
* 初始化数据库并执行初始化脚本。
|
||||
*
|
||||
* # 参数
|
||||
* - `db_config`: 数据库配置
|
||||
*
|
||||
* # 返回
|
||||
* - `Result<(), Box<dyn Error>>`: 初始化结果
|
||||
*/
|
||||
async fn initialization(db_config: config::SqlConfig) -> Result<(), Box<dyn Error>> {
|
||||
let path = env::current_dir()?
|
||||
.join("src")
|
||||
@ -38,77 +21,49 @@ impl DatabaseTrait for Postgresql {
|
||||
.join("init.sql");
|
||||
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 pool = PgPool::connect(&connection_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_pool = PgPool::connect(&new_connection_str).await?;
|
||||
|
||||
// 执行初始化脚本
|
||||
new_pool.execute(grammar.as_str()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到PostgreSQL数据库并返回Postgresql实例。
|
||||
*
|
||||
* # 参数
|
||||
* - `db_config`: 数据库配置
|
||||
*
|
||||
* # 返回
|
||||
* - `Result<Self, Box<dyn Error>>`: 连接结果
|
||||
*/
|
||||
async fn connect(db_config: config::SqlConfig) -> Result<Self, Box<dyn Error>> {
|
||||
let connection_str = format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
db_config.user, db_config.password, db_config.address, db_config.port, db_config.db_name
|
||||
);
|
||||
|
||||
// 连接到数据库池
|
||||
let pool = PgPool::connect(&connection_str)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
|
||||
|
||||
// 返回Postgresql实例
|
||||
Ok(Postgresql { pool })
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行查询并返回结果。
|
||||
*
|
||||
* # 参数
|
||||
* - `builder`: 查询构建器
|
||||
*
|
||||
* # 返回
|
||||
* - `Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>>`: 查询结果
|
||||
*/
|
||||
async fn execute_query<'a>(
|
||||
&'a self,
|
||||
builder: &QueryBuilder,
|
||||
) -> Result<Vec<HashMap<String, String>>, Box<dyn Error + 'a>> {
|
||||
let (query, values) = builder.build();
|
||||
|
||||
// 构建查询
|
||||
let mut sqlx_query = sqlx::query(&query);
|
||||
|
||||
// 绑定参数
|
||||
for value in values {
|
||||
sqlx_query = sqlx_query.bind(value);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
let rows = sqlx_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();
|
||||
|
@ -1,71 +1,36 @@
|
||||
// File path: /d:/data/echoes/backend/src/main.rs
|
||||
mod config;
|
||||
mod database;
|
||||
mod secret;
|
||||
|
||||
/**
|
||||
* 主程序入口,提供数据库连接和相关API接口。
|
||||
*
|
||||
* 接口:
|
||||
* - GET /api/install: 测试数据库连接
|
||||
* - GET /api/sql: 执行SQL查询并返回结果
|
||||
*/
|
||||
use chrono::Duration;
|
||||
use database::relational;
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::{get, post, http::Status, launch, response::status, routes, serde::json::Json};
|
||||
use std::sync::Arc;
|
||||
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>>>> =
|
||||
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>> {
|
||||
let database = relational::Database::link(database).await?; // 初始化数据库
|
||||
*SQL.lock().await = Some(database); // 保存数据库实例
|
||||
let database = relational::Database::link(database).await?;
|
||||
*SQL.lock().await = Some(database);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库的引用
|
||||
*
|
||||
* # 返回
|
||||
* - `Result<relational::Database, Box<dyn std::error::Error>>`: 数据库引用或错误信息
|
||||
*/
|
||||
async fn get_sql() -> Result<relational::Database, Box<dyn std::error::Error>> {
|
||||
SQL.lock()
|
||||
.await
|
||||
.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>")]
|
||||
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| {
|
||||
status::Custom(
|
||||
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")]
|
||||
async fn token_system() -> Result<status::Custom<String>, status::Custom<String>> {
|
||||
// 创建 Claims
|
||||
let claims = secret::CustomClaims {
|
||||
user_id: String::from("system"), // 用户ID
|
||||
device_ua: String::from("system"), // 设备用户代理
|
||||
user_id: String::from("system"),
|
||||
device_ua: String::from("system"),
|
||||
};
|
||||
// 生成JWT
|
||||
let token = secret::generate_jwt(claims, Duration::seconds(1)).map_err(|e| {
|
||||
status::Custom(
|
||||
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]
|
||||
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 {
|
||||
init_sql(config.sql_config)
|
||||
.await
|
||||
.expect("Failed to connect to database"); // 初始化数据库连接
|
||||
.expect("Failed to connect to database");
|
||||
rocket::build()
|
||||
.mount("/auth/token", routes![token_system])
|
||||
} else {
|
||||
rocket::build()
|
||||
.mount("/", routes![install]) // 挂载API路由
|
||||
.mount("/", routes![install])
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,3 @@
|
||||
// File path: src/secret.rs
|
||||
|
||||
/**
|
||||
* 本文件包含JWT的生成和验证功能。
|
||||
* 提供了生成密钥、生成JWT、验证JWT的相关函数。
|
||||
*/
|
||||
|
||||
use jwt_compact::{alg::Ed25519, AlgorithmExt, Header, Token, UntrustedToken, TimeOptions};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use chrono::{Duration, Utc};
|
||||
@ -15,16 +8,15 @@ use std::{env, fs};
|
||||
use std::error::Error;
|
||||
use rand::{SeedableRng, RngCore};
|
||||
|
||||
// 定义JWT的Claims结构体(有效载荷)
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CustomClaims {
|
||||
pub user_id: String, // 用户ID
|
||||
pub device_ua: String, // 用户UA
|
||||
pub user_id: String,
|
||||
pub device_ua: String,
|
||||
}
|
||||
|
||||
pub enum SecretKey {
|
||||
Signing, // 签名密钥
|
||||
Verifying, // 验证密钥
|
||||
Signing,
|
||||
Verifying,
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
@ -36,84 +28,72 @@ impl SecretKey {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名密钥和验证密钥,并将其保存到文件中。
|
||||
*/
|
||||
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]; // 存储签名密钥的字节数组
|
||||
csprng.fill_bytes(&mut private_key_bytes); // 生成签名密钥
|
||||
let mut private_key_bytes = [0u8; 32];
|
||||
csprng.fill_bytes(&mut private_key_bytes);
|
||||
|
||||
let signing_key = SigningKey::from_bytes(&private_key_bytes); // 从签名密钥获取SigningKey
|
||||
let verifying_key = signing_key.verifying_key(); // 获取验证密钥
|
||||
let signing_key = SigningKey::from_bytes(&private_key_bytes);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
let base_path = env::current_dir()?
|
||||
.join("assets")
|
||||
.join("key");
|
||||
|
||||
fs::create_dir_all(&base_path)?; // 创建目录
|
||||
fs::create_dir_all(&base_path)?;
|
||||
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()))?
|
||||
.write_all(verifying_key.as_bytes())?; // 保存验证密钥
|
||||
.write_all(verifying_key.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件中读取指定类型的密钥。
|
||||
*/
|
||||
pub fn get_key(key_type: SecretKey) -> Result<[u8; 32], Box<dyn Error>> {
|
||||
let path = env::current_dir()?
|
||||
.join("assets")
|
||||
.join("key")
|
||||
.join(key_type.as_string());
|
||||
let key_bytes = fs::read(path)?; // 读取密钥文件
|
||||
let mut key = [0u8; 32]; // 固定长度的数组
|
||||
key.copy_from_slice(&key_bytes[..32]); // 拷贝前32个字节
|
||||
let key_bytes = fs::read(path)?;
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(&key_bytes[..32]);
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT,包含自定义声明和有效期。
|
||||
*/
|
||||
pub fn generate_jwt(claims: CustomClaims, duration: Duration) -> Result<String, Box<dyn Error>> {
|
||||
let key_bytes = get_key(SecretKey::Signing)?; // 从文件读取私钥
|
||||
let signing_key = SigningKey::from_bytes(&key_bytes); // 创建SigningKey
|
||||
let key_bytes = get_key(SecretKey::Signing)?;
|
||||
let signing_key = SigningKey::from_bytes(&key_bytes);
|
||||
|
||||
let time_options = TimeOptions::new(
|
||||
Duration::seconds(0), // 设置时间容差为0
|
||||
Utc::now // 使用当前UTC时间作为时钟函数
|
||||
); // 设置时间容差为); // 默认时间选项
|
||||
let claims = jwt_compact::Claims::new(claims) // 创建JWT的有效载荷
|
||||
Duration::seconds(0),
|
||||
Utc::now
|
||||
);
|
||||
let claims = jwt_compact::Claims::new(claims)
|
||||
.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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT并返回自定义声明。
|
||||
*/
|
||||
pub fn validate_jwt(token: &str) -> Result<CustomClaims, Box<dyn Error>> {
|
||||
let key_bytes = get_key(SecretKey::Verifying)?; // 从文件读取验证密钥
|
||||
let verifying = VerifyingKey::from_bytes(&key_bytes)?; // 创建VerifyingKey
|
||||
let token = UntrustedToken::new(token)?; // 创建未受信任的Token
|
||||
let token: Token<CustomClaims> = Ed25519.validator(&verifying).validate(&token)?; // 验证Token
|
||||
let key_bytes = get_key(SecretKey::Verifying)?;
|
||||
let verifying = VerifyingKey::from_bytes(&key_bytes)?;
|
||||
let token = UntrustedToken::new(token)?;
|
||||
let token: Token<CustomClaims> = Ed25519.validator(&verifying).validate(&token)?;
|
||||
|
||||
let time_options = TimeOptions::new(
|
||||
Duration::seconds(0), // 设置时间容差为0
|
||||
Utc::now // 使用当前UTC时间作为时钟函数
|
||||
); // 设置时间容差为); // 默认时间选项
|
||||
Duration::seconds(0),
|
||||
Utc::now
|
||||
);
|
||||
token.claims()
|
||||
.validate_expiration(&time_options)? // 验证过期时间
|
||||
.validate_maturity(&time_options)?; // 验证成熟时间
|
||||
let claims = token.claims().custom.clone(); // 获取自定义声明
|
||||
.validate_expiration(&time_options)?
|
||||
.validate_maturity(&time_options)?;
|
||||
let claims = token.claims().custom.clone();
|
||||
|
||||
Ok(claims)
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
// File path: components/ErrorBoundary.tsx
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { ThemeService } from '../services/themeService';
|
||||
|
||||
@ -28,11 +27,9 @@ export default class ErrorBoundary extends Component<Props, State> {
|
||||
if (this.state.hasError) {
|
||||
const themeService = ThemeService.getInstance();
|
||||
try {
|
||||
// 尝试使用主题的错误模板
|
||||
const errorTemplate = themeService.getTemplate('error');
|
||||
return <div dangerouslySetInnerHTML={{ __html: errorTemplate }} />;
|
||||
} catch (e) {
|
||||
// 如果无法获取主题模板,显示默认错误页面
|
||||
return (
|
||||
<div className="error-page">
|
||||
<h1>Something went wrong</h1>
|
||||
|
@ -1,4 +1,3 @@
|
||||
// File path: components/LoadingBoundary.tsx
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
interface LoadingBoundaryProps {
|
||||
|
@ -1,13 +1,5 @@
|
||||
// src/contracts/CapabilityContract.ts
|
||||
/**
|
||||
* 能力契约接口
|
||||
*/
|
||||
export interface CapabilityProps<T> {
|
||||
// 能力名称
|
||||
name: string;
|
||||
// 能力描述
|
||||
description?: string;
|
||||
// 能力执行函数
|
||||
execute: (...args: any[]) => Promise<T>;
|
||||
}
|
||||
|
@ -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 interface Configuration {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
description?: string;
|
||||
data: SerializeType;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,40 +1,18 @@
|
||||
// File path: contracts\pluginContract.ts
|
||||
|
||||
/**
|
||||
* 插件配置接口
|
||||
*
|
||||
* 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。
|
||||
* 还包括插件的生命周期钩子和依赖项的配置。
|
||||
*/
|
||||
import { SerializeType } from "contracts/generalContract";
|
||||
|
||||
import { Configuration } from "contracts/generalContract";
|
||||
|
||||
export interface PluginConfig {
|
||||
name: string; // 插件名称
|
||||
version: string; // 插件版本
|
||||
displayName: string; // 插件显示名称
|
||||
description?: string; // 插件描述(可选)
|
||||
author?: string; // 插件作者(可选)
|
||||
enabled: boolean; // 插件是否启用
|
||||
icon?: string; // 插件图标URL(可选)
|
||||
managePath?: string; // 插件管理页面路径(可选)
|
||||
configuration?: PluginConfiguration; // 插件配置
|
||||
name: string;
|
||||
version: string;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
enabled: boolean;
|
||||
icon?: string;
|
||||
managePath?: string;
|
||||
configuration?: Configuration;
|
||||
routs: Set<{
|
||||
description?: string; // 路由描述(可选)
|
||||
path: string; // 路由路径
|
||||
description?: string;
|
||||
path: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件配置接口
|
||||
*
|
||||
* 该接口定义了插件的配置类型及其属性。
|
||||
*/
|
||||
export interface PluginConfiguration {
|
||||
[key: string]: {
|
||||
title: string; // 属性标题
|
||||
description?: string; // 属性描述(可选)
|
||||
data: SerializeType; // 额外数据(可选),支持序列化
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,11 @@
|
||||
export interface TemplateContract {
|
||||
// 模板名称
|
||||
name: string;
|
||||
// 模板描述
|
||||
description?: string;
|
||||
// 模板配置
|
||||
config: {
|
||||
// 模板布局
|
||||
layout?: string;
|
||||
// 模板样式
|
||||
styles?: string[];
|
||||
// 模板脚本
|
||||
scripts?: string[];
|
||||
};
|
||||
// 渲染函数
|
||||
render: (props: any) => React.ReactNode;
|
||||
}
|
||||
loader: () => Promise<void>;
|
||||
element: () => React.ReactNode;
|
||||
}
|
@ -1,50 +1,29 @@
|
||||
// File path: contracts\themeTypeContract.ts
|
||||
/**
|
||||
* 主题配置和模板接口定义文件
|
||||
* 该文件包含主题配置接口和主题模板接口的定义,用于主题管理和渲染。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 主题配置接口
|
||||
* 定义主题的基本信息、模板、全局配置、依赖、钩子和路由。
|
||||
*/
|
||||
import { SerializeType } from "contracts/generalContract";
|
||||
import { Configuration } from "contracts/generalContract";
|
||||
export interface ThemeConfig {
|
||||
name: string; // 主题的名称
|
||||
displayName: string; // 主题的显示名称
|
||||
icon?: string; // 主题图标URL(可选)
|
||||
version: string; // 主题的版本号
|
||||
description?: string; // 主题的描述信息
|
||||
author?: string; // 主题的作者信息
|
||||
templates: Map<string, ThemeTemplate>; // 主题模板的映射表
|
||||
/** 主题全局配置 */
|
||||
name: string;
|
||||
displayName: string;
|
||||
icon?: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
templates: Map<string, ThemeTemplate>;
|
||||
globalSettings?: {
|
||||
layout?: string; // 主题的布局配置
|
||||
css?: string; // 主题的CSS配置
|
||||
layout?: string;
|
||||
css?: string;
|
||||
};
|
||||
/** 主题配置文件 */
|
||||
settingsSchema: Record<string, {
|
||||
name: string; // 属性的名称
|
||||
description?: string; // 属性的描述信息
|
||||
data: SerializeType; // 属性的默认数据
|
||||
}>;
|
||||
/** 路由 */
|
||||
configuration: Configuration;
|
||||
routes: {
|
||||
index: string; // 首页使用的模板
|
||||
post: string; // 文章使用的模板
|
||||
tag: string; // 标签使用的模板
|
||||
category: string; // 分类使用的模板
|
||||
error: string; // 错误页面用的模板
|
||||
page: Map<string, string>; // 独立页面模板
|
||||
index: string;
|
||||
post: string;
|
||||
tag: string;
|
||||
category: string;
|
||||
error: string;
|
||||
page: Map<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题模板接口
|
||||
* 定义主题模板的基本信息,包括路径、名称和描述。
|
||||
*/
|
||||
export interface ThemeTemplate {
|
||||
path: string; // 模板文件的路径
|
||||
name: string; // 模板的名称
|
||||
description?: string; // 模板的描述信息
|
||||
path: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
@ -1,40 +1,23 @@
|
||||
// File path: /hooks/createServiceContext.tsx
|
||||
|
||||
import { createContext, useContext, ReactNode, FC } from 'react';
|
||||
|
||||
/**
|
||||
* 创建服务上下文的返回类型
|
||||
*/
|
||||
type ServiceContextReturn<N extends string,T> = {
|
||||
[K in `${N}Provider`]: FC<{ children: ReactNode }>;
|
||||
} & {
|
||||
[K in `use${N}`]: () => T;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建服务上下文和相关钩子的工厂函数
|
||||
*
|
||||
* @param serviceName - 服务名称,用于错误信息
|
||||
* @param ServiceClass - 服务类(包含静态 getInstance 方法)
|
||||
*/
|
||||
export function createServiceContext<T, N extends string>(
|
||||
serviceName: N,
|
||||
getServiceInstance: () => T
|
||||
): ServiceContextReturn<N,T> {
|
||||
const ServiceContext = createContext<T | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* 服务提供者组件
|
||||
*/
|
||||
const Provider: FC<{ children: ReactNode }> = ({ children }) => (
|
||||
<ServiceContext.Provider value={getServiceInstance()}>
|
||||
{children}
|
||||
</ServiceContext.Provider>
|
||||
);
|
||||
|
||||
/**
|
||||
* 自定义钩子,用于获取服务实例
|
||||
*/
|
||||
const useService = (): T => {
|
||||
const context = useContext(ServiceContext);
|
||||
if (context === undefined) {
|
||||
|
@ -16,13 +16,6 @@ export const { ApiProvider, useApi } = createServiceContext(
|
||||
"Api", () => ThemeService.getInstance(),
|
||||
);
|
||||
|
||||
|
||||
// File path:hooks/servicesProvider.tsx
|
||||
/**
|
||||
* ServiceProvider 组件用于提供扩展和主题上下文给其子组件。
|
||||
*
|
||||
* @param children - 要渲染的子组件。
|
||||
*/
|
||||
export const ServiceProvider = ({ children }: { children: ReactNode }) => (
|
||||
<ApiProvider>
|
||||
<CapabilityProvider>
|
||||
|
@ -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],
|
||||
);
|
||||
}
|
@ -1,32 +1,18 @@
|
||||
// File path: /d:/data/echoes/frontend/services/apiService.ts
|
||||
|
||||
/**
|
||||
* ApiConfig接口用于配置API服务的基本信息。
|
||||
*/
|
||||
interface ApiConfig {
|
||||
baseURL: string; // API的基础URL
|
||||
timeout?: number; // 请求超时时间(可选)
|
||||
baseURL: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export class ApiService {
|
||||
private static instance: ApiService; // ApiService的单例实例
|
||||
private baseURL: string; // API的基础URL
|
||||
private timeout: number; // 请求超时时间
|
||||
private static instance: ApiService;
|
||||
private baseURL: string;
|
||||
private timeout: number;
|
||||
|
||||
/**
|
||||
* 构造函数用于初始化ApiService实例。
|
||||
* @param config ApiConfig配置对象
|
||||
*/
|
||||
private constructor(config: ApiConfig) {
|
||||
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 {
|
||||
if (!this.instance && config) {
|
||||
this.instance = new ApiService(config);
|
||||
@ -34,11 +20,6 @@ export class ApiService {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统令牌。
|
||||
* @returns Promise<string> 返回系统令牌
|
||||
* @throws Error 如果未找到凭据或请求失败
|
||||
*/
|
||||
private async getSystemToken(): Promise<string> {
|
||||
const username = import.meta.env.VITE_SYSTEM_USERNAME;
|
||||
const password = import.meta.env.VITE_SYSTEM_PASSWORD;
|
||||
@ -63,25 +44,17 @@ export class ApiService {
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
return data; // Assuming the token is in the 'token' field of the response
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error getting system token:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起API请求。
|
||||
* @param endpoint 请求的API端点
|
||||
* @param options 请求选项
|
||||
* @param requiresAuth 是否需要身份验证(默认为true)
|
||||
* @returns Promise<T> 返回API响应数据
|
||||
* @throws Error 如果请求超时或发生其他错误
|
||||
*/
|
||||
public async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {},
|
||||
auth ?: string
|
||||
toekn ?: string
|
||||
): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
@ -89,8 +62,8 @@ export class ApiService {
|
||||
try {
|
||||
const headers = new Headers(options.headers);
|
||||
|
||||
if (auth) {
|
||||
headers.set('Authorization', `Bearer ${auth}`);
|
||||
if (toekn) {
|
||||
headers.set('Authorization', `Bearer ${toekn}`);
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
|
@ -1,29 +1,14 @@
|
||||
// File path: services/capabilityService.ts
|
||||
|
||||
/**
|
||||
* CapabilityService 是一个单例类,用于管理能力的实例。
|
||||
* 提供注册、执行和移除能力的功能。
|
||||
*/
|
||||
import { CapabilityProps } from "contracts/capabilityContract";
|
||||
|
||||
export class CapabilityService {
|
||||
// 存储能力的映射,键为能力名称,值为能力源和能力属性的集合
|
||||
private capabilities: Map<string, Set<{
|
||||
source: string;
|
||||
capability: CapabilityProps<any>}>> = new Map();
|
||||
|
||||
// CapabilityService 的唯一实例
|
||||
private static instance: CapabilityService;
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止外部实例化
|
||||
*/
|
||||
private constructor() { }
|
||||
|
||||
/**
|
||||
* 获取 CapabilityService 的唯一实例。
|
||||
* @returns {CapabilityService} 返回 CapabilityService 的唯一实例。
|
||||
*/
|
||||
public static getInstance(): CapabilityService {
|
||||
if (!this.instance) {
|
||||
this.instance = new CapabilityService();
|
||||
@ -31,23 +16,11 @@ export class CapabilityService {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册能力
|
||||
* @param capabilityName 能力名称
|
||||
* @param source 能力来源
|
||||
* @param capability 能力属性
|
||||
*/
|
||||
private register(capabilityName: string, source: string, capability: CapabilityProps<any>) {
|
||||
const handlers = this.capabilities.get(capabilityName) || new Set();
|
||||
handlers.add({ source, capability });
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行指定能力的方法
|
||||
* @param capabilityName 能力名称
|
||||
* @param args 方法参数
|
||||
* @returns {Set<T>} 执行结果的集合
|
||||
*/
|
||||
private executeCapabilityMethod<T>(capabilityName: string, ...args: any[]): Set<T> {
|
||||
const results = new Set<T>();
|
||||
const handlers = this.capabilities.get(capabilityName);
|
||||
@ -65,10 +38,6 @@ export class CapabilityService {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定来源的能力
|
||||
* @param source 能力来源
|
||||
*/
|
||||
private removeCapability(source: string) {
|
||||
this.capabilities.forEach((capability_s, capabilityName) => {
|
||||
const newHandlers = new Set(
|
||||
@ -78,21 +47,15 @@ export class CapabilityService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定能力
|
||||
* @param capability 能力名称
|
||||
*/
|
||||
private removeCapabilitys(capability: string) {
|
||||
this.capabilities.delete(capability);
|
||||
}
|
||||
|
||||
public validateCapability(capability: CapabilityProps<any>): boolean {
|
||||
// 验证能力是否符合基本要求
|
||||
if (!capability.name || !capability.execute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证能力名称格式
|
||||
const namePattern = /^[a-z][a-zA-Z0-9_]*$/;
|
||||
if (!namePattern.test(capability.name)) {
|
||||
return false;
|
||||
@ -100,6 +63,4 @@ export class CapabilityService {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
// src/core/PluginManager.ts
|
||||
import { PluginConfiguration } from 'types/pluginRequirement';
|
||||
import { Contracts } from 'contracts/capabilityContract';
|
||||
|
||||
@ -8,7 +7,6 @@ export class PluginManager {
|
||||
private extensions: Map<string, ExtensionProps> = new Map();
|
||||
|
||||
async loadPlugins() {
|
||||
// 扫描插件目录
|
||||
const pluginDirs = await this.scanPluginDirectory();
|
||||
|
||||
for (const dir of pluginDirs) {
|
||||
@ -16,22 +14,18 @@ export class PluginManager {
|
||||
const config = await import(`@/plugins/${dir}/plugin.config.ts`);
|
||||
const plugin: PluginProps = config.default;
|
||||
|
||||
// 注册插件
|
||||
this.plugins.set(plugin.name, plugin);
|
||||
|
||||
// 加载默认配置
|
||||
if (plugin.settingsSchema) {
|
||||
this.configurations.set(plugin.name, plugin.settingsSchema);
|
||||
}
|
||||
|
||||
// 注册扩展
|
||||
if (plugin.extensions) {
|
||||
Object.entries(plugin.extensions).forEach(([key, value]) => {
|
||||
this.extensions.set(`${plugin.name}.${key}`, value.extension);
|
||||
});
|
||||
}
|
||||
|
||||
// 执行安装钩子
|
||||
if (plugin.hooks?.onInstall) {
|
||||
await plugin.hooks.onInstall({});
|
||||
}
|
||||
@ -41,24 +35,19 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取插件配置
|
||||
async getPluginConfig(pluginName: string): Promise<PluginConfiguration | undefined> {
|
||||
// 先尝试从数据库获取
|
||||
const dbConfig = await this.fetchConfigFromDB(pluginName);
|
||||
if (dbConfig) {
|
||||
return dbConfig;
|
||||
}
|
||||
// 返回默认配置
|
||||
return this.configurations.get(pluginName);
|
||||
}
|
||||
|
||||
private async fetchConfigFromDB(pluginName: string) {
|
||||
// 实现数据库查询逻辑
|
||||
return null;
|
||||
}
|
||||
|
||||
private async scanPluginDirectory(): Promise<string[]> {
|
||||
// 实现插件目录扫描逻辑
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,88 +1,40 @@
|
||||
// File path: services/routeManager.ts
|
||||
import React from 'react'; // Import React
|
||||
import { useEffect } from 'react';
|
||||
import { useRoutes, RouteObject } from 'react-router-dom';
|
||||
import { ThemeService } from './themeService';
|
||||
import ErrorBoundary from '../components/ErrorBoundary';
|
||||
import { LoaderFunction, RouteObject } from 'react-router-dom';
|
||||
|
||||
export class RouteManager {
|
||||
private static instance: RouteManager;
|
||||
private themeService: ThemeService;
|
||||
private routes: RouteObject[] = [];
|
||||
|
||||
private constructor(themeService: ThemeService) {
|
||||
this.themeService = themeService;
|
||||
}
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(themeService?: ThemeService): RouteManager {
|
||||
if (!RouteManager.instance && themeService) {
|
||||
RouteManager.instance = new RouteManager(themeService);
|
||||
public static getInstance(): RouteManager {
|
||||
if (!RouteManager.instance) {
|
||||
RouteManager.instance = new RouteManager();
|
||||
}
|
||||
return RouteManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
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) => {
|
||||
private register(path:string, element: React.ReactNode) {
|
||||
this.routes.push({
|
||||
path,
|
||||
element: this.createRouteElement(template),
|
||||
});
|
||||
element,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建路由元素
|
||||
*/
|
||||
private createRouteElement(templateName: string) {
|
||||
return (props: any) => {
|
||||
const template = this.themeService.getTemplate(templateName);
|
||||
// 这里可以添加模板渲染逻辑
|
||||
return <div dangerouslySetInnerHTML={{ __html: template }} />;
|
||||
};
|
||||
|
||||
private createRouteElement(path: string,element:React.ReactNode,loader?:LoaderFunction) {
|
||||
this.routes.push({
|
||||
path,
|
||||
element,
|
||||
loader?: loader
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有路由
|
||||
*/
|
||||
public getRoutes(): RouteObject[] {
|
||||
return this.routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新路由
|
||||
*/
|
||||
public addRoute(path: string, templateName: string): void {
|
||||
this.routes.push({
|
||||
path,
|
||||
|
@ -1,4 +1,3 @@
|
||||
// File path: services/themeService.ts
|
||||
import { ThemeConfig, ThemeTemplate } from "contracts/themeContract";
|
||||
import { ApiService } from "./apiService";
|
||||
|
||||
@ -19,18 +18,12 @@ export class ThemeService {
|
||||
return ThemeService.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主题服务
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
// 从API获取当前主题配置
|
||||
const themeConfig = await this.api.request<ThemeConfig>(
|
||||
'/theme/current',
|
||||
{ method: 'GET' }
|
||||
);
|
||||
|
||||
// 加载主题配置
|
||||
await this.loadTheme(themeConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize theme:', error);
|
||||
@ -38,9 +31,6 @@ export class ThemeService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载主题配置
|
||||
*/
|
||||
private async loadTheme(config: ThemeConfig): Promise<void> {
|
||||
try {
|
||||
this.currentTheme = config;
|
||||
@ -51,9 +41,6 @@ export class ThemeService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载主题模板
|
||||
*/
|
||||
private async loadTemplates(): Promise<void> {
|
||||
if (!this.currentTheme) {
|
||||
throw new Error('No theme configuration loaded');
|
||||
@ -70,23 +57,16 @@ export class ThemeService {
|
||||
}
|
||||
};
|
||||
|
||||
// 并行加载所有模板
|
||||
const loadPromises = Array.from(this.currentTheme.templates.values())
|
||||
.map(template => loadTemplate(template));
|
||||
|
||||
await Promise.all(loadPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题配置
|
||||
*/
|
||||
public getThemeConfig(): ThemeConfig | undefined {
|
||||
return this.currentTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板内容
|
||||
*/
|
||||
public getTemplate(templateName: string): string {
|
||||
const template = this.templates.get(templateName);
|
||||
if (!template) {
|
||||
@ -95,9 +75,6 @@ export class ThemeService {
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由获取对应的模板
|
||||
*/
|
||||
public getTemplateByRoute(route: string): string {
|
||||
if (!this.currentTheme) {
|
||||
throw new Error('No theme configuration loaded');
|
||||
@ -105,7 +82,6 @@ export class ThemeService {
|
||||
|
||||
let templateName: string | undefined;
|
||||
|
||||
// 检查是否是预定义路由
|
||||
if (route === '/') {
|
||||
templateName = this.currentTheme.routes.index;
|
||||
} else if (route.startsWith('/post/')) {
|
||||
@ -115,7 +91,6 @@ export class ThemeService {
|
||||
} else if (route.startsWith('/category/')) {
|
||||
templateName = this.currentTheme.routes.category;
|
||||
} else {
|
||||
// 检查自定义页面路由
|
||||
templateName = this.currentTheme.routes.page.get(route);
|
||||
}
|
||||
|
||||
@ -126,9 +101,6 @@ export class ThemeService {
|
||||
return this.getTemplate(templateName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主题配置
|
||||
*/
|
||||
public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> {
|
||||
try {
|
||||
const updatedConfig = await this.api.request<ThemeConfig>(
|
||||
|
Loading…
Reference in New Issue
Block a user