增加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 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")
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
);
|
||||||
|
@ -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();
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
// File path: src/secret.rs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 本文件包含JWT的生成和验证功能。
|
|
||||||
* 提供了生成密钥、生成JWT、验证JWT的相关函数。
|
|
||||||
*/
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
@ -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>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// File path: components/LoadingBoundary.tsx
|
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
|
||||||
interface LoadingBoundaryProps {
|
interface LoadingBoundaryProps {
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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; // 额外数据(可选),支持序列化
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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 {
|
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,
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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>(
|
||||||
|
Loading…
Reference in New Issue
Block a user