pub mod action;
pub mod addon;
pub mod module;
pub mod request;
pub mod swagger;
#[allow(clippy::too_many_arguments)]
#[cfg(any(
feature = "sqlite",
feature = "mssql",
feature = "mysql",
feature = "pgsql"
))]
pub mod tables;
pub mod tools;
use crate::action::Action;
use crate::addon::Addon;
use crate::module::Module;
use crate::request::Request;
use crate::tools::{Tools, ToolsConfig};
#[cfg(any(
feature = "mysql",
feature = "sqlite",
feature = "mssql",
feature = "pgsql"
))]
use br_db::types::TableOptions;
use json::{array, object, JsonValue};
use lazy_static::lazy_static;
use log::{error, info};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::LazyLock;
use std::sync::{Mutex, OnceLock};
use std::{fs, thread};
lazy_static! {
static ref CONFIG: Mutex<HashMap<String, JsonValue>> = Mutex::new(HashMap::new());
}
static PLUGIN_TOOLS: OnceLock<Tools> = OnceLock::new();
static GLOBAL_HANDLE: LazyLock<Mutex<HashMap<String, String>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub static GLOBAL_ADDONS: OnceLock<Vec<String>> = OnceLock::new();
pub static GLOBAL_MODULE: OnceLock<Vec<String>> = OnceLock::new();
pub static GLOBAL_ACTION: OnceLock<Vec<String>> = OnceLock::new();
thread_local! {
static GLOBAL_DATA: RefCell<JsonValue> = RefCell::new(object!{});
}
pub trait Plugin {
fn addon(name: &str) -> Result<Box<dyn Addon>, String>;
fn module(name: &str) -> Result<Box<dyn Module>, String> {
let (addon_name, module_name) = Self::split2(name)?;
Self::addon(addon_name)?.module(module_name)
}
fn action(name: &str) -> Result<Box<dyn Action>, String> {
let (addon_name, module_name, action_name) = Self::split3(name)?;
Self::addon(addon_name)?
.module(module_name)?
.action(action_name)
}
fn api_run(name: &str, request: Request) -> Result<JsonValue, String> {
let (addon_name, module_name, action_name) = Self::split3(name)?;
match Self::addon(addon_name)?
.module(module_name)?
.action(action_name)?
.run(request)
{
Ok(e) => Ok(e.data),
Err(e) => Err(e.message),
}
}
fn load_tools(config: ToolsConfig) -> Result<(), String> {
if PLUGIN_TOOLS.get().is_some() {
return Ok(());
}
let tools = Tools::new(config)?;
let _ = PLUGIN_TOOLS.set(tools);
Ok(())
}
fn get_tools() -> Tools {
PLUGIN_TOOLS.get().expect("tools not initialized").clone()
}
fn load_config(name: &str, config: JsonValue) {
if let Ok(mut cfg) = CONFIG.lock() {
cfg.insert(name.into(), config.clone());
}
}
#[cfg(any(
feature = "mysql",
feature = "sqlite",
feature = "mssql",
feature = "pgsql"
))]
fn init_db() -> Result<(), String> {
info!("=============数据库更新开始=============");
let mut tables = HashMap::new();
let sql = PathBuf::from("sql");
if let Some(sql_path) = sql.to_str() {
match fs::remove_dir_all(sql_path) {
Ok(_) => {}
Err(e) => {
#[cfg(any(
feature = "mysql",
feature = "sqlite",
feature = "mssql",
feature = "pgsql"
))]
error!("目录删除失败: {e}");
}
}
}
if let Some(sql_path) = sql.to_str() {
match fs::create_dir_all(sql_path) {
Ok(_) => {}
Err(e) => error!("目录创建失败: {e}"),
}
}
let sql_file = sql.join("sql.json");
let mut db_install = array![];
for api_name in GLOBAL_MODULE.wait() {
let module = match Self::module(api_name) {
Ok(e) => e,
Err(e) => {
error!("加载模型错误: {}", e);
continue;
}
};
if !module.table() {
continue;
}
if !tables.contains_key(module._table_name()) {
tables.insert(module._table_name(), module);
}
}
for (_, module) in tables.iter_mut() {
let mut opt = TableOptions::default();
let unique = module
.table_unique()
.iter()
.map(|x| (*x).to_string())
.collect::<Vec<String>>();
let unique = unique.iter().map(|x| x.as_str()).collect::<Vec<&str>>();
let index = module
.table_index()
.iter()
.map(|x| x.iter().map(|&y| y.to_string()).collect::<Vec<String>>())
.collect::<Vec<Vec<String>>>();
let index = index
.iter()
.map(|x| x.iter().map(|y| y.as_str()).collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>();
opt.set_table_name(module._table_name());
opt.set_table_title(module.title());
opt.set_table_key(module.table_key());
opt.set_table_fields(module.fields().clone());
opt.set_table_unique(unique.clone());
opt.set_table_index(index.clone());
opt.set_table_partition(module.table_partition());
opt.set_table_partition_columns(module.table_partition_columns());
let mut tools = Self::get_tools();
let sql = tools
.db
.table(module._table_name())
.fetch_sql()
.table_create(opt.clone());
let mut exec_tools = Self::get_tools();
let table_exists = exec_tools.db.table_is_exist(module._table_name());
let update_sql = if table_exists {
tools
.db
.table(module._table_name())
.fetch_sql()
.table_update(opt.clone())
} else {
JsonValue::Null
};
db_install
.push(object! {
table:module._table_name(),
field:module.fields().clone(),
key:module.table_key(),
index:index.clone(),
unique:unique.clone(),
sql:sql,
update_sql:update_sql
})
.ok();
if let Err(e) = fs::write(sql_file.clone(), db_install.to_string()) {
error!("写入SQL文件失败: {e}");
}
if table_exists {
let res = exec_tools.db.table_update(opt);
match res.as_i32().unwrap_or(-1) {
-1 => {}
0 => {
info!("数据库更新情况: {} 失败", module._table_name());
}
1 => {
info!("数据库更新情况: {} 成功", module._table_name());
}
_ => {}
}
} else {
let res = exec_tools.db.table_create(opt);
info!("安装完成情况: {} {}", module._table_name(), res);
}
}
info!("=============数据库更新完成=============");
Ok(())
}
#[cfg(any(
feature = "mysql",
feature = "sqlite",
feature = "mssql",
feature = "pgsql"
))]
fn init_data() -> Result<(), String> {
let mut exec_tools = Self::get_tools();
for api_name in GLOBAL_MODULE.wait() {
let mut module = match Self::module(api_name) {
Ok(e) => e,
Err(e) => {
error!("加载模型错误: {}", e);
continue;
}
};
if !module.table() {
continue;
}
let init_data = module.init_data();
if !init_data.is_empty() {
let count = exec_tools.db.table(module._table_name()).count();
if count.is_empty() {
let r = exec_tools.db
.table(module._table_name())
.insert_all(init_data);
info!("初始化【{}】数据 {} 条", module._table_name(), r.len());
}
}
}
info!("所有模块数据初始化完成");
Ok(())
}
fn handles() {
let mut map = match GLOBAL_HANDLE.lock() {
Ok(m) => m,
Err(_) => return,
};
for api in GLOBAL_ADDONS.wait() {
let mut addon_name = match Self::addon(api.as_str()) {
Ok(e) => e,
Err(e) => {
error!("插件: {api} 加载错误 {e}");
continue;
}
};
if map.get(addon_name.name()).is_none() {
map.insert(addon_name.name().to_string(), addon_name.name().to_string());
thread::spawn(move || addon_name.handle());
}
}
for api in GLOBAL_MODULE.wait() {
let mut module_name = match Self::module(api.as_str()) {
Ok(e) => e,
Err(e) => {
error!("插件: {api} 加载错误 {e}");
continue;
}
};
if map.get(module_name.module_name()).is_none() {
map.insert(
module_name.module_name().to_string(),
module_name.module_name().to_string(),
);
thread::spawn(move || module_name.handle());
}
}
}
fn swagger(
title: &str,
description: &str,
version: &str,
uaturl: &str,
produrl: &str,
tags: JsonValue,
paths: JsonValue,
) -> JsonValue {
let info = object! {
openapi:"3.0.0",
info:{
title:title,
description:description,
version:version
},
components: {
securitySchemes: {
BearerToken: {
"type": "http",
"scheme": "bearer",
"bearerFormat": "Token"
}
}
},
tags:tags,
security: [
{
"BearerToken": []
}
],
servers:[
{
"url":uaturl,
"description": "测试地址"
},
{
"url":produrl,
"description": "正式地址"
}
],
paths:paths
};
info
}
fn generate_api_list(
apipath: PathBuf,
path: PathBuf,
index: usize,
) -> Result<Vec<String>, String> {
#[cfg(debug_assertions)]
{
let mut plugin_list = Vec::new();
if path.is_dir() {
let res = fs::read_dir(path);
match res {
Ok(entries) => {
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(e) => return Err(e.to_string()),
};
let path = entry.path();
if path.is_dir() {
let path_str = match path.to_str() {
Some(s) => s,
None => continue,
};
let res = Self::generate_api_list(
apipath.clone(),
path_str.parse().unwrap_or_default(),
index + 1,
)?;
plugin_list.extend(res);
} else if path.is_file() {
let path_str = match path.to_str() {
Some(s) => s,
None => continue,
};
if path_str.ends_with("mod.rs") {
continue;
}
let addon = path
.parent()
.and_then(|p| p.parent())
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.unwrap_or_default();
let model = path
.parent()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.unwrap_or_default();
let action = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default()
.trim_end_matches(".rs");
let api = format!("{addon}.{model}.{action}");
match Self::action(api.as_str()) {
Ok(e) => plugin_list.push(e.api()),
Err(_) => continue,
}
}
}
}
Err(e) => return Err(e.to_string()),
}
}
if index == 0 {
if let Some(parent) = apipath.clone().parent() {
let _ = fs::create_dir_all(parent);
}
let _ = fs::write(&apipath, JsonValue::from(plugin_list.clone()).to_string());
info!(
"=============API数量: {} 条=============",
plugin_list.len()
);
Self::_load_apis(plugin_list.clone())?;
}
Ok(plugin_list)
}
#[cfg(not(debug_assertions))]
{
let apis = fs::read_to_string(&apipath).unwrap_or_default();
let apis = json::parse(&apis).unwrap_or(array![]);
let apis = apis
.members()
.map(|x| x.as_str().unwrap_or_default().to_string())
.collect::<Vec<String>>();
info!("=============API数量: {} 条=============", apis.len());
Self::_load_apis(apis.clone())?;
Ok(apis)
}
}
fn _load_apis(apis: Vec<String>) -> Result<(), String> {
let mut action_list = vec![];
let mut module_list = vec![];
let mut addons_list = vec![];
for api in apis {
let action = Self::action(api.as_str())?;
action_list.push(action.api());
if !module_list.contains(&action.module_name()) {
module_list.push(action.module_name());
}
if !addons_list.contains(&action.addon_name()) {
addons_list.push(action.addon_name());
}
}
let _ = GLOBAL_ACTION.set(action_list);
let _ = GLOBAL_MODULE.set(module_list);
let _ = GLOBAL_ADDONS.set(addons_list);
Ok(())
}
fn set_global_data(key: &str, value: JsonValue) {
GLOBAL_DATA.with(|data| {
data.borrow_mut()[key] = value;
});
}
fn get_global_data() -> JsonValue {
GLOBAL_DATA.with(|data| data.borrow().clone())
}
fn get_global_data_key(key: &str) -> JsonValue {
GLOBAL_DATA.with(|data| data.borrow()[key].clone())
}
#[inline]
fn split2(name: &str) -> Result<(&str, &str), String> {
let t = name.split('.').collect::<Vec<&str>>();
if t.len() < 2 {
return Err(format!("模型格式不正确: {name}"));
}
Ok((t[0], t[1]))
}
#[inline]
fn split3(name: &str) -> Result<(&str, &str, &str), String> {
if let Some((a, rest)) = name.split_once('.') {
if let Some((b, c)) = rest.split_once('.') {
if !a.is_empty() && !b.is_empty() && !c.is_empty() {
Ok((a, b, c))
} else {
Err("动作格式不正确".to_string())
}
} else {
Err("动作格式不正确".to_string())
}
} else {
Err("动作格式不正确".to_string())
}
}
}
#[derive(Debug, Clone)]
pub struct ApiResponse {
pub types: ApiType,
pub code: i32,
pub message: String,
pub data: JsonValue,
pub success: bool,
pub timestamp: i64,
}
impl ApiResponse {
#[must_use]
pub fn json(&self) -> JsonValue {
match self.types {
ApiType::Json => object! {
code: self.code,
message: self.message.clone(),
data: self.data.clone(),
success: self.success
},
ApiType::Redirect
| ApiType::Download
| ApiType::Preview
| ApiType::Txt
| ApiType::Html => self.data.clone(),
}
}
pub fn swagger(&mut self) -> JsonValue {
let type_str = self.types.str();
let mut content = object! {};
content[type_str] = object! {};
content[type_str]["schema"]["type"] = if self.data.is_array() {
"array"
} else {
"object"
}
.into();
content[type_str]["schema"]["properties"] = self.data.clone();
content[type_str]["schema"]["type"] = match content[type_str]["schema"]["type"]
.as_str()
.unwrap_or("object")
{
"int" => "integer".into(),
_ => content[type_str]["schema"]["type"].clone(),
};
object! {
"description":self.message.clone(),
"content":content
}
}
pub fn success(data: JsonValue, mut message: &str) -> Self {
if message.is_empty() {
message = "success";
}
Self {
success: true,
types: ApiType::Json,
code: 0,
message: message.to_string(),
data,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn fail(code: i32, message: &str) -> Self {
Self {
types: ApiType::Json,
code,
message: message.to_string(),
data: JsonValue::Null,
success: false,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn error(data: JsonValue, message: &str) -> Self {
Self {
types: ApiType::Json,
code: -1,
message: message.to_string(),
data,
success: false,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn redirect(url: &str) -> Self {
Self {
types: ApiType::Redirect,
code: 0,
message: "".to_string(),
data: url.into(),
success: true,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn download(filename: &str) -> Self {
Self {
types: ApiType::Download,
code: 0,
message: "".to_string(),
data: filename.into(),
success: true,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn preview(filename: &str) -> Self {
Self {
types: ApiType::Preview,
code: 0,
message: "".to_string(),
data: filename.into(),
success: true,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn txt(txt: &str) -> Self {
Self {
types: ApiType::Txt,
code: 0,
message: "".to_string(),
data: txt.into(),
success: true,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
pub fn html(data: &str) -> Self {
Self {
types: ApiType::Html,
code: 0,
message: "".to_string(),
data: data.into(),
success: true,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
}
impl Default for ApiResponse {
fn default() -> Self {
Self {
types: ApiType::Json,
code: 0,
message: "".to_string(),
data: JsonValue::Null,
success: false,
timestamp: br_fields::datetime::Timestamp::timestamp(),
}
}
}
#[derive(Debug, Clone)]
pub enum ApiType {
Json,
Redirect,
Download,
Preview,
Txt,
Html,
}
impl ApiType {
#[must_use]
pub fn str(&self) -> &'static str {
match self {
Self::Json => "application/json",
Self::Redirect | Self::Download | Self::Preview => "text/html",
Self::Txt => "text/plain",
Self::Html => "text/html",
}
}
}