1pub mod action;
3pub mod addon;
5pub mod module;
7pub mod request;
9pub mod swagger;
11pub mod tools;
13
14use crate::action::Action;
15use crate::addon::Addon;
16use crate::module::Module;
17use crate::request::Request;
18use crate::tools::{Tools, ToolsConfig};
19#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql"))]
20use br_db::types::TableOptions;
21use json::{array, object, JsonValue};
22use lazy_static::lazy_static;
23use log::info;
24use std::collections::HashMap;
25use std::path::PathBuf;
26use std::sync::Mutex;
27use std::{fs, io};
28
29lazy_static! {
30 static ref PLUGIN_TOOLS: Mutex<HashMap<String,Tools>> =Mutex::new(HashMap::new());
32}
33
34pub trait Plugin {
36 fn addon(name: &str) -> Result<Box<dyn Addon>, String>;
38 fn module(name: &str) -> Result<Box<dyn Module>, String> {
40 let res = name.split(".").collect::<Vec<&str>>();
41 if res.len() < 2 {
42 return Err("模型格式不正确".to_string());
43 }
44 Self::addon(res[0])?.module(res[1])
45 }
46 fn action(name: &str) -> Result<Box<dyn Action>, String> {
48 let res = name.split(".").collect::<Vec<&str>>();
49 if res.len() < 3 {
50 return Err("动作格式不正确".to_string());
51 }
52 Self::addon(res[0])?.module(res[1])?.action(res[2])
53 }
54 fn api_run(name: &str, request: Request) -> Result<JsonValue, String> {
56 let res = name.split(".").collect::<Vec<&str>>();
57 if res.len() != 3 {
58 return Err("action格式不正确".to_string());
59 }
60 match Self::addon(res[0])?.module(res[1])?.action(res[2])?.run(request) {
61 Ok(e) => Ok(e.data),
62 Err(e) => Err(e.message),
63 }
64 }
65 fn api(name: &str) -> Result<Box<dyn Action>, String> {
67 let res = name.split(".").collect::<Vec<&str>>();
68 if res.len() != 3 {
69 return Err("api 格式不正确".to_string());
70 }
71 Self::addon(res[0])?.module(res[1])?.action(res[2])
72 }
73 fn load_tools(config: ToolsConfig) -> Result<(), String> {
76 if PLUGIN_TOOLS.lock().unwrap().get("tools").is_none() {
77 let res = Tools::new(config)?;
78 PLUGIN_TOOLS.lock().unwrap().insert("tools".into(), res.clone());
79 }
80 Ok(())
81 }
82 fn get_tools() -> Tools {
84 let tools = PLUGIN_TOOLS.lock().unwrap();
85 let tools = tools.get("tools").unwrap().clone();
86 tools
87 }
88 #[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql"))]
92 fn init_db(file_path: PathBuf) -> Result<Vec<String>, String> {
93 info!("=============数据库更新开始=============");
94 let list = match fs::read_to_string(file_path.clone()) {
95 Ok(e) => json::parse(&e).unwrap_or(array![]),
96 Err(e) => return Err(format!("加载API清单失败: {}", e)),
97 };
98 let mut api_list = vec![];
99 let mut table_name_list = vec![];
100 for api_name in list.members() {
101 let api_info = api_name.as_str().unwrap_or("").split(".").collect::<Vec<&str>>();
102 let mut addon = match Self::addon(api_info[0]) {
103 Ok(e) => e,
104 Err(_) => {
105 continue;
106 }
107 };
108 let mut module = match addon.module(api_info[1]) {
109 Ok(e) => e,
110 Err(_) => {
111 continue;
112 }
113 };
114 let mut action = match module.action(api_info[2]) {
115 Ok(e) => e,
116 Err(_) => {
117 continue;
118 }
119 };
120
121 if action.auth() || action.public() {
122 api_list.push(api_name.to_string().clone());
123 }
124
125 if !module.table() {
126 continue;
127 }
128 let mut opt = TableOptions::default();
129
130 let unique = module.table_unique().iter().map(|x| x.to_string()).collect::<Vec<String>>();
131 let unique = unique.iter().map(|x| x.as_str()).collect::<Vec<&str>>();
132
133 let index = module.table_index().iter().map(|x| x.iter().map(|&y| y.to_string()).collect::<Vec<String>>()).collect::<Vec<Vec<String>>>();
134 let index = index.iter().map(|x| x.iter().map(|y| y.as_str()).collect::<Vec<&str>>()).collect::<Vec<Vec<&str>>>();
135
136 opt.set_table_name(module._table_name());
137 opt.set_table_title(module.title());
138 opt.set_table_key(module.table_key());
139 opt.set_table_fields(module.fields().clone());
140 opt.set_table_unique(unique);
141 opt.set_table_index(index);
142 opt.set_table_partition(module.table_partition());
143 opt.set_table_partition_columns(module.table_partition_columns());
144
145 if table_name_list.contains(&module._table_name()) {
146 continue;
147 }
148 table_name_list.push(module._table_name());
149 if Self::get_tools().db.table_is_exist(module._table_name()) {
150 let res = Self::get_tools().db.table_update_new(opt);
151 match res.as_i32().unwrap() {
152 -1 => {}
153 0 => {
154 info!("数据库更新情况: {} 失败", module._table_name());
155 }
156 1 => {
157 info!("数据库更新情况: {} 成功", module._table_name());
158 }
159 _ => {}
160 }
161 } else {
162 let res = Self::get_tools().db.table_create_new(opt);
163 info!("安装完成情况: {} {}", module._table_name(), res);
164 }
165 }
166 info!("=============数据库更新完成=============");
167 info!("=============API数量: {} 条=============", api_list.len());
168 fs::write(file_path, JsonValue::from(api_list.clone()).to_string()).unwrap();
169 Ok(api_list)
170 }
171 fn swagger(
173 title: &str,
174 description: &str,
175 version: &str,
176 uaturl: &str,
177 produrl: &str,
178 tags: JsonValue,
179 paths: JsonValue,
180 ) -> JsonValue {
181 let info = object! {
182 openapi:"3.0.0",
183 info:{
184 title:title,
185 description:description,
186 version:version
187 },
188 components: {
189 securitySchemes: {
190 BearerToken: {
191 "type": "http",
192 "scheme": "bearer",
193 "bearerFormat": "Token"
194 }
195 }
196 },
197 tags:tags,
198 security: [
199 {
200 "BearerToken": []
201 }
202 ],
203 servers:[
204 {
205 "url":uaturl,
206 "description": "测试地址"
207 },
208 {
209 "url":produrl,
210 "description": "正式地址"
211 }
212 ],
213 paths:paths
214 };
215 info
216 }
217 fn generate_api_list(apipath: PathBuf, path: PathBuf, index: usize) -> io::Result<Vec<String>> {
219 #[cfg(debug_assertions)]
220 {
221 let mut plugin_list = vec![];
222 if path.is_dir() {
223 let res = fs::read_dir(path);
224 match res {
225 Ok(entries) => {
226 for entry in entries {
227 let entry = entry.unwrap();
228 let path = entry.path();
229 if path.is_dir() {
230 let res = Self::generate_api_list(
231 apipath.clone(),
232 path.to_str().unwrap().parse().unwrap(),
233 index + 1,
234 )?;
235 plugin_list.extend(res);
236 } else if path.is_file() {
237 if path.to_str().unwrap().ends_with("mod.rs") {
238 continue;
239 }
240 let addon = path.parent().unwrap().parent().unwrap().file_name().unwrap().to_str().unwrap();
241 let model = path.parent().unwrap().file_name().unwrap().to_str().unwrap();
242 let action = path.file_name().unwrap().to_str().unwrap().trim_end_matches(".rs");
243 plugin_list.push(format!("{}.{}.{}", addon, model, action));
244 }
245 }
246 }
247 Err(e) => {
248 return Err(std::io::Error::new(
249 std::io::ErrorKind::Other,
250 e.to_string(),
251 ))
252 }
253 }
254 }
255 if index == 0 {
256 fs::create_dir_all(apipath.clone().parent().unwrap()).unwrap();
257 fs::write(apipath, JsonValue::from(plugin_list.clone()).to_string()).unwrap();
258 }
259 Ok(plugin_list)
260 }
261 #[cfg(not(debug_assertions))]
262 {
263 let apis = fs::read_to_string(apipath).unwrap();
264 let apis = json::parse(&apis).unwrap();
265 let mut apis = apis.members().map(|x| x.as_str().unwrap().to_string()).collect::<Vec<String>>();
266 Ok(apis)
267 }
268 }
269}
270#[derive(Debug, Clone)]
272pub struct ApiResponse {
273 pub types: ApiType,
274 pub code: i32,
275 pub message: String,
276 pub data: JsonValue,
277 pub success: bool,
278}
279impl ApiResponse {
280 pub fn json(self) -> JsonValue {
281 match self.types {
282 ApiType::Json => object! {
283 code: self.code,
284 message: self.message,
285 data: self.data,
286 success: self.success
287 },
288 ApiType::Redirect => self.data,
289 ApiType::Download => self.data,
290 ApiType::Preview => self.data,
291 ApiType::Txt => self.data,
292 }
293 }
294 pub fn swagger(&mut self) -> JsonValue {
295 let mut content = object! {};
296 content[self.types.str().as_str()] = object! {};
297 content[self.types.str().as_str()]["schema"]["type"] = if self.data.is_array() {
298 "array"
299 } else {
300 "object"
301 }.into();
302 content[self.types.str().as_str()]["schema"]["properties"] = self.data.clone();
303
304 content[self.types.str().as_str()]["schema"]["type"] = match content[self.types.str().as_str()]["schema"]["type"].as_str().unwrap() {
305 "int" => "integer".into(),
306 _ => content[self.types.str().as_str()]["schema"]["type"].clone(),
307 };
308 let data = object! {
309 "description":self.message.clone(),
310 "content":content
311 };
312 data
313 }
314 pub fn success(data: JsonValue, mut message: &str) -> Self {
315 if message.is_empty() {
316 message = "success";
317 }
318 Self {
319 success: true,
320 types: ApiType::Json,
321 code: 0,
322 message: message.to_string(),
323 data,
324 }
325 }
326 pub fn fail(code: i32, message: &str) -> Self {
327 Self {
328 types: ApiType::Json,
329 code,
330 message: message.to_string(),
331 data: JsonValue::Null,
332 success: false,
333 }
334 }
335 pub fn error(data: JsonValue, message: &str) -> Self {
336 Self {
337 types: ApiType::Json,
338 code: -1,
339 message: message.to_string(),
340 data,
341 success: false,
342 }
343 }
344 pub fn redirect(url: &str) -> Self {
346 Self {
347 types: ApiType::Redirect,
348 code: 0,
349 message: "".to_string(),
350 data: url.into(),
351 success: true,
352 }
353 }
354 pub fn download(filename: &str) -> Self {
356 Self {
357 types: ApiType::Download,
358 code: 0,
359 message: "".to_string(),
360 data: filename.into(),
361 success: true,
362 }
363 }
364 pub fn preview(filename: &str) -> Self {
366 Self {
367 types: ApiType::Preview,
368 code: 0,
369 message: "".to_string(),
370 data: filename.into(),
371 success: true,
372 }
373 }
374 pub fn txt(txt: &str) -> Self {
376 Self {
377 types: ApiType::Txt,
378 code: 0,
379 message: "".to_string(),
380 data: txt.into(),
381 success: true,
382 }
383 }
384}
385impl Default for ApiResponse {
386 fn default() -> Self {
387 Self {
388 types: ApiType::Json,
389 code: 0,
390 message: "".to_string(),
391 data: JsonValue::Null,
392 success: false,
393 }
394 }
395}
396#[derive(Debug, Clone)]
398pub enum ApiType {
399 Json,
401 Redirect,
404 Download,
407 Preview,
410 Txt,
412}
413impl ApiType {
414 pub fn str(&mut self) -> String {
415 match self {
416 ApiType::Json => "application/json",
417 ApiType::Redirect | ApiType::Download | ApiType::Preview => "text/html",
418 ApiType::Txt => "text/plain",
419 }.to_string()
420 }
421}