1pub mod action;
3pub mod addon;
5pub mod module;
7pub mod request;
9pub mod swagger;
11pub mod tools;
13#[allow(clippy::too_many_arguments)]
14#[cfg(any(feature = "sqlite", feature = "mssql", feature = "mysql", feature = "pgsql"))]
15pub mod tables;
17
18use crate::action::Action;
19use crate::addon::Addon;
20use crate::module::Module;
21use crate::request::Request;
22use crate::tools::{Tools, ToolsConfig};
23#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql", feature = "pgsql"))]
24use br_db::types::TableOptions;
25use json::{array, object, JsonValue};
26use lazy_static::lazy_static;
27use log::{error, info};
28use std::collections::HashMap;
29use std::path::PathBuf;
30use std::sync::{Mutex, OnceLock};
31use std::{fs, thread};
32use std::cell::RefCell;
33use std::sync::LazyLock;
34
35lazy_static! {
36 static ref PLUGIN_TOOLS: Mutex<HashMap<String,Tools>> =Mutex::new(HashMap::new());
38 static ref CONFIG: Mutex<HashMap<String,JsonValue>> =Mutex::new(HashMap::new());
39}
40static GLOBAL_HANDLE: LazyLock<Mutex<HashMap<String, String>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
42pub static GLOBAL_ADDONS: OnceLock<Vec<String>> = OnceLock::new();
44pub static GLOBAL_MODULE: OnceLock<Vec<String>> = OnceLock::new();
46pub static GLOBAL_ACTION: OnceLock<Vec<String>> = OnceLock::new();
48
49thread_local! {
50 static GLOBAL_DATA: RefCell<JsonValue> = RefCell::new(object!{});
52}
53pub trait Plugin {
55 fn addon(name: &str) -> Result<Box<dyn Addon>, String>;
57 fn module(name: &str) -> Result<Box<dyn Module>, String> {
59 let (addon_name, module_name) = Self::split2(name)?;
60 Self::addon(addon_name)?.module(module_name)
61 }
62 fn action(name: &str) -> Result<Box<dyn Action>, String> {
64 let (addon_name, module_name, action_name) = Self::split3(name)?;
65 Self::addon(addon_name)?.module(module_name)?.action(action_name)
66 }
67 fn api_run(name: &str, request: Request) -> Result<JsonValue, String> {
69 let (addon_name, module_name, action_name) = Self::split3(name)?;
70 match Self::addon(addon_name)?.module(module_name)?.action(action_name)?.run(request) {
71 Ok(e) => Ok(e.data),
72 Err(e) => Err(e.message),
73 }
74 }
75 fn load_tools(config: ToolsConfig) -> Result<(), String> {
78 let mut tools_map = PLUGIN_TOOLS.lock().unwrap_or_else(|e| e.into_inner());
79 if tools_map.get("tools").is_none() {
80 tools_map.insert("tools".into(), Tools::new(config)?);
81 }
82 Ok(())
83 }
84 fn get_tools() -> Tools {
86 let tools_map = PLUGIN_TOOLS.lock().unwrap_or_else(|e| e.into_inner());
87 let tools = tools_map.get("tools").unwrap().clone();
88 tools
89 }
90 fn load_config(name: &str, config: JsonValue) {
92 let mut config_map = CONFIG.lock().unwrap_or_else(|e| e.into_inner());
93 config_map.insert(name.into(), config.clone());
94 }
95 #[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql", feature = "pgsql"))]
99 fn init_db() -> Result<(), String> {
100 info!("=============数据库更新开始=============");
101 let mut tables = HashMap::new();
102 let sql = PathBuf::from("sql");
104
105 match fs::remove_dir_all(sql.to_str().unwrap()) {
107 Ok(_) => {}
108 Err(e) => {
109 #[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql", feature = "pgsql"))]
110 error!("目录删除失败: {e}");
111 }
112 }
113
114 match fs::create_dir_all(sql.to_str().unwrap()) {
116 Ok(_) => {}
117 Err(e) => error!("目录创建失败: {e}"),
118 }
119
120 let sql_file = sql.join("sql.json");
121 let mut db_install = array![];
122
123 for api_name in GLOBAL_MODULE.wait() {
124 let module = match Self::module(api_name) {
125 Ok(e) => e,
126 Err(e) => {
127 error!("加载模型错误: {}",e);
128 continue;
129 }
130 };
131 if !module.table() {
132 continue;
133 }
134 if !tables.contains_key(module._table_name()) {
135 tables.insert(module._table_name(), module);
136 }
137 }
138
139 for (_, module) in tables.iter_mut() {
140 let mut opt = TableOptions::default();
141
142 let unique = module.table_unique().iter().map(|x| (*x).to_string()).collect::<Vec<String>>();
143 let unique = unique.iter().map(|x| x.as_str()).collect::<Vec<&str>>();
144
145 let index = module.table_index().iter().map(|x| x.iter().map(|&y| y.to_string()).collect::<Vec<String>>()).collect::<Vec<Vec<String>>>();
146 let index = index.iter().map(|x| x.iter().map(|y| y.as_str()).collect::<Vec<&str>>()).collect::<Vec<Vec<&str>>>();
147
148
149 opt.set_table_name(module._table_name());
150 opt.set_table_title(module.title());
151 opt.set_table_key(module.table_key());
152 opt.set_table_fields(module.fields().clone());
153 opt.set_table_unique(unique.clone());
154 opt.set_table_index(index.clone());
155 opt.set_table_partition(module.table_partition());
156 opt.set_table_partition_columns(module.table_partition_columns());
157 let sql = Self::get_tools().db.table(module._table_name()).fetch_sql().table_create(opt.clone());
159 let update_sql = Self::get_tools().db.table(module._table_name()).fetch_sql().table_update(opt.clone());
160 db_install.push(object! {
161 table:module._table_name(),
162 field:module.fields().clone(),
163 key:module.table_key(),
164 index:index.clone(),
165 unique:unique.clone(),
166 sql:sql,
167 update_sql:update_sql
168 }).unwrap();
169 fs::write(sql_file.clone(), db_install.to_string()).unwrap();
170
171 {
173 use std::fs::OpenOptions;
174 use std::io::Write;
175 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
176 let _ = writeln!(file, r#"{{"id":"log_table_check","timestamp":{},"location":"br-addon/src/lib.rs:171","message":"检查表是否存在","data":{{"table_name":"{}","hypothesisId":"A"}},"sessionId":"debug-session","runId":"run1"}}"#,
177 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
178 module._table_name());
179 }
180 }
181 let table_exists = Self::get_tools().db.table_is_exist(module._table_name());
183 {
185 use std::fs::OpenOptions;
186 use std::io::Write;
187 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
188 let _ = writeln!(file, r#"{{"id":"log_table_exists_result","timestamp":{},"location":"br-addon/src/lib.rs:172","message":"表存在性检查结果","data":{{"table_name":"{}","exists":{},"hypothesisId":"A"}},"sessionId":"debug-session","runId":"run1"}}"#,
189 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
190 module._table_name(), table_exists);
191 }
192 }
193 if table_exists {
195 {
197 use std::fs::OpenOptions;
198 use std::io::Write;
199 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
200 let _ = writeln!(file, r#"{{"id":"log_table_update_start","timestamp":{},"location":"br-addon/src/lib.rs:173","message":"开始更新表","data":{{"table_name":"{}","hypothesisId":"B"}},"sessionId":"debug-session","runId":"run1"}}"#,
201 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
202 module._table_name());
203 }
204 }
205 let res = Self::get_tools().db.table_update(opt);
207 {
209 use std::fs::OpenOptions;
210 use std::io::Write;
211 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
212 let _ = writeln!(file, r#"{{"id":"log_table_update_result","timestamp":{},"location":"br-addon/src/lib.rs:174","message":"表更新结果","data":{{"table_name":"{}","result":{},"hypothesisId":"B"}},"sessionId":"debug-session","runId":"run1"}}"#,
213 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
214 module._table_name(), res);
215 }
216 }
217 let result = res.as_i32()
220 .or_else(|| {
221 if res.is_boolean() {
223 Some(if res.as_bool().unwrap_or(false) { 1 } else { 0 })
224 } else {
225 None
226 }
227 })
228 .unwrap_or(0); match result {
231 -1 => {}
232 0 => {
233 info!("数据库更新情况: {} 失败", module._table_name());
234 }
235 1 => {
236 info!("数据库更新情况: {} 成功", module._table_name());
237 }
238 _ => {}
239 }
240 } else {
241 {
243 use std::fs::OpenOptions;
244 use std::io::Write;
245 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
246 let _ = writeln!(file, r#"{{"id":"log_table_create_start","timestamp":{},"location":"br-addon/src/lib.rs:183","message":"开始创建表","data":{{"table_name":"{}","hypothesisId":"C"}},"sessionId":"debug-session","runId":"run1"}}"#,
247 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
248 module._table_name());
249 }
250 }
251 let res = Self::get_tools().db.table_create(opt);
253 {
255 use std::fs::OpenOptions;
256 use std::io::Write;
257 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/Users/xiaduan/coding/scs/.cursor/debug.log") {
258 let _ = writeln!(file, r#"{{"id":"log_table_create_result","timestamp":{},"location":"br-addon/src/lib.rs:184","message":"表创建结果","data":{{"table_name":"{}","result":{},"hypothesisId":"C"}},"sessionId":"debug-session","runId":"run1"}}"#,
259 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(),
260 module._table_name(), res);
261 }
262 }
263 info!("安装完成情况: {} {}", module._table_name(), res);
265 }
266 let init_data = module.init_data();
267 if !init_data.is_empty() {
268 let count = Self::get_tools().db.table(module._table_name()).count();
269 if count.is_empty() {
270 let r = Self::get_tools().db.table(module._table_name()).insert_all(init_data);
271 info!("初始化【{}】数据 {} 条",module._table_name(),r.len());
272 }
273 }
274 }
275 info!("=============数据库更新完成=============");
276 Ok(())
277 }
278
279 fn handles() {
281 let mut map = GLOBAL_HANDLE.lock().unwrap_or_else(|e| e.into_inner());
282
283 for api in GLOBAL_ADDONS.wait() {
284 let mut addon_name = match Self::addon(api.as_str()) {
285 Ok(e) => e,
286 Err(e) => {
287 error!("插件: {api} 加载错误 {e}");
288 continue;
289 }
290 };
291 if map.get(addon_name.name()).is_none() {
292 map.insert(addon_name.name().to_string(), addon_name.name().to_string());
293 thread::spawn(move || addon_name.handle());
294 }
295 }
296 for api in GLOBAL_MODULE.wait() {
297 let mut module_name = match Self::module(api.as_str()) {
298 Ok(e) => e,
299 Err(e) => {
300 error!("插件: {api} 加载错误 {e}");
301 continue;
302 }
303 };
304 if map.get(module_name.module_name()).is_none() {
305 map.insert(module_name.module_name().to_string(), module_name.module_name().to_string());
306 thread::spawn(move || module_name.handle());
307 }
308 }
309 }
310 fn swagger(
312 title: &str,
313 description: &str,
314 version: &str,
315 uaturl: &str,
316 produrl: &str,
317 tags: JsonValue,
318 paths: JsonValue,
319 ) -> JsonValue {
320 let info = object! {
321 openapi:"3.0.0",
322 info:{
323 title:title,
324 description:description,
325 version:version
326 },
327 components: {
328 securitySchemes: {
329 BearerToken: {
330 "type": "http",
331 "scheme": "bearer",
332 "bearerFormat": "Token"
333 }
334 }
335 },
336 tags:tags,
337 security: [
338 {
339 "BearerToken": []
340 }
341 ],
342 servers:[
343 {
344 "url":uaturl,
345 "description": "测试地址"
346 },
347 {
348 "url":produrl,
349 "description": "正式地址"
350 }
351 ],
352 paths:paths
353 };
354 info
355 }
356 fn generate_api_list(apipath: PathBuf, path: PathBuf, index: usize) -> Result<Vec<String>, String> {
358 #[cfg(debug_assertions)]
359 {
360 let mut plugin_list = Vec::new();
361 if path.is_dir() {
362 let res = fs::read_dir(path);
363 match res {
364 Ok(entries) => {
365 for entry in entries {
366 let entry = match entry {
367 Ok(e) => e,
368 Err(e) => {
369 return Err(e.to_string())
370 }
371 };
372 let path = entry.path();
373 if path.is_dir() {
374 let res = Self::generate_api_list(
375 apipath.clone(),
376 path.to_str().unwrap().parse().unwrap(),
377 index + 1,
378 )?;
379 plugin_list.extend(res);
380 } else if path.is_file() {
381 if path.to_str().unwrap().ends_with("mod.rs") {
382 continue;
383 }
384 let addon = path.parent().unwrap().parent().unwrap().file_name().unwrap().to_str().unwrap();
385 let model = path.parent().unwrap().file_name().unwrap().to_str().unwrap();
386 let action = path.file_name().unwrap().to_str().unwrap().trim_end_matches(".rs");
387 let api = format!("{addon}.{model}.{action}");
388 match Self::action(api.as_str()) {
389 Ok(e) => plugin_list.push(e.api()),
390 Err(_) => continue
391 }
392 }
393 }
394 }
395 Err(e) => return Err(e.to_string())
396 }
397 }
398 if index == 0 {
399 fs::create_dir_all(apipath.clone().parent().unwrap()).unwrap();
400 fs::write(apipath, JsonValue::from(plugin_list.clone()).to_string()).unwrap();
401 info!("=============API数量: {} 条=============", plugin_list.len());
402 Self::_load_apis(plugin_list.clone())?;
403 }
404 Ok(plugin_list)
405 }
406 #[cfg(not(debug_assertions))]
407 {
408 let apis = fs::read_to_string(apipath).unwrap();
409 let apis = json::parse(&apis).unwrap();
410 let apis = apis.members().map(|x| x.as_str().unwrap().to_string()).collect::<Vec<String>>();
411 info!("=============API数量: {} 条=============", apis.len());
412 Self::_load_apis(apis.clone())?;
413 Ok(apis)
414 }
415 }
416 fn _load_apis(apis: Vec<String>) -> Result<(), String> {
418 let mut action_list = vec![];
419 let mut module_list = vec![];
420 let mut addons_list = vec![];
421 for api in apis {
422 let action = Self::action(api.as_str())?;
423 action_list.push(action.api());
424 if !module_list.contains(&action.module_name()) {
425 module_list.push(action.module_name());
426 }
427 if !addons_list.contains(&action.addon_name()) {
428 addons_list.push(action.addon_name());
429 }
430 }
431 let _ = GLOBAL_ACTION.set(action_list);
432 let _ = GLOBAL_MODULE.set(module_list);
433 let _ = GLOBAL_ADDONS.set(addons_list);
434 Ok(())
435 }
436 fn set_global_data(key: &str, value: JsonValue) {
438 GLOBAL_DATA.with(|data| {
439 data.borrow_mut()[key] = value;
440 });
441 }
442 fn get_global_data() -> JsonValue {
444 GLOBAL_DATA.with(|data| {
445 data.borrow().clone()
446 })
447 }
448 fn get_global_data_key(key: &str) -> JsonValue {
450 GLOBAL_DATA.with(|data| {
451 data.borrow()[key].clone()
452 })
453 }
454 #[inline]
456 fn split2(name: &str) -> Result<(&str, &str), String> {
457 let t = name.split(".").collect::<Vec<&str>>();
458 if t.len() < 2 {
459 return Err(format!("模型格式不正确: {name}"));
460 }
461 Ok((t[0], t[1]))
462 }
463
464 #[inline]
466 fn split3(name: &str) -> Result<(&str, &str, &str), String> {
467 if let Some((a, rest)) = name.split_once('.') {
468 if let Some((b, c)) = rest.split_once('.') {
469 if !a.is_empty() && !b.is_empty() && !c.is_empty() {
470 Ok((a, b, c))
471 } else {
472 Err("动作格式不正确".to_string())
473 }
474 } else {
475 Err("动作格式不正确".to_string())
476 }
477 } else {
478 Err("动作格式不正确".to_string())
479 }
480 }
481}
482#[derive(Debug, Clone)]
484pub struct ApiResponse {
485 pub types: ApiType,
486 pub code: i32,
487 pub message: String,
488 pub data: JsonValue,
489 pub success: bool,
490 pub timestamp: i64,
491}
492impl ApiResponse {
493 pub fn json(self) -> JsonValue {
494 match self.types {
495 ApiType::Json => object! {
496 code: self.code,
497 message: self.message,
498 data: self.data,
499 success: self.success
500 },
501 ApiType::Redirect => self.data,
502 ApiType::Download => self.data,
503 ApiType::Preview => self.data,
504 ApiType::Txt => self.data,
505 ApiType::Html => self.data,
506 }
507 }
508 pub fn swagger(&mut self) -> JsonValue {
509 let mut content = object! {};
510 content[self.types.str().as_str()] = object! {};
511 content[self.types.str().as_str()]["schema"]["type"] = if self.data.is_array() {
512 "array"
513 } else {
514 "object"
515 }.into();
516 content[self.types.str().as_str()]["schema"]["properties"] = self.data.clone();
517
518 content[self.types.str().as_str()]["schema"]["type"] = match content[self.types.str().as_str()]["schema"]["type"].as_str().unwrap() {
519 "int" => "integer".into(),
520 _ => content[self.types.str().as_str()]["schema"]["type"].clone(),
521 };
522 let data = object! {
523 "description":self.message.clone(),
524 "content":content
525 };
526 data
527 }
528 pub fn success(data: JsonValue, mut message: &str) -> Self {
529 if message.is_empty() {
530 message = "success";
531 }
532 Self {
533 success: true,
534 types: ApiType::Json,
535 code: 0,
536 message: message.to_string(),
537 data,
538 timestamp: br_fields::datetime::Timestamp::timestamp(),
539 }
540 }
541 pub fn fail(code: i32, message: &str) -> Self {
542 Self {
543 types: ApiType::Json,
544 code,
545 message: message.to_string(),
546 data: JsonValue::Null,
547 success: false,
548 timestamp: br_fields::datetime::Timestamp::timestamp(),
549 }
550 }
551 pub fn error(data: JsonValue, message: &str) -> Self {
552 Self {
553 types: ApiType::Json,
554 code: -1,
555 message: message.to_string(),
556 data,
557 success: false,
558 timestamp: br_fields::datetime::Timestamp::timestamp(),
559 }
560 }
561 pub fn redirect(url: &str) -> Self {
563 Self {
564 types: ApiType::Redirect,
565 code: 0,
566 message: "".to_string(),
567 data: url.into(),
568 success: true,
569 timestamp: br_fields::datetime::Timestamp::timestamp(),
570 }
571 }
572 pub fn download(filename: &str) -> Self {
574 Self {
575 types: ApiType::Download,
576 code: 0,
577 message: "".to_string(),
578 data: filename.into(),
579 success: true,
580 timestamp: br_fields::datetime::Timestamp::timestamp(),
581 }
582 }
583 pub fn preview(filename: &str) -> Self {
585 Self {
586 types: ApiType::Preview,
587 code: 0,
588 message: "".to_string(),
589 data: filename.into(),
590 success: true,
591 timestamp: br_fields::datetime::Timestamp::timestamp(),
592 }
593 }
594 pub fn txt(txt: &str) -> Self {
596 Self {
597 types: ApiType::Txt,
598 code: 0,
599 message: "".to_string(),
600 data: txt.into(),
601 success: true,
602 timestamp: br_fields::datetime::Timestamp::timestamp(),
603 }
604 }
605 pub fn html(data: &str) -> Self {
606 Self {
607 types: ApiType::Html,
608 code: 0,
609 message: "".to_string(),
610 data: data.into(),
611 success: true,
612 timestamp: br_fields::datetime::Timestamp::timestamp(),
613 }
614 }
615}
616impl Default for ApiResponse {
617 fn default() -> Self {
618 Self {
619 types: ApiType::Json,
620 code: 0,
621 message: "".to_string(),
622 data: JsonValue::Null,
623 success: false,
624 timestamp: br_fields::datetime::Timestamp::timestamp(),
625 }
626 }
627}
628#[derive(Debug, Clone)]
630pub enum ApiType {
631 Json,
633 Redirect,
636 Download,
639 Preview,
642 Txt,
644 Html,
646}
647impl ApiType {
648 pub fn str(&mut self) -> String {
649 match self {
650 ApiType::Json => "application/json",
651 ApiType::Redirect | ApiType::Download | ApiType::Preview => "text/html",
652 ApiType::Txt => "text/plain",
653 ApiType::Html => "text/html",
654 }.to_string()
655 }
656}