diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3ef0f03 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + ".\\src-tauri\\Cargo.toml" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index f2e2ad3..4a11422 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@fontsource/fira-mono": "^4.5.10", "@neoconfetti/svelte": "^1.0.0", "@smui/button": "7.0.0-beta.16", + "@smui/checkbox": "7.0.0-beta.16", "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.3", "@sveltejs/kit": "^1.27.4", @@ -27,6 +28,7 @@ "eslint-plugin-svelte": "^2.30.0", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", + "shiki": "^1.1.7", "svelte": "^4.2.7", "svelte-check": "^3.6.0", "tslib": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9ee841..e908605 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ dependencies: '@smui/linear-progress': specifier: 7.0.0-beta.16 version: 7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3) - '@smui/snackbar': - specifier: 7.0.0-beta.16 - version: 7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3) '@smui/tab': specifier: 7.0.0-beta.16 version: 7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3) @@ -46,6 +43,9 @@ devDependencies: '@smui/button': specifier: 7.0.0-beta.16 version: 7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3) + '@smui/checkbox': + specifier: 7.0.0-beta.16 + version: 7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3) '@sveltejs/adapter-auto': specifier: ^2.0.0 version: 2.1.1(@sveltejs/kit@1.30.3) @@ -70,9 +70,6 @@ devDependencies: '@zerodevx/svelte-toast': specifier: ^0.9.5 version: 0.9.5(svelte@4.2.8) - carbon-components-svelte: - specifier: ^0.82.0 - version: 0.82.4 eslint: specifier: ^8.28.0 version: 8.55.0 @@ -88,6 +85,9 @@ devDependencies: prettier-plugin-svelte: specifier: ^3.1.2 version: 3.1.2(prettier@3.1.1)(svelte@4.2.8) + shiki: + specifier: ^1.1.7 + version: 1.1.7 svelte: specifier: ^4.2.7 version: 4.2.8 @@ -477,7 +477,6 @@ packages: '@material/theme': 14.0.0 '@material/touch-target': 14.0.0 tslib: 2.6.2 - dev: false /@material/chips@14.0.0: resolution: {integrity: sha512-SfZX/Ovdq4NgjdtIr/N1O3fEHisZC+t8G8629OV/NrniSS6rKOa+q1mImzna8R4pfuYO+7nT5nZewQpL/JSYaQ==} @@ -1048,6 +1047,10 @@ packages: resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} dev: true + /@shikijs/core@1.1.7: + resolution: {integrity: sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==} + dev: true + /@smui-extra/accordion@7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3): resolution: {integrity: sha512-Y0oYzhjcMjx471Bp44tDK5w2MZB7IovNIiwkkPX7Bieuuwucvt3kuiMzAGWsWx9JZINRkfYK4FtuO92CEElzFQ==} dependencies: @@ -1159,7 +1162,6 @@ packages: transitivePeerDependencies: - svelte - typescript - dev: false /@smui/chips@7.0.0-beta.16(svelte@4.2.8)(typescript@5.3.3): resolution: {integrity: sha512-MwEsdUdUyPMzucHfMurGQ8G+EW/jZXgM3lWxL9Erul6Eua/whJuDznJEtAEGEbjjZ6HSTrfarVY274tXRcW7rg==} @@ -2053,12 +2055,6 @@ packages: engines: {node: '>=6'} dev: true - /carbon-components-svelte@0.82.4: - resolution: {integrity: sha512-fuqMl+KNJXMlGhuwK4M11ZnGcbHF6nR7TOiiq6UJr5ALCCen4jk71sMAOurUWbOTOVe4DXM4z+Sb6/BV/Gf1Ag==} - dependencies: - flatpickr: 4.6.9 - dev: true - /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2432,10 +2428,6 @@ packages: rimraf: 3.0.2 dev: true - /flatpickr@4.6.9: - resolution: {integrity: sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==} - dev: true - /flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true @@ -2995,6 +2987,12 @@ packages: engines: {node: '>=8'} dev: true + /shiki@1.1.7: + resolution: {integrity: sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==} + dependencies: + '@shikijs/core': 1.1.7 + dev: true + /sirv@2.0.3: resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} engines: {node: '>= 10'} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 898532d..a7e8443 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -288,15 +288,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.48.5", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -486,7 +488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -496,7 +498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" dependencies = [ "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -520,7 +522,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -531,7 +533,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -792,7 +794,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -1902,7 +1904,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -2093,7 +2095,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -2228,9 +2230,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2246,9 +2248,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2597,22 +2599,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -2634,7 +2636,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -2684,7 +2686,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -2883,9 +2885,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.40" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3249,7 +3251,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -3333,7 +3335,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -3465,7 +3467,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", ] [[package]] @@ -3522,14 +3524,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ts_bridge_derive" -version = "0.1.1" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "typenum" version = "1.17.0" @@ -3692,7 +3686,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -3726,7 +3720,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4299,11 +4293,11 @@ dependencies = [ name = "yapi-to-ts" version = "0.1.3" dependencies = [ + "chrono", "reqwest", "serde", "serde_json", "tauri", "tauri-build", "tokio", - "ts_bridge_derive", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b87cdbd..506e8c8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,12 +13,12 @@ edition = "2021" tauri-build = { version = "1.5", features = [] } [dependencies] -tauri = { version = "1.5", features = [ "shell-open", "dialog-all"] } -serde = { version = "1.0", features = ["derive"] } +tauri = { version = "1.5", features = [ "clipboard-all", "path-all", "shell-open", "dialog-all"] } +serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0" reqwest = { version = "0.11", features = ["json", "blocking" , "socks"] } tokio = { version = "1.33", features = ["full"] } -ts_bridge_derive = { path = "./ts_bridge_derive" } +chrono = "0.4.34" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/src-tauri/src/commands/global_config.rs b/src-tauri/src/commands/global_config.rs new file mode 100644 index 0000000..4ca13f5 --- /dev/null +++ b/src-tauri/src/commands/global_config.rs @@ -0,0 +1,89 @@ +use serde_json::json; +use tauri::{AppHandle, Manager}; + +use crate::{ + models::{ + global_config::GlobalConfigRequest, + web_response::WebResponse, + }, + services::{ + global_config::{ + get_global_config, get_latest_project_source_path, update_project, write_config, + }, + log::log_error, + }, +}; + +#[tauri::command] +pub fn load_global_config(app_handle: tauri::AppHandle) -> Result { + match get_global_config(&app_handle) { + Ok(global_config) => Ok(WebResponse { + message: String::from("获取全局配置成功!"), + data: Some(json!(global_config)), + }), + Err(e) => log_error(&app_handle, e.to_string()), + } +} + +// 初始化 +#[tauri::command] +pub fn add_project(source_path: &str, app_handle: AppHandle) -> Result { + match get_global_config(&app_handle) { + Ok(mut global_config) => { + match update_project(&app_handle, &source_path.to_string(), &mut global_config) { + Ok(_) => { + app_handle + .emit_all("load_project", source_path.to_string()) + .unwrap(); + Ok(WebResponse { + message: String::from("成功添加工程!"), + data: None, + }) + } + Err(e) => log_error(&app_handle, e.to_string()), + } + } + Err(e) => log_error(&app_handle, e.to_string()), + } +} + +#[tauri::command] +pub fn load_latest_project(app_handle: tauri::AppHandle) -> Result { + match get_latest_project_source_path(&app_handle) { + Ok(source_path) => { + app_handle.emit_all("load_project", source_path).unwrap(); + Ok(WebResponse { + message: String::from("获取最新项目成功!"), + data: None, + }) + } + Err(e) => { + app_handle + .emit_all("load_project_error", Some(e.to_string())) + .unwrap(); + log_error(&app_handle, e.to_string()) + } + } +} + +#[tauri::command] +pub fn update_global_config( + data: GlobalConfigRequest, + app_handle: AppHandle, +) -> Result { + let config = get_global_config(&app_handle); + match config { + Ok(mut global_config) => { + global_config.merge_from_request(data); + + match write_config(&app_handle, &global_config) { + Ok(_) => Ok(WebResponse { + message: String::from("更新成功!"), + data: None, + }), + Err(e) => log_error(&app_handle, e.to_string()), + } + } + Err(e) => log_error(&app_handle, e.to_string()), + } +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..1f8071c --- /dev/null +++ b/src-tauri/src/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod global_config; +pub mod yapi; \ No newline at end of file diff --git a/src-tauri/src/commands/yapi/category.rs b/src-tauri/src/commands/yapi/category.rs new file mode 100644 index 0000000..6f35833 --- /dev/null +++ b/src-tauri/src/commands/yapi/category.rs @@ -0,0 +1,43 @@ +use serde_json::json; +use tauri::AppHandle; + +use crate::{ + models::web_response::WebResponse, + services::{ + log::{log, log_error}, + yapi::{category::fetch_cat_interface_list, config::merge_interface_to_project_config}, + }, +}; + +#[tauri::command] +pub async fn get_cat_interface_list( + app_handle: AppHandle, + token: &str, + cat_id: u32, + source_path: &str, +) -> Result { + match fetch_cat_interface_list(cat_id, token.to_string(), source_path, &app_handle).await { + Ok(res) => { + for interface in &res.list { + match merge_interface_to_project_config(interface, source_path, &cat_id.to_string()) + { + Ok(_) => { + log( + &app_handle, + format!("分类{}的接口{}合并到配置文件成功", cat_id, interface._id), + ); + } + Err(err) => { + log(&app_handle, err.to_string()); + } + } + } + + Ok(WebResponse { + data: Some(json!(res)), + message: format!("获取分类{}的接口信息成功", cat_id), + }) + } + Err(err) => log_error(&app_handle, err.to_string()), + } +} diff --git a/src-tauri/src/commands/yapi/config.rs b/src-tauri/src/commands/yapi/config.rs new file mode 100644 index 0000000..9aa966a --- /dev/null +++ b/src-tauri/src/commands/yapi/config.rs @@ -0,0 +1,53 @@ +use tauri::AppHandle; + +use crate::{ + models::{web_response::WebResponse, yapi::config::YapiConfigRequest}, + services::{ + log::log_error, + yapi::config::{get_project_config, init_project_config, write_project_config}, + }, +}; + +#[tauri::command] +pub fn update_project_config( + source_path: &str, + data: YapiConfigRequest, + app_handle: AppHandle, +) -> Result { + let config = get_project_config(source_path); + match config { + Ok(mut config) => { + config.merge_from_request(data); + + match write_project_config(source_path, config) { + Ok(_) => Ok(WebResponse { + message: String::from("更新成功!"), + data: None, + }), + Err(e) => log_error(&app_handle, e.to_string()), + } + } + Err(e) => log_error(&app_handle, e.to_string()), + } +} + +#[tauri::command] +pub fn load_project_config(source_path: &str, app_handle: AppHandle) -> Result { + match get_project_config(source_path) { + Ok(config) => { + if config.base_url.is_empty() { + return Err("请初始化配置".to_string()); + } + Ok(WebResponse { + message: String::from("获取项目成功!"), + data: Some(config.into()), + }) + } + Err(e) => { + if let Err(e) = init_project_config(source_path.to_string()) { + return Err(format!("初始化配置文件失败,错误信息:{}", e.to_string())); + } + log_error(&app_handle, format!("获取项目失败! {}", e.to_string())) + } + } +} diff --git a/src-tauri/src/commands/yapi/interface.rs b/src-tauri/src/commands/yapi/interface.rs new file mode 100644 index 0000000..5357cd4 --- /dev/null +++ b/src-tauri/src/commands/yapi/interface.rs @@ -0,0 +1,92 @@ +use serde_json::json; +use tauri::{async_runtime::spawn, AppHandle, Manager, State}; + +use crate::{ + models::{ + web_response::WebResponse, + yapi::{interface::InterfaceFetchParams, queue::Queue}, + }, + services::{ + log::log_error, + yapi::interface::{ + fetch_interface_detail, get_interface_ts_string, write_content_to_interface_path, + }, + }, +}; + +#[tauri::command] +pub async fn add_interface_task( + app_handle: AppHandle, + data: InterfaceFetchParams, +) -> Result { + let queue: State<'_, Queue> = app_handle.state(); + + queue.add_task(data).await; + + Ok(WebResponse { + data: None, + message: "已添加任务".to_string(), + }) +} + +#[tauri::command] +pub async fn start_task(app_handle: AppHandle) -> Result { + let queue: State<'_, Queue> = app_handle.state(); + let count = queue.waiting_queue.lock().await.len(); + spawn(async move { + let app_handle_clone = app_handle.clone(); + let queue: State<'_, Queue> = app_handle_clone.state(); + queue.start_execute(&app_handle_clone).await; + }); + + Ok(WebResponse { + data: Some(json!(count)), + message: "已启动跑批任务".to_string(), + }) +} + +#[tauri::command] +pub async fn cancel_task(app_handle: AppHandle) -> Result { + let queue: State<'_, Queue> = app_handle.state(); + queue.cancel_execute().await; + Ok(WebResponse { + data: None, + message: "已停止跑批任务".to_string(), + }) +} + +#[tauri::command] +pub fn write_to_file( + path: String, + content: String, + source_path: &str, + app_handle: AppHandle, +) -> Result { + match write_content_to_interface_path(path, source_path, content) { + Err(e) => log_error(&app_handle, e.to_string()), + Ok(_) => Ok(WebResponse { + data: None, + message: "已写入文件".to_string(), + }), + } +} + +#[tauri::command] +pub async fn get_interface_detail( + app_handle: AppHandle, + data: InterfaceFetchParams, +) -> Result { + match fetch_interface_detail(data, &app_handle).await { + Err(e) => log_error(&app_handle, e.to_string()), + Ok(res) => match get_interface_ts_string(&res) { + Ok(ts) => Ok(WebResponse { + data: Some(json!({ + "interface_data" : res, + "ts": ts + })), + message: "获取成功".to_string(), + }), + Err(e) => log_error(&app_handle, e.to_string()), + }, + } +} diff --git a/src-tauri/src/commands/yapi/mod.rs b/src-tauri/src/commands/yapi/mod.rs new file mode 100644 index 0000000..604ab0c --- /dev/null +++ b/src-tauri/src/commands/yapi/mod.rs @@ -0,0 +1,5 @@ +pub mod category; +pub mod config; +pub mod interface; +pub mod project; +pub mod request; \ No newline at end of file diff --git a/src-tauri/src/commands/yapi/project.rs b/src-tauri/src/commands/yapi/project.rs new file mode 100644 index 0000000..d96399f --- /dev/null +++ b/src-tauri/src/commands/yapi/project.rs @@ -0,0 +1,77 @@ +use serde_json::json; +use tauri::AppHandle; + +use crate::{ + models::web_response::WebResponse, + services::{ + log::{log, log_error}, + yapi::{ + config::{merge_category_to_project_config, merge_yapi_project_to_project_config}, + project::{fetch_project_base_info, fetch_project_cat_menu}, + }, + }, +}; + +#[tauri::command] +pub async fn get_yapi_project_base_info( + app_handle: AppHandle, + token: &str, + source_path: &str, +) -> Result { + match fetch_project_base_info(token.to_string(), source_path, &app_handle).await { + Ok(yapi_project_base_info) => { + match merge_yapi_project_to_project_config(source_path, &yapi_project_base_info, token) + { + Ok(_) => { + log( + &app_handle, + format!("更新项目{}至配置文件成功", yapi_project_base_info.name), + ); + } + Err(e) => { + log(&app_handle, e.to_string()); + } + } + Ok(WebResponse { + message: format!("获取yapi项目{}成功", yapi_project_base_info.name), + data: Some(json!(yapi_project_base_info)), + }) + } + Err(err) => log_error(&app_handle, err.to_string()), + } +} + +#[tauri::command] +pub async fn get_yapi_project_cat_menu( + app_handle: AppHandle, + token: &str, + project_id: u32, + source_path: &str, +) -> Result { + match fetch_project_cat_menu(project_id, token.to_string(), source_path, &app_handle).await { + Ok(res) => { + for category in &res { + match merge_category_to_project_config( + category, + source_path, + &project_id.to_string(), + ) { + Ok(_) => { + log( + &app_handle, + format!("更新类目{}至配置文件成功", category.name), + ); + } + Err(e) => { + log(&app_handle, e.to_string()); + } + } + } + Ok(WebResponse { + message: "获取yapi项目分类菜单成功".to_string(), + data: Some(json!(res)), + }) + } + Err(err) => log_error(&app_handle, err.to_string()), + } +} diff --git a/src-tauri/src/commands/yapi/request.rs b/src-tauri/src/commands/yapi/request.rs new file mode 100644 index 0000000..ad643b2 --- /dev/null +++ b/src-tauri/src/commands/yapi/request.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; + +use serde_json::json; +use tauri::AppHandle; + +use crate::{ + models::web_response::WebResponse, + services::{ + log::log_error, + request::{get_file_tree, get_request_ts_string, write_request_ts_file}, + }, +}; + +#[tauri::command] +pub fn load_file_tree(source_path: &str, app_handle: AppHandle) -> Result { + match get_file_tree(source_path, None) { + Ok(file_tree) => Ok(WebResponse { + data: Some(json!(file_tree)), + message: "获取types文件树成功".to_string(), + }), + Err(err) => log_error(&app_handle, err.to_string()), + } +} + +#[tauri::command] +pub fn get_request_string( + source_path: &str, + app_handle: AppHandle, + path: &str, +) -> Result { + match get_request_ts_string(source_path, &PathBuf::from(path)) { + Ok(request_string) => Ok(WebResponse { + data: Some(json!(request_string)), + message: "获取请求字符串成功".to_string(), + }), + Err(err) => log_error(&app_handle, err.to_string()), + } +} + +#[tauri::command] +pub fn write_request_to_file( + source_path: &str, + app_handle: AppHandle, + path: &str, + content: String, +) -> Result { + match write_request_ts_file(source_path, &PathBuf::from(path), content) { + Ok(_) => Ok(WebResponse { + data: None, + message: "写入文件成功".to_string(), + }), + Err(err) => log_error(&app_handle, err.to_string()), + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2ce5f0f..c2bf973 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,34 +1,24 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::sync::Arc; +use models::yapi::queue::Queue; +use services::{conversion::path_buf_to_string, global_config::init_config}; +use tauri::{api::dialog, CustomMenuItem, Manager, Menu, MenuItem, Submenu}; -use serde_json::json; -use services::config::GetConfigRequest; -use structs::{context::ContextGlobal, queue::Queue}; -use tauri::{api::dialog, async_runtime::spawn, CustomMenuItem, Manager, Menu, Submenu, MenuItem}; -use tokio::sync::Mutex; - -use crate::services::{ - category::update_categories, - common::{execute, pause}, - config::{get_config, read_config, update_config}, - interface::update_interface, - project::update_projects, - request::{check_request_config, get_request_list, update_request}, +use crate::commands::{ + global_config::{add_project, load_global_config, load_latest_project, update_global_config}, + yapi::category::get_cat_interface_list, + yapi::config::{load_project_config, update_project_config}, + yapi::interface::{ + add_interface_task, cancel_task, get_interface_detail, start_task, write_to_file, + }, + yapi::project::{get_yapi_project_base_info, get_yapi_project_cat_menu}, + yapi::request::{get_request_string, load_file_tree, write_request_to_file}, }; +pub mod commands; +pub mod models; pub mod services; -pub mod structs; -pub mod utils; - -const DEAFULT_RATE_LIMIT: usize = 2; - -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} fn main() { let add = CustomMenuItem::new("add".to_string(), "添加代码库"); @@ -44,66 +34,58 @@ fn main() { .add_native_item(MenuItem::Paste) .add_native_item(MenuItem::SelectAll), ); - let menu = Menu::new() - .add_submenu(file_menu) - .add_submenu(edit_menu); + let menu = Menu::new().add_submenu(file_menu).add_submenu(edit_menu); tauri::Builder::default() - .manage(ContextGlobal { - source_path: Arc::new(Mutex::new(None)), - }) - // .manage(Queue::new(DEAFULT_RATE_LIMIT, 0)) .setup(|app| { - let main_window = app.get_window("main").unwrap(); + let app_handle = app.handle(); + init_config(&app_handle).unwrap(); - app.manage(Queue::new(DEAFULT_RATE_LIMIT, 0, main_window)); + app.manage(Queue::new(&app_handle)); Ok(()) }) .menu(menu) .on_menu_event(|event| { let window = event.window().clone(); - let handler: tauri::AppHandle = window.app_handle(); - let handler_clone = handler.clone(); + let app_handle = window.app_handle(); + // let handler_clone = handler.clone(); match event.menu_item_id() { - "add" => dialog::FileDialogBuilder::default().pick_folder(move |dir_path| { - if let Some(dir_path) = dir_path { - spawn(async move { - match read_config( - json!(GetConfigRequest { - dir_path: dir_path.to_str().unwrap().to_string(), - }) - .to_string(), - handler_clone, - ) - .await - { - Ok(res) => { - window.emit_all("init_completed", res.message).unwrap(); - } - Err(e) => { - window.emit_all("missing_config", e).unwrap(); - } + "add" => dialog::FileDialogBuilder::default().pick_folder(move |source_path| { + if let Some(source_path) = source_path { + let source_path = path_buf_to_string(source_path); + + match add_project(&source_path, app_handle) { + Ok(_) => { + window.emit_all("load_project", source_path).unwrap(); + } + Err(e) => { + window.emit_all("load_project_error", e).unwrap(); } - }); + } } }), _ => {} } }) .invoke_handler(tauri::generate_handler![ - greet, - read_config, - update_config, - get_config, - update_categories, - update_interface, - update_projects, - execute, - pause, - get_request_list, - check_request_config, - update_request, + update_global_config, + add_project, + load_latest_project, + load_project_config, + update_project_config, + get_yapi_project_base_info, + get_yapi_project_cat_menu, + get_cat_interface_list, + add_interface_task, + start_task, + cancel_task, + write_to_file, + load_global_config, + load_file_tree, + get_request_string, + write_request_to_file, + get_interface_detail, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/models/file.rs b/src-tauri/src/models/file.rs new file mode 100644 index 0000000..c9b8523 --- /dev/null +++ b/src-tauri/src/models/file.rs @@ -0,0 +1,10 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct FileTree { + pub full_path: PathBuf, + pub name: String, + pub children: Box>, +} \ No newline at end of file diff --git a/src-tauri/src/models/global_config.rs b/src-tauri/src/models/global_config.rs new file mode 100644 index 0000000..2446cfe --- /dev/null +++ b/src-tauri/src/models/global_config.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct GlobalConfig { + // 项目工程 + pub projects: Vec, + // 请求间隔 + pub break_seconds: u64, + // 并行请求数 + pub rate_limit: usize, + // 代理 + pub proxy: Option, +} + +impl Default for GlobalConfig { + fn default() -> Self { + GlobalConfig { + projects: Vec::new(), + break_seconds: 3, + rate_limit: 5, + proxy: None, + } + } +} + +impl GlobalConfig { + pub fn merge_from_request(&mut self, update: GlobalConfigRequest) { + if let Some(rate_limit) = update.rate_limit { + self.rate_limit = rate_limit; + } + if let Some(break_seconds) = update.break_seconds { + self.break_seconds = break_seconds; + } + if let Some(proxy) = update.proxy { + self.proxy = Some(proxy); + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GlobalConfigRequest { + pub rate_limit: Option, + pub break_seconds: Option, + pub proxy: Option, +} diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs new file mode 100644 index 0000000..704d8d4 --- /dev/null +++ b/src-tauri/src/models/mod.rs @@ -0,0 +1,5 @@ +pub mod web_response; +pub mod global_config; +pub mod yapi; +pub mod notification; +pub mod file; \ No newline at end of file diff --git a/src-tauri/src/models/notification.rs b/src-tauri/src/models/notification.rs new file mode 100644 index 0000000..526bf73 --- /dev/null +++ b/src-tauri/src/models/notification.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub enum NotificationDesc { + Success, + Error, +} + + +#[derive(Serialize, Deserialize, Clone)] +pub struct Notification { + pub message: String, + pub desc: NotificationDesc, +} diff --git a/src-tauri/src/models/web_response.rs b/src-tauri/src/models/web_response.rs new file mode 100644 index 0000000..17749af --- /dev/null +++ b/src-tauri/src/models/web_response.rs @@ -0,0 +1,14 @@ +#[derive(serde::Serialize)] +pub struct WebResponse { + pub message: String, + pub data: Option, +} + +impl Default for WebResponse { + fn default() -> Self { + Self { + message: "".to_string(), + data: None, + } + } +} \ No newline at end of file diff --git a/src-tauri/src/models/yapi/category.rs b/src-tauri/src/models/yapi/category.rs new file mode 100644 index 0000000..818a0bf --- /dev/null +++ b/src-tauri/src/models/yapi/category.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +// 分类菜单列表项 +#[derive(Debug, Deserialize, Serialize)] +pub struct CategoryMenuItem { + pub _id: u32, + pub name: String, + pub interfaces : Option +} + + +// 分类详情列表 +#[derive(Debug, Deserialize, Serialize)] +pub struct CategoryDataList { + pub count : u32, + pub total : u32, + pub list : Vec +} + +// 分类详情 +#[derive(Debug, Deserialize, Serialize , Clone)] +pub struct InterfaceDataItem { + pub _id : u32, + pub catid : u32, + pub title : String, + pub path : String, +} diff --git a/src-tauri/src/models/yapi/config.rs b/src-tauri/src/models/yapi/config.rs new file mode 100644 index 0000000..f3cedba --- /dev/null +++ b/src-tauri/src/models/yapi/config.rs @@ -0,0 +1,99 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct YapiConfig { + pub base_url: String, + pub types_path: String, + pub project_list: Vec, + pub request_path: String, + pub request_template: String, + pub header_template: String, + pub file_name_template: String, + pub type_import_template: String, +} + +impl Default for YapiConfig { + fn default() -> Self { + Self { + base_url: String::new(), + types_path: String::new(), + project_list: Vec::new(), + request_path: String::new(), + request_template: String::new(), + header_template: String::new(), + file_name_template: String::new(), + type_import_template: String::new(), + } + } +} + +impl Into for YapiConfig { + fn into(self) -> Value { + json!(self) + } +} + +impl YapiConfig { + pub fn new() -> Self { + Self::default() + } + pub fn merge_from_request(&mut self, yapi_config_request: YapiConfigRequest) { + // if yapi_config_request has some value , merge this property to YapiConfig + if let Some(base_url) = yapi_config_request.base_url { + self.base_url = base_url; + } + if let Some(types_path) = yapi_config_request.types_path { + self.types_path = types_path; + } + if let Some(request_path) = yapi_config_request.request_path { + self.request_path = request_path; + } + if let Some(request_template) = yapi_config_request.request_template { + self.request_template = request_template; + } + if let Some(header_template) = yapi_config_request.header_template { + self.header_template = header_template; + } + if let Some(file_name_template) = yapi_config_request.file_name_template { + self.file_name_template = file_name_template; + } + if let Some(type_import_template) = yapi_config_request.type_import_template { + self.type_import_template = type_import_template; + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct YapiConfigRequest { + pub base_url: Option, + pub types_path: Option, + pub request_path: Option, + pub request_template: Option, + pub header_template: Option, + pub file_name_template: Option, + pub type_import_template: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct YapiProject { + pub token: String, + pub project_id: String, + pub project_name: Option, + pub categories: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct YapiCategory { + pub id: String, + pub name: String, + pub interfaces: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct YapiInterface { + pub id: String, + pub name: String, + pub path: Option, + pub lock: Option +} diff --git a/src-tauri/src/models/yapi/interface.rs b/src-tauri/src/models/yapi/interface.rs new file mode 100644 index 0000000..2fa5d79 --- /dev/null +++ b/src-tauri/src/models/yapi/interface.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +// 接口详情 +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct InterfaceData { + pub _id: u32, + pub path: String, + pub project_id: u32, + pub title: String, + pub catid: u32, + pub req_body_other: Option, + pub req_query: Option>, + pub req_params: Option>, + pub req_body_form: Option>, + pub req_body_type: Option, + pub res_body: Option, + pub method: String, +} + +#[derive(Debug, Deserialize)] +pub struct InterfaceFetchParams { + pub interface_id: u32, + pub token: String, + pub source_path: String, +} + +#[derive(PartialEq, Debug)] +pub enum JsonType { + Object, + Array, + Atom, + Unknown, +} + +#[derive(PartialEq, Debug, Deserialize, Serialize, Clone)] +pub enum FormType { + Form, + Json, +} + +#[derive(PartialEq, Clone, Debug)] +pub enum ObjectType { + Object, + Array, +} + +#[derive(PartialEq, Debug)] +pub enum WebType { + Request, + Response, +} + +#[derive(Clone, Debug)] +pub enum JsonValue { + ObjectLike(ObjectLike), + Atom(Atom), + Null, +} + +#[derive(Clone, Debug)] +pub struct Atom { + pub value: String, + pub required: bool, + pub key: String, + pub description: String, +} + +#[derive(Clone, Debug)] +pub struct Node { + pub interface_name: String, + pub key: String, + pub value: JsonValue, + pub required: bool, + pub description: String, +} + +#[derive(Clone, Debug)] +pub struct ObjectLike { + pub interface_name: String, + pub nodes: Vec, + pub object_type: ObjectType, + pub required: bool, + pub key: String, + pub description: String, +} + +#[derive(Debug)] +pub struct Root { + pub interface_name: String, + pub interface_desc: String, + pub key: String, + pub children: Option>, +} diff --git a/src-tauri/src/structs/mod.rs b/src-tauri/src/models/yapi/mod.rs similarity index 58% rename from src-tauri/src/structs/mod.rs rename to src-tauri/src/models/yapi/mod.rs index 8f60531..6a0843e 100644 --- a/src-tauri/src/structs/mod.rs +++ b/src-tauri/src/models/yapi/mod.rs @@ -1,9 +1,6 @@ +pub mod config; pub mod project; +pub mod web_response; pub mod category; pub mod interface; -pub mod web_response; -pub mod context; -pub mod config; -pub mod resolver; -pub mod queue; -pub mod request; \ No newline at end of file +pub mod queue; \ No newline at end of file diff --git a/src-tauri/src/models/yapi/project.rs b/src-tauri/src/models/yapi/project.rs new file mode 100644 index 0000000..2aed2f3 --- /dev/null +++ b/src-tauri/src/models/yapi/project.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct YapiProjectBaseInfo { + pub _id: u32, + pub desc: String, + pub name: String +} \ No newline at end of file diff --git a/src-tauri/src/models/yapi/queue.rs b/src-tauri/src/models/yapi/queue.rs new file mode 100644 index 0000000..77d3e51 --- /dev/null +++ b/src-tauri/src/models/yapi/queue.rs @@ -0,0 +1,167 @@ +use std::{ + collections::VecDeque, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; + +use serde::{Deserialize, Serialize}; +use tauri::{async_runtime::Mutex, AppHandle, Manager}; +use tokio::{sync::Semaphore, time::sleep}; + +use crate::services::{ + global_config::get_global_config, + yapi::interface::{fetch_interface_detail, get_interface_ts_string}, +}; + +use super::interface::{InterfaceData, InterfaceFetchParams}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ResolvedInterface { + pub interface: InterfaceData, + pub ts_string: String, +} + +#[derive(Debug, Clone)] +pub struct Queue { + pub semaphore: Arc, + pub waiting_queue: Arc>>, + pub running: Arc, + pub app_handle: Arc>, +} + +#[derive(Debug, Clone)] +pub struct ResultQueue { + pub list: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct EmitMessage { + pub msg: String, + pub is_success: bool, // 0:success,1:fail + pub resolved_interface: Option, +} + +impl Queue { + pub fn new(app_handle: &AppHandle) -> Queue { + let global_config = get_global_config(app_handle).unwrap(); + + Queue { + semaphore: Arc::new(Semaphore::new(global_config.rate_limit)), + waiting_queue: Arc::new(Mutex::new(VecDeque::new())), + running: Arc::new(AtomicBool::new(false)), + app_handle: Arc::new(Mutex::new(app_handle.clone())), + } + } + + pub async fn add_task(&self, interface_fetch_params: InterfaceFetchParams) { + self.waiting_queue + .lock() + .await + .push_back(interface_fetch_params); + + self.running.store(true, Ordering::Release); + } + + pub async fn cancel_execute(&self) { + self.running.store(false, Ordering::Relaxed); + self.clear().await; + } + + pub async fn start_execute(&self, app_handle: &AppHandle) { + let global_config = get_global_config(app_handle).unwrap(); + + while self.running.load(Ordering::Relaxed) { + // 消耗一个令牌 + self.acquire().await; + + let interface_data = { + let mut queue = self.waiting_queue.lock().await; + queue.pop_front() + }; + let app_handle = Arc::clone(&self.app_handle); + let sem = self.semaphore.clone(); + + match interface_data { + Some(fetch_interface_params) => { + tokio::spawn(async move { + let app_handle = app_handle.lock().await.clone(); + + match fetch_interface_detail(fetch_interface_params, &app_handle).await { + Ok(detail) => match get_interface_ts_string(&detail) { + Ok(ts_string) => { + let title = detail.title.clone(); + queue_log( + &app_handle, + Some(ResolvedInterface { + interface: detail, + ts_string, + }), + format!("接口 {} 已完成!", title), + true, + ) + } + Err(e) => { + queue_log( + &app_handle, + None, + format!("接口转换失败: {}", e.to_string()), + false, + ); + } + }, + Err(e) => { + queue_log( + &app_handle, + None, + format!("接口请求失败:{}", e.to_string()), + false, + ); + } + } + + sleep(Duration::from_secs(global_config.break_seconds)).await; + + // 补充一个令牌 + sem.add_permits(1); + }); + } + None => { + self.running.store(false, Ordering::Relaxed); + self.clear().await; + // 补充一个令牌 + sem.add_permits(1); + } + } + } + } + + async fn clear(&self) { + self.waiting_queue.lock().await.clear(); + } + + async fn acquire(&self) { + let permit = self.semaphore.acquire().await.unwrap(); + permit.forget(); + } +} + +fn queue_log( + app_handle: &AppHandle, + resolved_interface: Option, + msg: String, + is_success: bool, +) { + app_handle + .emit_all( + "queue_log", + EmitMessage { + msg, + is_success, + resolved_interface, + }, + ) + .unwrap(); +} diff --git a/src-tauri/src/models/yapi/web_response.rs b/src-tauri/src/models/yapi/web_response.rs new file mode 100644 index 0000000..b7de237 --- /dev/null +++ b/src-tauri/src/models/yapi/web_response.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +// 公共返回参数 +#[derive(Debug, Deserialize, Serialize)] +pub struct YapiResponse { + pub errcode : u32, + pub errmsg : String, + pub data : T +} \ No newline at end of file diff --git a/src-tauri/src/services/category.rs b/src-tauri/src/services/category.rs deleted file mode 100644 index 37f7e72..0000000 --- a/src-tauri/src/services/category.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, json}; -use tauri::{Manager, State}; - -use crate::structs::{ - category::Category, - config::Config, - context::{Context, ContextGlobal}, - interface::Interface, - queue::Queue, -}; - -use super::{common::CustomResponse, interface::add_interfaces_task}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CategoryList { - pub id: String, - pub name: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UpdateCategories { - pub categories: Vec, - pub token: String, - pub is_full_update: bool, -} - -// 更新分类 -#[tauri::command] -pub async fn update_categories( - data: String, - handle: tauri::AppHandle, -) -> Result { - let handle_clone = handle.clone(); - let context_global: State<'_, ContextGlobal> = handle.state(); - let queue: State<'_, Queue> = handle.state(); - - let data = from_str::(&data).unwrap(); - let categories = data.categories; - let token = data.token; - let source_path = context_global.source_path.lock().await.clone().unwrap(); - let context = Context::from_source_path(&source_path); - - let categories: Vec = categories - .iter() - .map(|cat| { - let id = cat.id.to_string(); - let name = cat.name.clone(); - Category::new(token.clone(), context.clone(), id, name) - }) - .collect(); - - if let Err(e) = - add_categories_task(&queue, categories, &data.is_full_update, &source_path).await - { - return Err(e); - } - - tauri::async_runtime::spawn(async move { - let queue: State<'_, Queue> = handle_clone.state(); - queue.start_task().await; - }); - - return Ok(CustomResponse { - message: String::from("开始执行任务序列"), - data: Some(json!(queue.total_count.lock().await.clone())), - }); -} - -pub async fn add_categories_task( - queue: &State<'_, Queue>, - categories: Vec, - is_full_update: &bool, - source_path: &PathBuf, -) -> Result<(), String> { - for category in categories { - // 待解析的interfaces - let result = category.fetch_interfaces().await; - - match result { - Ok(interfaces) => { - let tasks = match is_full_update { - true => interfaces, - false => filter_new_interfaces(interfaces, source_path, category.id), - }; - add_interfaces_task(&queue, tasks).await; - } - Err(err) => { - return Err(err); - } - } - } - - Ok(()) -} - -fn filter_new_interfaces( - interfaces: Vec, - source_path: &PathBuf, - category_id: String, -) -> Vec { - let config = Config::new(source_path); - let config_json = config.read_config().unwrap(); - - let mut config_interfaces = vec![]; - - 'project: for project in config_json.project_list { - for category in project.categories { - if category.id == category_id { - config_interfaces = category.interfaces; - break 'project; - } - } - } - - let filtered_interfaces = interfaces - .into_iter() - .filter(|interface| { - if config_interfaces.len() == 0 { - return true; - } - - for config_interface in &config_interfaces { - if config_interface.id == interface.id { - return false; - } - } - - true - }) - .collect::>(); - - filtered_interfaces -} diff --git a/src-tauri/src/services/common.rs b/src-tauri/src/services/common.rs deleted file mode 100644 index da1becf..0000000 --- a/src-tauri/src/services/common.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; -use tauri::{State, Manager}; - -use crate::structs::queue::Queue; - -#[derive(serde::Serialize)] -pub struct CustomResponse { - pub message: String, - pub data: Option, -} - - -#[tauri::command] -pub async fn execute(handle: tauri::AppHandle) -> Result { - tauri::async_runtime::spawn(async move { - let queue: State<'_, Queue> = handle.state(); - queue.start_task().await; - }); - return Ok(CustomResponse { - message: String::from("正在异步执行任务..."), - data: None, - }); -} - -#[tauri::command] -pub async fn pause(handle: tauri::AppHandle) -> Result { - tauri::async_runtime::spawn(async move { - let queue: State<'_, Queue> = handle.state(); - queue.stop_task().await; - }); - return Ok(CustomResponse { - message: String::from("任务已取消!"), - data: None, - }); -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct SearchRequest { - pub key: Option, -} \ No newline at end of file diff --git a/src-tauri/src/services/config.rs b/src-tauri/src/services/config.rs deleted file mode 100644 index b1c0957..0000000 --- a/src-tauri/src/services/config.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, json}; -use tauri::{Manager, State}; - -use crate::{ - structs::{ - config::{CategoryShort, Config, ConfigJson, ProjectShort}, - context::ContextGlobal, - queue::Queue, - }, - utils::join_path, -}; - -use super::common::{CustomResponse, SearchRequest}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct GetConfigRequest { - pub dir_path: String, -} - -// 读取配置(初始化) -#[tauri::command] -pub async fn read_config(data: String, handle: tauri::AppHandle) -> Result { - let cx: State<'_, ContextGlobal> = handle.state(); - let params = from_str::(&data).unwrap(); - let queue: State<'_, Queue> = handle.state(); - let dir_path = PathBuf::from(¶ms.dir_path); - - let config: Config = Config::new(&dir_path); - // 更新queue基础配置 - init_queue(&queue, &config).await; - // 更新context基础配置 - cx.update(dir_path.clone()).await; - - match config.read_config() { - Ok(mut data) => { - if data.base_url.is_some() { - // 更新配置文件 source_path - data.source_path = Some(dir_path.clone()); - config.write_config(&data); - - update_queue(&queue, &data).await; - return Ok(CustomResponse { - message: String::from("读取配置成功!"), - data: None, - }); - } - return Err("数据为空!".to_string()); - } - Err(e) => { - return Err(e); - } - } -} - -// 获取配置 -#[tauri::command] -pub async fn get_config(data: String, handle: tauri::AppHandle) -> Result { - let params = from_str::(&data).unwrap(); - let cx: State<'_, ContextGlobal> = handle.state(); - let path = cx.source_path.lock().await.clone(); - - if let Some(path) = &path { - let config = Config::new(&path); - - match config.read_config() { - Ok(mut data) => { - match params.key { - Some(key) => { - if !key.is_empty() { - data.project_list - .retain_mut(|project| filter_categories(project, &key)); - } - } - None => {} - } - Ok(CustomResponse { - message: String::from("获取配置成功!"), - data: Some(json!(data)), - }) - } - Err(_) => { - return Err(String::from("read config error")); - } - } - } else { - return Err(String::from("path is None")); - } -} - -fn filter_categories(project: &mut ProjectShort, key: &String) -> bool { - project.categories.retain_mut(|category| { - if category.name.contains(key) { - true - } else { - filter_interfaces(category, key) - } - }); - - if project.categories.len() != 0 { - true - } else { - false - } -} - -fn filter_interfaces(category: &mut CategoryShort, key: &String) -> bool { - category.interfaces.retain_mut(|interface| { - if let Some(name) = &interface.name { - if name.contains(key) { - return true; - } - } - - if let Some(path) = &interface.path { - if path.contains(key) { - return true; - } - } - - false - }); - - if category.interfaces.len() != 0 { - true - } else { - false - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UpdateConfigRequest { - pub source_path: Option, - pub base_url: Option, - pub types_path: Option, - pub rate_limit: Option, - pub break_seconds: Option, - pub request_template: Option, - pub header_template: Option, - pub type_import_template: Option, - pub proxy: Option, - pub request_path: Option, - pub file_name_template: Option, -} - -// 更新类型配置 -#[tauri::command] -pub async fn update_config( - data: String, - handle: tauri::AppHandle, -) -> Result { - let form = from_str::(&data).unwrap(); - let cx: State<'_, ContextGlobal> = handle.state(); - let path = cx.source_path.lock().await.clone().unwrap(); - let queue: State<'_, Queue> = handle.state(); - - let config = Config::new(&path); - - let mut config_json = match config.read_config() { - Ok(data) => data, - Err(_) => ConfigJson::new(path.clone()), - }; - - if let Some(base_url) = &form.base_url { - let mut url = base_url.clone(); - if url.ends_with("/") { - url.pop(); - } - config_json.base_url = Some(url); - } - - if let Some(types_path) = &form.types_path { - config_json.types_path = Some(PathBuf::from(types_path)); - config_json.types_full_path = Some(join_path( - &config_json.source_path.clone().unwrap(), - types_path.clone(), - )); - } - - if let Some(rate_limit) = &form.rate_limit { - config_json.rate_limit = Some(rate_limit.clone()); - } - - if let Some(break_seconds) = &form.break_seconds { - config_json.break_seconds = Some(break_seconds.clone()); - } - - if let Some(request_template) = &form.request_template { - config_json.request_template = Some(request_template.clone()); - } - - if let Some(header_template) = &form.header_template { - config_json.header_template = Some(header_template.clone()); - } - - if let Some(type_import_template) = &form.type_import_template { - config_json.type_import_template = Some(type_import_template.clone()); - } - - if let Some(file_name_template) = &form.file_name_template { - config_json.file_name_template = Some(file_name_template.clone()); - } - - if let Some(request_path) = &form.request_path { - config_json.request_path = Some(PathBuf::from(request_path)); - config_json.request_full_path = Some(join_path( - &config_json.source_path.clone().unwrap(), - request_path.clone(), - )); - } - - if let Some(proxy) = &form.proxy { - config_json.proxy = Some(proxy.clone()); - } - - update_queue(&queue, &config_json).await; - - config.write_config(&config_json); - - return Ok(CustomResponse { - message: String::from("更新配置成功!"), - data: None, - }); -} - -async fn init_queue(queue: &State<'_, Queue>, config: &Config) { - let mut queue_config = queue.config.lock().await; - *queue_config = Some(config.clone()); -} - -async fn update_queue(queue: &State<'_, Queue>, config: &ConfigJson) { - *queue.break_seconds.lock().await = match config.break_seconds { - Some(val) => val, - None => 2, - }; - *queue.rate_limit.lock().await = match config.rate_limit { - Some(val) => val, - None => 0, - }; -} diff --git a/src-tauri/src/services/conversion.rs b/src-tauri/src/services/conversion.rs new file mode 100644 index 0000000..14f4b7e --- /dev/null +++ b/src-tauri/src/services/conversion.rs @@ -0,0 +1,13 @@ +use std::path::PathBuf; + +pub fn path_buf_to_string(input: PathBuf) -> String { + input.to_string_lossy().to_string() +} + +pub fn string_to_path_buf(input: String) -> PathBuf { + PathBuf::from(input) +} + +pub fn string_to_u8_slice(input: &String) -> &[u8] { + input.as_bytes() +} \ No newline at end of file diff --git a/src-tauri/src/services/global_config.rs b/src-tauri/src/services/global_config.rs new file mode 100644 index 0000000..45e884f --- /dev/null +++ b/src-tauri/src/services/global_config.rs @@ -0,0 +1,99 @@ +use std::{ + fs::OpenOptions, + io::{self, Read, Write}, +}; + +use serde_json::{from_str, json}; +use tauri::AppHandle; + +use crate::models::global_config::GlobalConfig; + +use super::{log::log, storage::get_local_data_dir}; + +pub const CONFIG_NAME: &str = "config.json"; + +pub fn init_config(app_handle: &AppHandle) -> Result<(), io::Error> { + if get_local_data_dir(app_handle) + .unwrap() + .join(CONFIG_NAME) + .exists() + { + return Ok(()); + } + + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .open(get_local_data_dir(app_handle).unwrap().join(CONFIG_NAME))?; + file.write_all(json!(GlobalConfig::default()).to_string().as_bytes())?; + Ok(()) +} + +pub fn get_global_config(app_handle: &AppHandle) -> Result { + let local_data_dir = get_local_data_dir(app_handle); + if let Some(local_dir) = local_data_dir { + let mut file = OpenOptions::new() + .read(true) + .open(local_dir.join(CONFIG_NAME))?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let config = from_str::(&contents.to_string()).unwrap(); + Ok(config) + } else { + log(app_handle, "local_data_dir is None".to_string()); + Err(io::Error::new( + io::ErrorKind::Other, + "local_data_dir is None", + )) + } +} + +pub fn write_config(app_handle: &AppHandle, contents: &GlobalConfig) -> Result<(), io::Error> { + let local_data_dir = get_local_data_dir(app_handle); + if let Some(local_dir) = local_data_dir { + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(local_dir.join(CONFIG_NAME))?; + file.write_all(json!(contents).to_string().as_bytes())?; + Ok(()) + } else { + log(app_handle, "local_data_dir is None".to_string()); + Err(io::Error::new( + io::ErrorKind::Other, + "local_data_dir is None", + )) + } +} + +pub fn get_latest_project_source_path(app_handle: &AppHandle) -> Result { + let config = get_global_config(app_handle)?; + let latest_project_source_path = config.projects.first(); + if let Some(latest_project_source_path) = latest_project_source_path { + Ok(latest_project_source_path.to_string()) + } else { + Err(io::Error::new(io::ErrorKind::Other, "未找到任何工程项目")) + } +} + +pub fn update_project( + app_handle: &AppHandle, + source_path: &String, + global_config: &mut GlobalConfig, +) -> Result<(), io::Error> { + if global_config.projects.contains(source_path) { + // move to first + let index = global_config + .projects + .iter() + .position(|x| x == source_path) + .unwrap(); + global_config.projects.remove(index); + global_config.projects.insert(0, source_path.clone()); + write_config(app_handle, global_config) + } else { + // insert to first + global_config.projects.insert(0, source_path.clone()); + write_config(app_handle, global_config) + } +} diff --git a/src-tauri/src/services/interface.rs b/src-tauri/src/services/interface.rs deleted file mode 100644 index 94981c7..0000000 --- a/src-tauri/src/services/interface.rs +++ /dev/null @@ -1,59 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, json}; -use tauri::{Manager, State}; - -use crate::structs::{context::{Context, ContextGlobal}, interface::Interface, queue::Queue}; - -use super::common::CustomResponse; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct InterfaceList { - pub id: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UpdateInterfaces { - pub interfaces: Vec, - pub token: String, -} - -// 更新接口 -#[tauri::command] -pub async fn update_interface( - data: String, - handle: tauri::AppHandle, -) -> Result { - let context_global: State<'_, ContextGlobal> = handle.state(); - let queue: State<'_, Queue> = handle.state(); - let handle_clone = handle.clone(); - - let source_path = context_global.source_path.lock().await.clone().unwrap(); - let context = Context::from_source_path(&source_path); - - let data = from_str::(&data).unwrap(); - let token = data.token; - - let interfaces: Vec<_> = data - .interfaces - .iter() - .map(|item| Interface::new(token.clone(), context.clone(), item.id.to_string())) - .collect(); - - add_interfaces_task(&queue, interfaces).await; - - tauri::async_runtime::spawn(async move { - let queue: State<'_, Queue> = handle_clone.state(); - queue.start_task().await; - }); - - return Ok(CustomResponse { - message: String::from("添加任务成功"), - data: Some(json!(queue.total_count.lock().await.clone())), - }); -} - -pub async fn add_interfaces_task(queue: &State<'_, Queue>, interfaces: Vec) { - for interface in interfaces { - queue.add_task(interface).await; - } -} diff --git a/src-tauri/src/services/log.rs b/src-tauri/src/services/log.rs new file mode 100644 index 0000000..9fa989e --- /dev/null +++ b/src-tauri/src/services/log.rs @@ -0,0 +1,45 @@ +use std::{fs, io::Write}; + +use tauri::AppHandle; + +use super::storage::get_app_log_dir; + +const LOG_NAME: &str = "log.txt"; + +pub fn log_error(app_handle: &AppHandle, contents: String) -> Result { + log(app_handle, format!("Error: {}", contents)); + Err(contents) +} + +pub fn log(app_handle: &AppHandle, contents: String) { + let app_log_dir = get_app_log_dir(app_handle); + + if let Some(log_dir) = app_log_dir { + if let Err(e) = fs::create_dir_all(&log_dir) { + println!("create_dir_all error: {:?}", e); + }; + + // add time + let contents = format!( + "{} {}\n", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + contents + ); + + let file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(log_dir.join(LOG_NAME)); + + match file { + Ok(mut file) => { + if let Err(e) = file.write_all(contents.as_bytes()) { + println!("write log error: {:?}", e); + } + } + Err(e) => { + println!("open log error: {:?}", e); + } + } + } +} diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index e625107..f226e93 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,6 +1,8 @@ -pub mod category; -pub mod common; -pub mod config; -pub mod interface; -pub mod project; -pub mod request; \ No newline at end of file +pub mod global_config; +pub mod request; +pub mod storage; +pub mod conversion; +pub mod log; +pub mod yapi; +pub mod reqwest; +pub mod notification; \ No newline at end of file diff --git a/src-tauri/src/services/notification.rs b/src-tauri/src/services/notification.rs new file mode 100644 index 0000000..64da63f --- /dev/null +++ b/src-tauri/src/services/notification.rs @@ -0,0 +1,15 @@ +use tauri::{AppHandle, Manager}; + +use crate::models::notification::{Notification, NotificationDesc}; + +pub fn notification(app_handle: &AppHandle, desc: NotificationDesc, message: &str) { + app_handle + .emit_all( + "notification", + Notification { + desc, + message: message.to_string(), + }, + ) + .unwrap() +} diff --git a/src-tauri/src/services/project.rs b/src-tauri/src/services/project.rs deleted file mode 100644 index dcfd4e2..0000000 --- a/src-tauri/src/services/project.rs +++ /dev/null @@ -1,158 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, json}; -use tauri::{Manager, State}; - -use crate::structs::{ - category::Category, - config::{CategoryShort, Config, ConfigJson, InterfaceShort, ProjectShort}, - context::{Context, ContextGlobal}, - project::Project, - queue::Queue, -}; - -use super::{category::add_categories_task, common::CustomResponse}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ProjectList { - pub id: String, - pub token: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UpdateProjects { - pub projects: Vec, - pub is_full_update: bool, -} - -// 更新项目 -#[tauri::command] -pub async fn update_projects( - data: String, - handle: tauri::AppHandle, -) -> Result { - let handle_clone = handle.clone(); - let context_global: State<'_, ContextGlobal> = handle.state(); - let queue: State<'_, Queue> = handle.state(); - - let source_path = context_global.source_path.lock().await.clone().unwrap(); - let context = Context::from_source_path(&source_path); - let data = from_str::(&data).unwrap(); - - let projects: Vec<_> = generate_modal(&data, &context); - - for project in projects { - let result = project.fetch_categories().await; - - match result { - Ok(categories) => { - merge_to_config(&context_global, project, &categories).await; - - if let Err(e) = - add_categories_task(&queue, categories, &data.is_full_update, &source_path) - .await - { - return Err(e); - } - } - Err(_) => { - return Err(String::from("fetch categories error")); - } - } - } - - tauri::async_runtime::spawn(async move { - let queue: State<'_, Queue> = handle_clone.state(); - queue.start_task().await; - }); - - return Ok(CustomResponse { - message: String::from("开始执行任务序列"), - data: Some(json!(queue.total_count.lock().await.clone())), - }); -} - -fn generate_modal(data: &UpdateProjects, context: &Context) -> Vec { - let projects: Vec<_> = data - .projects - .iter() - .map(|project| { - let context_clone = context.clone(); - let id = project.id.clone(); - - Project::new(id, project.token.clone(), context_clone) - }) - .collect(); - - projects -} - -async fn merge_to_config( - context_global: &ContextGlobal, - project: Project, - categories: &Vec, -) { - let path = context_global.source_path.lock().await.clone().unwrap(); - let config = Config::new(&path); - let new_project = init_short_project(&project.id, &project.token, categories); - - let mut config_json = config.read_config().unwrap(); - - merge_projects(&new_project, &mut config_json); - - config.write_config(&config_json); -} - -fn init_short_project( - project_id: &String, - token: &String, - categories: &Vec, -) -> ProjectShort { - let short_categories: Vec<_> = categories - .into_iter() - .map(|category| CategoryShort { - id: category.id.clone(), - name: category.name.clone(), - interfaces: category - .interfaces - .iter() - .map(|interface| InterfaceShort { - id: interface.id.clone(), - name: None, - path: None - }) - .collect::>(), - }) - .collect(); - - ProjectShort { - project_id: project_id.clone(), - token: token.clone(), - categories: short_categories, - } -} - -fn merge_projects(new_project: &ProjectShort, old_config: &mut ConfigJson) { - let old_project = old_config - .project_list - .iter_mut() - .find(|cp| cp.project_id == new_project.project_id); - - match old_project { - Some(op) => { - merge_categories(new_project, op); - } - None => { - old_config.project_list.push(new_project.clone()); - } - } -} - -fn merge_categories(new_project: &ProjectShort, old_project: &mut ProjectShort) { - for category in &new_project.categories { - if old_project.categories.iter().any(|oc| oc.id == category.id) { - continue; - } - old_project.categories.push(category.clone()); - } -} - diff --git a/src-tauri/src/services/request.rs b/src-tauri/src/services/request.rs index bfff53b..b8b93f8 100644 --- a/src-tauri/src/services/request.rs +++ b/src-tauri/src/services/request.rs @@ -1,113 +1,259 @@ -use std::path::PathBuf; +use std::{ + fs::{self, File}, + io::{self, BufRead, BufReader, Write}, + path::{Path, PathBuf}, +}; -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, json}; -use tauri::{Manager, State}; +use crate::models::file::FileTree; -use crate::structs::{ - context::ContextGlobal, - request::{Request, RequestNode}, -}; +use super::yapi::{config::get_project_config, resolver::common::get_legal_name}; + +pub fn get_file_tree( + source_path: &str, + search_path: Option, +) -> Result>, io::Error> { + let project_config = get_project_config(source_path)?; + let types_full_path = PathBuf::from(source_path).join(project_config.types_path); + let search_path = match search_path { + Some(path) => path, + None => types_full_path, + }; + let read_dir = fs::read_dir(search_path)?; + + let list: Vec<_> = read_dir + .into_iter() + .map(|dir_result| { + let dir = dir_result.unwrap(); + let file_type = dir.file_type().unwrap(); + let file_path = dir.path(); + let file_name = dir.file_name().into_string().unwrap(); + + if file_type.is_file() { + let node = FileTree { + full_path: file_path.clone(), + name: file_name.clone(), + children: Box::new(vec![]), + }; + node + } else { + let node = FileTree { + full_path: file_path.clone(), + name: file_name.clone(), + children: match get_file_tree(source_path, Some(dir.path())) { + Ok(children) => children, + Err(_) => Box::new(vec![]), + }, + }; + node + } + }) + .collect(); + + Ok(Box::new(list)) +} -use super::common::{CustomResponse, SearchRequest}; +pub fn get_request_ts_string(source_path: &str, path: &PathBuf) -> Result { + let project_config = get_project_config(source_path)?; + let sub_path = get_type_relative_path(source_path, project_config.types_path, path); -#[tauri::command] -pub async fn check_request_config(handle: tauri::AppHandle) -> Result { - let context_global: State<'_, ContextGlobal> = handle.state(); - let source_path = context_global.source_path.lock().await.clone().unwrap(); + let mut ts_string = format!("{}\n", project_config.header_template.clone()); - let request = Request::from_source_path(source_path); + let mut import_list: Vec = vec![]; + let mut export_list: Vec = vec![]; - if request.request_full_path.is_none() - || request.header_template.is_none() - || request.request_template.is_none() - || request.type_import_template.is_none() - { - return Err("请初始化数据".to_string()); + for dir in fs::read_dir(path)?.into_iter() { + let dir = dir?; + let file_type = dir.file_type()?; + let file_path = dir.path(); + let file_name_without_ext = get_file_name_without_ext(&file_path); + + if file_type.is_file() { + if check_file(&file_path, &file_name_without_ext) { + op_import_list( + project_config.type_import_template.clone(), + &sub_path, + &file_name_without_ext, + &mut import_list, + ); + op_export_list( + project_config.request_template.clone(), + &file_path, + &sub_path, + &mut export_list, + ); + } + } } - Ok(CustomResponse { - message: "已读取配置".to_string(), - data: None, - }) + if import_list.len() == 0 && export_list.len() == 0 { + return Ok(String::new()); + } + + for str in import_list { + ts_string += str.as_str(); + } + + for str in export_list { + ts_string += str.as_str(); + } + + Ok(ts_string) } -#[tauri::command] -pub async fn get_request_list( - data: String, - handle: tauri::AppHandle, -) -> Result { - let params = from_str::(&data).unwrap(); - let context_global: State<'_, ContextGlobal> = handle.state(); - let source_path = context_global.source_path.lock().await.clone().unwrap(); +pub fn write_request_ts_file( + source_path: &str, + path: &PathBuf, + content: String, +) -> Result<(), io::Error> { + let project_config = get_project_config(source_path)?; + let sub_path = get_type_relative_path(source_path, project_config.types_path, path); + let write_path = get_write_path( + project_config.request_path, + source_path, + project_config.file_name_template, + &sub_path, + ); + let parent = write_path.parent().unwrap(); - let request = Request::from_source_path(source_path); + fs::create_dir_all(parent)?; - let list = request.get_node_list(None); + let mut file = fs::File::create(format!("{}.ts", write_path.to_str().unwrap()))?; - let mut root_node = RequestNode { - full_path: request.context.types_full_path.clone().unwrap(), - name: "root".to_string(), - children: list, - }; + file.write_all(content.as_bytes())?; + + Ok(()) +} - match params.key { - Some(key) => { - if !key.is_empty() { - filter_request_list(&mut root_node, key); +// 获取 type 文件的相对路径 +fn get_type_relative_path(source_path: &str, type_path: String, path: &PathBuf) -> Option { + let types_root_path = PathBuf::from(source_path).join(type_path); + match path == &types_root_path { + true => None, + false => match path.strip_prefix(&types_root_path) { + Ok(path) => Some(path.to_path_buf()), + Err(_) => None, + }, + } +} + +// 获取写入 request 文件的路径 +fn get_write_path( + request_path: String, + source_path: &str, + file_name_template: String, + sub_path: &Option, +) -> PathBuf { + let request_full_path = PathBuf::from(source_path).join(request_path); + match &sub_path { + Some(sub_path) => { + let mut write_path = request_full_path.join(&sub_path); + + if let Some(file_name) = write_path.file_name() { + let real_file_name = file_name_template.replace("$1", file_name.to_str().unwrap()); + write_path.set_file_name(real_file_name); } + + write_path } - None => {} + None => request_full_path.join("index"), } +} + +// 检查用于生成 request 的 type 文件是否有 Request/Response interface +fn check_file(file_path: &PathBuf, file_name_without_ext: &String) -> bool { + let req = format!("{}Request", get_legal_name(file_name_without_ext)); + let resp = format!("{}Response", get_legal_name(file_name_without_ext)); - Ok(CustomResponse { - message: "获取成功".to_string(), - data: Some(json!(Vec::from([root_node]))), - }) + if is_string_in_file(&file_path, &req) && is_string_in_file(&file_path, &resp) { + return true; + } + false } -fn filter_request_list(node: &mut RequestNode, key: String) -> bool { - if node.children.len() == 0 { - if node.name.contains(&key) { - true - } else { - false - } - } else { - node.children - .retain_mut(|sub_node| filter_request_list(sub_node, key.clone())); - - if node.children.len() == 0 { - false - } else { - true +pub fn is_string_in_file(ts_file: &PathBuf, string: &str) -> bool { + let file = File::open(ts_file).unwrap(); + let reader = BufReader::new(file); + + for line in reader.lines() { + if line.unwrap().contains(string) { + return true; } } + false +} + +// 把import ts 定义字符串添加进import_list +fn op_import_list( + type_import_template: String, + sub_path: &Option, + file_name: &String, + import_list: &mut Vec, +) { + let sub_path_unix = get_sub_path_unix(sub_path); + + let import_string = type_import_template + .replace("$1", &format!("{}Request", get_legal_name(&file_name))) + .replace("$2", &format!("{}Response", get_legal_name(&file_name))) + .replace("$3", &format!("{}/{}", sub_path_unix, file_name)) + + "\n"; + + import_list.push(import_string); } -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct UpdateRequest { - full_path: Vec, +// 把export ts 定义字符串添加进export_list +fn op_export_list( + request_template: String, + file_path: &PathBuf, + sub_path: &Option, + export_list: &mut Vec, +) { + let comment = get_comment(file_path.clone()); + + let sub_path_unix = get_sub_path_unix(sub_path); + + let file_name = get_file_name_without_ext(&file_path); + + let export_string = request_template + .replace("$1", &file_name) + .replace("$2", &format!("{}Request", get_legal_name(&file_name))) + .replace("$3", &format!("{}Response", get_legal_name(&file_name))) + .replace("$4", &format!("{}/{}", sub_path_unix.as_str(), &file_name)) + + "\n"; + + let export_string_with_comment = format!("{}\n{}", comment, export_string); + + export_list.push(export_string_with_comment); } -#[tauri::command] -pub async fn update_request( - data: String, - handle: tauri::AppHandle, -) -> Result { - let form = from_str::(&data).unwrap(); - let context_global: State<'_, ContextGlobal> = handle.state(); - let source_path = context_global.source_path.lock().await.clone().unwrap(); +fn get_sub_path_unix(sub_path: &Option) -> String { + match sub_path { + Some(sub_path) => format!( + "/{}", + sub_path + .to_str() + .unwrap() + .replace("\\", "/") + .replace("//", "/") + ), + None => "".to_string(), + } +} + +fn get_comment(ts_file: PathBuf) -> String { + let file = File::open(ts_file).unwrap(); + let reader = BufReader::new(file); - let request = Request::from_source_path(source_path); + let mut iteror = reader.lines().into_iter(); + let first_line = iteror.next().unwrap().unwrap(); - for fp in form.full_path { - let path = PathBuf::from(fp); - request.write_ts(&path); + if first_line.starts_with("//") { + return first_line; } - Ok(CustomResponse { - message: "已成功写入ts文件".to_string(), - data: None, - }) + String::from("") +} + +fn get_file_name_without_ext(file_path: &PathBuf) -> String { + let file_name_osstr = Path::file_stem(&file_path).unwrap(); + file_name_osstr.to_os_string().into_string().unwrap() } diff --git a/src-tauri/src/services/reqwest.rs b/src-tauri/src/services/reqwest.rs new file mode 100644 index 0000000..4e32792 --- /dev/null +++ b/src-tauri/src/services/reqwest.rs @@ -0,0 +1,34 @@ +use std::io; + +use reqwest::Client; +use serde::de::DeserializeOwned; +use tauri::AppHandle; + +use super::global_config::get_global_config; + +pub fn get_reqwest_client(app_handle: &AppHandle) -> Result { + let global_config = get_global_config(app_handle)?; + + let proxy = if let Some(proxy) = global_config.proxy { + if !proxy.is_empty() { + Some(reqwest::Proxy::all(proxy).unwrap()) + } else { + None + } + } else { + None + }; + + if let Some(proxy) = proxy.clone() { + Ok(reqwest::Client::builder().proxy(proxy).build().unwrap()) + } else { + Ok(reqwest::Client::new()) + } +} + +pub async fn get_data(client: Client, url: String) -> Result> +where + T: DeserializeOwned, +{ + Ok(client.get(url).send().await?.json::().await?) +} diff --git a/src-tauri/src/services/storage.rs b/src-tauri/src/services/storage.rs new file mode 100644 index 0000000..7f2cd44 --- /dev/null +++ b/src-tauri/src/services/storage.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; +use tauri::AppHandle; + +pub fn get_app_config_dir(app_handle: &AppHandle) -> Option { + app_handle.path_resolver().app_config_dir() +} + +pub fn get_local_data_dir(app_handle: &AppHandle) -> Option { + app_handle.path_resolver().app_local_data_dir() +} + +pub fn get_app_log_dir(app_handle: &AppHandle) -> Option { + app_handle.path_resolver().app_log_dir() +} diff --git a/src-tauri/src/services/yapi/category.rs b/src-tauri/src/services/yapi/category.rs new file mode 100644 index 0000000..2fd3a8a --- /dev/null +++ b/src-tauri/src/services/yapi/category.rs @@ -0,0 +1,38 @@ +use std::io; + +use tauri::AppHandle; + +use crate::{ + models::yapi::{category::CategoryDataList, web_response::YapiResponse}, + services::reqwest::{get_data, get_reqwest_client}, +}; + +use super::config::get_project_config; + +const INTERFACE_LIST_CAT_API: &str = "api/interface/list_cat"; + +pub async fn fetch_cat_interface_list( + cat_id: u32, + token: String, + source_path: &str, + app_handle: &AppHandle, +) -> Result { + let client = get_reqwest_client(&app_handle)?; + let mut project_config = get_project_config(source_path)?; + if !project_config.base_url.ends_with('/') { + project_config.base_url.push('/'); + } + + let url = format!( + "{}{}?token={}&catid={}&limit=1000", + project_config.base_url, INTERFACE_LIST_CAT_API, token, cat_id + ); + + match get_data::>(client, url.clone()).await { + Ok(res) => Ok(res.data), + Err(err) => Err(io::Error::new( + io::ErrorKind::Other, + format!("获取分类下的接口列表失败: {}, url:{}", err.to_string(), url), + )), + } +} diff --git a/src-tauri/src/services/yapi/config.rs b/src-tauri/src/services/yapi/config.rs new file mode 100644 index 0000000..cfaea50 --- /dev/null +++ b/src-tauri/src/services/yapi/config.rs @@ -0,0 +1,140 @@ +use std::{ + fs::OpenOptions, + io::{self, Read, Write}, +}; + +use serde_json::from_str; + +use crate::{ + models::yapi::{ + category::{CategoryMenuItem, InterfaceDataItem}, + config::{YapiCategory, YapiConfig, YapiInterface, YapiProject}, + project::YapiProjectBaseInfo, + }, + services::conversion::string_to_path_buf, +}; + +pub const PROJECT_CONFIG_NAME: &str = "yapi.json"; + +pub fn init_project_config(source_path: String) -> Result<(), io::Error> { + let yapi_config = YapiConfig::default(); + let yapi_config_path = string_to_path_buf(source_path).join(PROJECT_CONFIG_NAME); + + if yapi_config_path.exists() { + return Ok(()); + } + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(yapi_config_path)?; + file.write_all(serde_json::to_string(&yapi_config)?.as_bytes())?; + Ok(()) +} + +pub fn get_project_config(source_path: &str) -> Result { + let mut file = OpenOptions::new() + .read(true) + .open(string_to_path_buf(source_path.to_string()).join(PROJECT_CONFIG_NAME))?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(from_str(&contents)?) +} + +pub fn write_project_config(source_path: &str, yapi_config: YapiConfig) -> Result<(), io::Error> { + // why use truncate? + // write mean overwrite file, Overwriting doesn't mean you're overwriting the entire content of a file. Say your file has 8 As in it, then you write 3 Xs into to the buffer with the write(true) argument supplied, you have now overwritten the first 3 As, meaning your file now contains XXXAAAAA, see, your overwrote some data (example taken from here). Rust doesn't automatically truncate (remove file contents without removing the file) the file, for that you need to also call truncate(true) + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(string_to_path_buf(source_path.to_string()).join(PROJECT_CONFIG_NAME))?; + file.write(serde_json::to_string(&yapi_config)?.as_bytes())?; + + Ok(()) +} + +pub fn merge_yapi_project_to_project_config( + source_path: &str, + yapi_project_base_info: &YapiProjectBaseInfo, + token: &str, +) -> Result<(), io::Error> { + let mut yapi_config = get_project_config(source_path)?; + if !yapi_config + .project_list + .iter() + .any(|project| project.project_id == format!("{}", yapi_project_base_info._id)) + { + yapi_config.project_list.push(YapiProject { + token: token.to_string(), + project_id: format!("{}", yapi_project_base_info._id), + project_name: Some(yapi_project_base_info.name.clone()), + categories: vec![], + }) + }; + + write_project_config(source_path, yapi_config)?; + Ok(()) +} + +pub fn merge_category_to_project_config( + category_menu_item: &CategoryMenuItem, + source_path: &str, + project_id: &str, +) -> Result<(), io::Error> { + let mut yapi_config = get_project_config(source_path)?; + // get the project match project_id in the yapi_config + let project = yapi_config + .project_list + .iter_mut() + .find(|project| project.project_id == project_id); + + if let Some(project) = project { + if !project + .categories + .iter() + .any(|category| category.id == category_menu_item._id.to_string()) + { + project.categories.push(YapiCategory { + id: category_menu_item._id.to_string(), + name: category_menu_item.name.clone(), + interfaces: vec![], + }) + } + } + + write_project_config(source_path, yapi_config)?; + + Ok(()) +} + +pub fn merge_interface_to_project_config( + interface_data_item: &InterfaceDataItem, + source_path: &str, + cat_id: &str, +) -> Result<(), io::Error> { + let mut yapi_config = get_project_config(source_path)?; + + 'project: for project in &mut yapi_config.project_list { + for category in &mut project.categories { + if category.id == cat_id { + if !category + .interfaces + .iter() + .any(|interface| interface.id == interface_data_item._id.to_string()) + { + category.interfaces.push(YapiInterface { + id: interface_data_item._id.to_string(), + name: interface_data_item.title.clone(), + path: Some(interface_data_item.path.clone()), + lock: Some(false), + }) + } + break 'project; + } + } + } + + write_project_config(source_path, yapi_config)?; + + Ok(()) +} diff --git a/src-tauri/src/services/yapi/interface.rs b/src-tauri/src/services/yapi/interface.rs new file mode 100644 index 0000000..4e3e8a3 --- /dev/null +++ b/src-tauri/src/services/yapi/interface.rs @@ -0,0 +1,112 @@ +use std::{fs, io, path::PathBuf}; + +use tauri::AppHandle; + +use crate::{ + models::yapi::{ + interface::{FormType, InterfaceData, InterfaceFetchParams, WebType}, + web_response::YapiResponse, + }, + services::reqwest::{get_data, get_reqwest_client}, +}; + +use super::{ + config::get_project_config, + resolver::{ + common::{get_json, get_path_arr, get_req_body_type, get_request_json}, + form_resolver, json_resolver, + }, +}; + +const ADD_INTERFACE_TASK_API: &str = "api/interface/get"; + +// 获取接口详情 +pub async fn fetch_interface_detail( + fetch_interface_params: InterfaceFetchParams, + app_handle: &AppHandle, +) -> Result { + let client = get_reqwest_client(&app_handle)?; + let mut project_config = get_project_config(&fetch_interface_params.source_path)?; + if !project_config.base_url.ends_with('/') { + project_config.base_url.push('/'); + } + + let url = format!( + "{}{}?token={}&id={}", + project_config.base_url, + ADD_INTERFACE_TASK_API, + fetch_interface_params.token, + fetch_interface_params.interface_id + ); + + match get_data::>(client, url).await { + Ok(res) => Ok(res.data), + Err(err) => Err(io::Error::new( + io::ErrorKind::Other, + format!("获取接口详情失败: {}", err.to_string()), + )), + } +} + +// 接口转ts字符串 +pub fn get_interface_ts_string(data: &InterfaceData) -> Result { + if let Err(e) = is_legal(data) { + return Err(e); + } + + let resp_ts_string = json_resolver::get_ts_string( + WebType::Response, + data, + &get_json(data.res_body.clone().unwrap_or("".to_string())), + ); + let req_ts_string; + + let request_json = get_json(get_request_json(data)); + + if data.method == "POST" { + // post请求可能是json也可能是form + match get_req_body_type(data) { + FormType::Form => { + req_ts_string = form_resolver::get_ts_string(WebType::Request, data, &request_json) + } + FormType::Json => { + req_ts_string = json_resolver::get_ts_string(WebType::Request, data, &request_json) + } + }; + } else { + req_ts_string = form_resolver::get_ts_string(WebType::Request, data, &request_json) + } + + Ok(format!("{}\n{}", req_ts_string, resp_ts_string)) +} + +fn is_legal(data: &InterfaceData) -> Result<(), String> { + if data.res_body.is_none() { + return Err("接口响应体为空".to_string()); + } + Ok(()) +} + +pub fn write_content_to_interface_path( + path: String, + source_path: &str, + content: String, +) -> Result<(), io::Error> { + let project_config = get_project_config(source_path)?; + let path_arr = get_path_arr(path); + let mut file_path = PathBuf::from(source_path).join(project_config.types_path); + + for p in path_arr { + file_path.push(p); + } + + let dir_path = file_path.parent().unwrap(); + + fs::create_dir_all(dir_path).unwrap(); + + let file_full_name = format!("{}.ts", file_path.to_str().unwrap().to_string()); + + fs::write(file_full_name, content).unwrap(); + + Ok(()) +} diff --git a/src-tauri/src/services/yapi/mod.rs b/src-tauri/src/services/yapi/mod.rs new file mode 100644 index 0000000..94c2626 --- /dev/null +++ b/src-tauri/src/services/yapi/mod.rs @@ -0,0 +1,5 @@ +pub mod config; +pub mod project; +pub mod category; +pub mod interface; +pub mod resolver; \ No newline at end of file diff --git a/src-tauri/src/services/yapi/project.rs b/src-tauri/src/services/yapi/project.rs new file mode 100644 index 0000000..a7863f2 --- /dev/null +++ b/src-tauri/src/services/yapi/project.rs @@ -0,0 +1,65 @@ +use std::io; + +use tauri::AppHandle; + +use crate::{ + models::yapi::{ + category::CategoryMenuItem, project::YapiProjectBaseInfo, web_response::YapiResponse, + }, + services::reqwest::{get_data, get_reqwest_client}, +}; + +use super::config::get_project_config; + +const PROJECT_BASE_INFO_API: &str = "api/project/get"; +const CATEGORY_MENU_API: &str = "api/interface/getCatMenu"; + +pub async fn fetch_project_base_info( + token: String, + source_path: &str, + app_handle: &AppHandle, +) -> Result { + let mut project_config = get_project_config(source_path)?; + if !project_config.base_url.ends_with('/') { + project_config.base_url.push('/'); + } + + let url = format!( + "{}{}?token={}", + project_config.base_url, PROJECT_BASE_INFO_API, token + ); + let client = get_reqwest_client(&app_handle)?; + + // notification(&app_handle, NotificationDesc::Success, "获取项目信息中..."); + + match get_data::>(client, url).await { + Ok(res) => Ok(res.data), + Err(e) => Err(io::Error::new( + io::ErrorKind::Other, + format!("获取项目信息失败: {}", e.to_string()), + )), + } +} + +pub async fn fetch_project_cat_menu( + project_id: u32, + token: String, + source_path: &str, + app_handle: &AppHandle, +) -> Result, io::Error> { + let client = get_reqwest_client(&app_handle)?; + let project_config = get_project_config(source_path)?; + + let url = format!( + "{}{}?project_id={}&token={}", + project_config.base_url, CATEGORY_MENU_API, project_id, token + ); + + match get_data::>>(client, url).await { + Ok(res) => Ok(res.data), + Err(err) => Err(io::Error::new( + io::ErrorKind::Other, + format!("获取分类列表失败: {}", err.to_string()), + )), + } +} diff --git a/src-tauri/src/services/yapi/resolver/common.rs b/src-tauri/src/services/yapi/resolver/common.rs new file mode 100644 index 0000000..5d7382e --- /dev/null +++ b/src-tauri/src/services/yapi/resolver/common.rs @@ -0,0 +1,102 @@ +use serde_json::{json, Value}; + +use crate::models::yapi::interface::{FormType, InterfaceData}; + + +pub fn get_legal_name(raw_name: &str) -> String { + let chars = raw_name + .chars() + .filter(|c| c.is_ascii_alphanumeric()) + .collect::>(); + + String::from_iter(chars) +} + +pub fn get_legal_desc(raw_desc: &str) -> String { + raw_desc.replace("\n", "").to_string() +} + +pub fn get_desc(value: &Value, key: &str) -> String { + let desc = match value.get(key) { + Some(desc) => match desc.as_str() { + Some(desc_str) => get_legal_desc(desc_str), + None => String::from("无注释"), + }, + None => String::from("无注释"), + }; + + desc +} + +pub fn get_json(json_str: String) -> serde_json::Value { + match serde_json::from_str(&json_str) { + Ok(json) => json, + Err(_) => serde_json::Value::Null, + } +} + +// 解析请求体json字符串 +pub fn get_request_json(interface_data: &InterfaceData) -> String { + match get_req_body_type(interface_data) { + FormType::Form => { + let form = &interface_data.req_body_form; + + if let Some(res) = form { + if res.len() != 0 { + return json!(res).to_string(); + } + } + + let req_query = &interface_data.req_query; + + if let Some(res) = req_query { + if res.len() != 0 { + return json!(res).to_string(); + } + } + + let req_params = &interface_data.req_params; + if let Some(res) = req_params { + if res.len() != 0 { + return json!(res).to_string(); + } + } + + String::from("") + } + FormType::Json => { + let json = &interface_data.req_body_other; + + if let Some(res) = json { + res.clone() + } else { + String::from("") + } + } + } +} + +// 解析请求体类型 +pub fn get_req_body_type(interface_data: &InterfaceData) -> FormType { + match &interface_data.req_body_type { + Some(req_body_type) => { + if req_body_type.as_str() == "form" { + FormType::Form + } else { + FormType::Json + } + } + None => FormType::Form, + } +} + +// 将接口路径转换为数组形式 +pub fn get_path_arr(raw_path: String) -> Vec { + let path_arr: Vec<_> = raw_path + .split("/") + .filter(|x| !x.is_empty()) + .map(|x| x.to_string()) + .collect(); + path_arr +} + diff --git a/src-tauri/src/services/yapi/resolver/form_resolver.rs b/src-tauri/src/services/yapi/resolver/form_resolver.rs new file mode 100644 index 0000000..9d7a937 --- /dev/null +++ b/src-tauri/src/services/yapi/resolver/form_resolver.rs @@ -0,0 +1,91 @@ +use serde_json::Value; + +use crate::models::yapi::interface::{InterfaceData, WebType}; + +use super::common::{get_desc, get_legal_name, get_path_arr}; + +pub fn get_ts_string( + web_type: WebType, + interface_data: &InterfaceData, + form_value: &Value, +) -> String { + let header = format!("// {}", interface_data.title); + let file_name = get_path_arr(interface_data.path.clone()) + .last() + .unwrap_or(&"unknowFileName".to_string()) + .clone(); + let interface_ts_name = get_interface_ts_name(web_type, &file_name); + let mut res_string = format!("{}\nexport interface {} {{\n", header, interface_ts_name); + + // let form_value = json!(get_request_json(interface_data)); + + match form_value.is_array() { + true => { + for value in form_value.as_array().unwrap() { + let name = get_name(&value); + let t = get_type(&value); + let desc = get_desc(&value, "desc"); + let required = is_required(&value); + let required_symbol = if required { "" } else { "?" }; + + let type_define: String = + format!(" // {}\n {}{}: {}\n", desc, name, required_symbol, t); + + res_string = res_string + type_define.as_str(); + } + } + false => {} + } + res_string = res_string + "}\n"; + + res_string +} + +fn get_interface_ts_name(web_type: WebType, file_name: &str) -> String { + match web_type { + WebType::Request => format!("{}Request", get_legal_name(file_name)), + WebType::Response => format!("{}Response", get_legal_name(file_name)), + } +} + +fn get_name(value: &Value) -> String { + match value.get("name") { + Some(name) => match name.as_str() { + Some(name_str) => get_legal_name(name_str), + None => String::from("unknowName"), + }, + None => String::from("unknowName"), + } +} + +fn is_required(value: &Value) -> bool { + match value.get("required") { + Some(required) => match required.as_str() { + Some(required_string) => { + if required_string == "1" { + true + } else { + false + } + } + None => false, + }, + None => false, + } +} + +fn get_type(value: &Value) -> String { + match value.get("type") { + Some(t) => match t.as_str() { + Some(t_str) => { + if t_str == "text" { + String::from("string") + } else { + String::from("any") + } + } + None => String::from("string"), + }, + None => String::from("string"), + } +} diff --git a/src-tauri/src/services/yapi/resolver/json_resolver.rs b/src-tauri/src/services/yapi/resolver/json_resolver.rs new file mode 100644 index 0000000..51d5395 --- /dev/null +++ b/src-tauri/src/services/yapi/resolver/json_resolver.rs @@ -0,0 +1,344 @@ +use serde_json::Value; + +use crate::models::yapi::interface::{ + Atom, InterfaceData, JsonType, JsonValue, Node, ObjectLike, ObjectType, Root, WebType, +}; + +use super::common::{get_desc, get_legal_name, get_path_arr}; + +// 生成 ts 字符串 + +pub fn get_ts_string( + web_type: WebType, + interface_data: &InterfaceData, + json_value: &Value, +) -> String { + let root = generate_root(web_type, interface_data, json_value); + get_root_ts(root) +} + +fn get_root_ts(root: Root) -> String { + let ts_name = get_ts_interface_name(&root.interface_name, &root.key); + let header = format!("// {}\n", root.interface_desc); + let mut res_string = String::from(header); + + if let Some(nodes) = root.children { + res_string = res_string + resolve_root_interfaces(&nodes, &ts_name).as_str(); + } else { + res_string = res_string + resolve_root_interfaces(&vec![], &ts_name).as_str(); + } + + res_string +} + +fn resolve_root_interfaces(nodes: &Vec, ts_name: &str) -> String { + let mut sub_list = Vec::new(); + let mut res_string = format!("export interface {} {{\n", ts_name); + + for node in nodes { + let mut t = String::new(); + if let JsonValue::Atom(atom) = node { + t = get_atom_ts(atom); + } else if let JsonValue::ObjectLike(object_like) = node { + t = get_object_like_ts(object_like); + sub_list.push(object_like); + } + + res_string = res_string + t.as_str(); + } + + res_string = res_string + "}\n"; + + // 递归解析子interface + let resolved_sub_list = resolve_root_interfaces_sub_list(sub_list); + + for (json_values, ts_name) in resolved_sub_list { + res_string = res_string + &resolve_root_interfaces(&json_values, &ts_name); + } + + res_string +} + +fn resolve_root_interfaces_sub_list(sub_list: Vec<&ObjectLike>) -> Vec<(Vec, String)> { + sub_list + .iter() + .map(|object_like| { + let ts_name = get_ts_interface_name(&object_like.interface_name, &object_like.key); + let nodes = &object_like.nodes; + let json_values: Vec<_> = nodes.iter().map(|node| node.value.clone()).collect(); + (json_values, ts_name) + }) + .collect() +} + +fn get_atom_ts(atom: &Atom) -> String { + let required_symbol = if atom.required { "" } else { "?" }; + format!( + " // {}\n {}{}: {}\n", + atom.description.replace("\n", ""), + atom.key, + required_symbol, + format_atom_type(&atom.value) + ) +} + +fn format_atom_type(value: &String) -> String { + if value.as_str() == "integer" { + "number".to_string() + } else { + value.to_string() + } +} + +fn get_object_like_ts(object_like: &ObjectLike) -> String { + let required_symbol = if object_like.required { "" } else { "?" }; + let object_name = get_ts_interface_name(&object_like.interface_name, &object_like.key); + let array_symbol = if object_like.object_type == ObjectType::Array { + "[]" + } else { + "" + }; + + format!( + " // {}\n {}{}: {}{}\n", + object_like.description, object_like.key, required_symbol, object_name, array_symbol + ) +} + +// -------------- 生成模型 + +// 生成根节点模型 +fn generate_root(web_type: WebType, interface_data: &InterfaceData, json_value: &Value) -> Root { + let root_key = if web_type == WebType::Request { + String::from("request") + } else { + String::from("response") + }; + let interface_name = get_model_interface_name(interface_data); + let interface_desc = interface_data.title.clone(); + let mut children = None; + + if is_object(json_value) { + children = Some(get_root_children(interface_name.clone(), json_value)); + } + Root { + interface_name, + interface_desc, + key: root_key, + children, + } +} + +fn get_root_children(interface_name: String, json_value: &Value) -> Vec { + let properties = json_value.get("properties").unwrap(); + let properties_iterator = properties.as_object().unwrap().iter(); + let required_list = json_value.get("required"); + + let json_values: Vec<_> = properties_iterator + .map(|(key, value)| { + let node_type = get_json_type(value); + let raw_type = get_ts_type(value); + let required = is_required(key, required_list); + let description = get_desc(value, "description"); + let key = get_name(&key); + + match node_type { + JsonType::Object => { + let nodes = generate_nodes(value, &interface_name); + let object = ObjectLike { + interface_name: interface_name.to_string(), + nodes, + object_type: ObjectType::Object, + required, + description, + key, + }; + + JsonValue::ObjectLike(object) + } + JsonType::Atom => { + let atom = Atom { + value: raw_type, + required, + key, + description, + }; + JsonValue::Atom(atom) + } + JsonType::Array => { + let items = value.get("items").unwrap(); + let nodes = generate_nodes(items, &interface_name); + let array = ObjectLike { + interface_name: interface_name.to_string(), + nodes, + object_type: ObjectType::Array, + required, + description, + key, + }; + + JsonValue::ObjectLike(array) + } + _ => JsonValue::Null, + } + }) + .collect(); + + json_values +} + +fn generate_nodes(json_value: &Value, interface_name: &str) -> Vec { + match json_value.get("properties") { + Some(properties) => { + let properties_iterator = properties.as_object().unwrap().iter(); + let required_list = json_value.get("required"); + + let nodes: Vec<_> = properties_iterator + .map(|(key, value)| { + let node_type = get_json_type(value); + let required = is_required(key, required_list); + let raw_type = get_ts_type(value); + let key = get_name(&key); + let description = get_desc(value, "description"); + + let value = match node_type { + JsonType::Object => { + let nodes = generate_nodes(value, interface_name); + JsonValue::ObjectLike(ObjectLike { + interface_name: interface_name.to_string(), + nodes, + object_type: ObjectType::Object, + required, + description: description.clone(), + key: key.clone(), + }) + } + JsonType::Array => { + let items = value.get("items").unwrap(); + let nodes = generate_nodes(items, interface_name); + JsonValue::ObjectLike(ObjectLike { + interface_name: interface_name.to_string(), + nodes, + object_type: ObjectType::Array, + required, + description: description.clone(), + key: key.clone(), + }) + } + JsonType::Atom => { + let atom = Atom { + value: raw_type, + required, + key: key.clone(), + description: description.clone(), + }; + JsonValue::Atom(atom) + } + _ => JsonValue::Null, + }; + + Node { + interface_name: interface_name.to_string(), + key: key.clone(), + value, + required, + description: description.clone(), + } + }) + .collect(); + + nodes + } + None => { + vec![] + } + } +} + +// -------------- 可组合方法 + +fn is_object(value: &Value) -> bool { + if let JsonType::Object = get_json_type(value) { + return true; + } + + false +} + +fn is_required(key: &String, list: Option<&Value>) -> bool { + if let Some(required_list) = list { + let required_key_list = required_list.as_array().unwrap(); + + if required_key_list + .iter() + .any(|x| x.to_string() == format!("\"{}\"", *key)) + { + return true; + } + false + } else { + false + } +} + +fn get_json_type(value: &Value) -> JsonType { + match value.get("type") { + Some(type_value) => match type_value.as_str() { + Some(type_string) => { + if type_string == "object" { + JsonType::Object + } else if type_string == "array" { + JsonType::Array + } else { + JsonType::Atom + } + } + None => JsonType::Unknown, + }, + None => JsonType::Unknown, + } +} + +fn get_ts_type(value: &Value) -> String { + match value.get("type") { + Some(type_value) => match type_value.as_str() { + Some(type_value_str) => type_value_str.to_string(), + None => String::from("any"), + }, + None => String::from("any"), + } +} + +fn get_name(raw_name: &str) -> String { + if raw_name.is_empty() { + String::from("unknownName") + } else { + raw_name.to_string() + } +} + +fn get_model_interface_name(interface_data: &InterfaceData) -> String { + let file_name = get_path_arr(interface_data.path.clone()) + .last() + .unwrap_or(&"unknowFileName".to_string()) + .clone(); + file_name +} + +// 拼接生成ts接口名字 +fn get_ts_interface_name(interface_name: &str, key: &str) -> String { + format!( + "{}{}", + get_legal_name(interface_name), + capitalize_first_letter(&get_legal_name(key)) + ) +} + +// 大写第一个字符 +fn capitalize_first_letter(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} diff --git a/src-tauri/src/services/yapi/resolver/mod.rs b/src-tauri/src/services/yapi/resolver/mod.rs new file mode 100644 index 0000000..844c31c --- /dev/null +++ b/src-tauri/src/services/yapi/resolver/mod.rs @@ -0,0 +1,3 @@ +pub mod form_resolver; +pub mod common; +pub mod json_resolver; \ No newline at end of file diff --git a/src-tauri/src/structs/category.rs b/src-tauri/src/structs/category.rs deleted file mode 100644 index b05294d..0000000 --- a/src-tauri/src/structs/category.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::path::PathBuf; - -use crate::utils::get_err; - -use super::context::Context; -use super::interface::Interface; -use super::web_response::{CategoryDataList, CommonResponse}; -use serde::{Deserialize, Serialize}; -use serde_json::{from_value, Value}; -type Response = CommonResponse; - -#[derive(Debug, Deserialize, Serialize)] -pub struct Category { - token: String, - context: Context, - pub id: String, - pub name: String, - pub interfaces: Vec, -} - -impl Category { - pub fn new(token: String, context: Context, id: String, name: String) -> Self { - Category { - token, - context, - id, - name, - interfaces: Vec::new(), - } - } - - pub fn from_source_path(token: String, id: String, name: String, source_path: PathBuf) -> Self { - let context = Context::from_source_path(&source_path); - Category { - token, - context, - id, - name, - interfaces: Vec::new(), - } - } - - // 获取 interfaces - pub async fn fetch_interfaces(&self) -> Result, String> { - let interface_list_res = self.get_interface_list().await; - - match interface_list_res { - Ok(interface_list) => { - let interfaces: Vec<_> = interface_list - .list - .iter() - .map(|item| { - Interface::new( - self.token.clone(), - self.context.clone(), - item._id.to_string(), - ) - }) - .collect(); - - return Ok(interfaces); - } - Err(e) => return Err(e), - } - } - - // 获取分类下的所有接口 - async fn get_interface_list(&self) -> Result { - let api_url = format!( - "{}/api/interface/list_cat?token={}&catid={}&limit=1000", - self.context.base_url.clone().unwrap(), - self.token.clone(), - self.id - ); - - let proxy = if let Some(proxy) = self.context.proxy.clone() { - if !proxy.is_empty() { - Some(reqwest::Proxy::all(proxy).unwrap()) - } else { - None - } - } else { - None - }; - let client = if let Some(proxy) = proxy.clone() { - reqwest::Client::builder().proxy(proxy).build().unwrap() - } else { - reqwest::Client::new() - }; - - let res = client - .get(api_url.clone()) - .header("ACCEPT", "application/json") - .send() - .await; - - match res { - Ok(response) => { - let to_json = response.json::().await; - - match to_json { - Ok(json) => { - let (err_code, err_msg) = get_err(&json); - - if err_code != 0 { - panic!("{err_msg}"); - } - - let raw_data = from_value::(json).unwrap(); - - return Ok(raw_data.data); - } - Err(_) => { - return Err(format!( - "category {} transform to json error", - self.id.clone() - )) - } - } - } - Err(e) => { - return Err(format!( - "fetch category {} error , detail: {}", - self.id.clone(), - e.to_string() - )); - } - } - } -} - -// #[cfg(test)] -// mod test { -// // use crate::structs::project::TEMP_TOKEN; - -// // use super::super::context::get_test_context; -// // use super::*; - -// #[tokio::test] -// #[ignore] -// async fn test() -> Result<(), Box> { -// // let category_id = String::from("4796"); -// // let context = get_test_context(); -// // let name = String::from("a"); - -// // Category::new(TEMP_TOKEN.into(), context, category_id, name) -// // .fetch_interfaces() -// // .await -// // .fetch_interface_detail() -// // .await -// // .write_ts_file(); - -// Ok(()) -// } -// } diff --git a/src-tauri/src/structs/config.rs b/src-tauri/src/structs/config.rs deleted file mode 100644 index 4252da6..0000000 --- a/src-tauri/src/structs/config.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{fs::File, io::Write, path::PathBuf}; - -use serde::{Deserialize, Serialize}; - -const CONFIG_PATH: &str = "yapi.json"; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ProjectShort { - pub token: String, - pub project_id: String, - pub categories: Vec, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct CategoryShort { - pub id: String, - pub name: String, - pub interfaces: Vec, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct InterfaceShort { - pub id: String, - pub name: Option, - pub path : Option -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ConfigJson { - pub source_path: Option, - pub base_url: Option, - pub rate_limit: Option, - pub types_path: Option, - pub types_full_path: Option, - pub break_seconds: Option, - pub project_list: Vec, - pub request_path: Option, - pub request_full_path: Option, - pub request_template: Option, - pub header_template: Option, - pub file_name_template : Option, - pub type_import_template : Option, - pub proxy :Option -} - -impl ConfigJson { - pub fn new(source_path : PathBuf) -> Self{ - Self{ - source_path: Some(source_path), - base_url: None, - rate_limit: None, - types_path: None, - types_full_path: None, - break_seconds: None, - project_list: vec![], - request_path: None, - request_full_path:None, - request_template: None, - header_template: None, - file_name_template: None, - type_import_template: None, - proxy:None - } - } -} - -#[derive(Debug, Clone)] -pub struct Config { - pub config_path: PathBuf, -} - -impl Config { - pub fn new(source_path: &PathBuf) -> Self { - let config_path = source_path.clone().join(CONFIG_PATH); - - Self { config_path } - } - - pub fn read_config(&self) -> Result { - let config_path = self.config_path.to_str().unwrap(); - let f = File::open(config_path); - - match f { - Ok(file) => { - let read_result = serde_json::from_reader(file); - match read_result { - Ok(config_json) => { - let config_json: ConfigJson = config_json; - Ok(config_json) - } - Err(e) => return Err(e.to_string()), - } - } - Err(_) => Err("未找到配置文件".to_owned()), - } - } - - pub fn write_config(&self, config_json: &ConfigJson) { - let config_path = self.config_path.to_str().unwrap(); - let mut file = File::create(config_path).unwrap(); - - let json = serde_json::to_string(&config_json).unwrap(); - file.write_all(json.as_bytes()).unwrap(); - } -} diff --git a/src-tauri/src/structs/context.rs b/src-tauri/src/structs/context.rs deleted file mode 100644 index 1651cdc..0000000 --- a/src-tauri/src/structs/context.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::{path::PathBuf, sync::Arc}; - -use serde::{Deserialize, Serialize}; -use tokio::sync::Mutex; - -use super::config::{ConfigJson, Config}; - - -#[derive(Debug, Clone)] -pub struct ContextGlobal { - pub source_path: Arc>>, -} - -impl ContextGlobal { - pub async fn update(&self, source_path : PathBuf) { - *self.source_path.lock().await = Some(source_path); - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Context { - pub source_path: Option, - pub base_url: Option, - pub rate_limit: Option, - pub break_seconds: Option, - pub types_path: Option, - pub types_full_path: Option, - pub proxy: Option -} - -impl Context { - - pub fn from_source_path(source: &PathBuf) -> Self { - let config = Config::new(&source.clone()); - let config_json = config.read_config().unwrap(); - Self::from_json(config_json) - } - - pub fn from_json(json : ConfigJson) -> Self { - Self { - source_path: json.source_path.clone(), - base_url: json.base_url.clone(), - rate_limit: json.rate_limit.clone(), - break_seconds: json.break_seconds.clone(), - types_path: json.types_path.clone(), - types_full_path: json.types_full_path.clone(), - proxy: json.proxy.clone(), - } - } - -} diff --git a/src-tauri/src/structs/interface.rs b/src-tauri/src/structs/interface.rs deleted file mode 100644 index 8f67e4b..0000000 --- a/src-tauri/src/structs/interface.rs +++ /dev/null @@ -1,316 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -use crate::utils::get_err; - -use super::context::Context; -use super::resolver::enums::{FormType, WebType}; -use super::resolver::form_resolver::FormResolver; -use super::resolver::json_resolver::JsonResolver; -use super::web_response::{CommonResponse, InterfaceData}; -use serde::{Deserialize, Serialize}; -use serde_json::{from_value, json, Value}; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Interface { - token: String, - context: Context, - pub id: String, - pub resolved_interface: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct ResolvedInterface { - pub request_json: String, - pub request_form_type: Option, - pub response_json: Option, - pub id: String, - pub category_id: String, - pub path: String, - pub title: String, - pub method: String, -} - -impl Interface { - pub fn new(token: String, context: Context, interface_id: String) -> Self { - Interface { - token, - context, - id: interface_id, - resolved_interface: None, - } - } - - pub fn source_path(token: String, interface_id: String, source_path: PathBuf) -> Self { - let context = Context::from_source_path(&source_path); - Interface { - token, - context, - id: interface_id, - resolved_interface: None, - } - } - - pub async fn fetch_detail(&self) -> Result { - let api_url = format!( - "{}/api/interface/get?token={}&id={}", - self.context.base_url.clone().unwrap(), - self.token.clone(), - self.id - ); - - let proxy = if let Some(proxy) = self.context.proxy.clone() { - if !proxy.is_empty() { - Some(reqwest::Proxy::all(proxy).unwrap()) - } else { - None - } - } else { - None - }; - let client = if let Some(proxy) = proxy.clone() { - reqwest::Client::builder().proxy(proxy).build().unwrap() - } else { - reqwest::Client::new() - }; - - let result = client - .get(api_url.clone()) - .header("ACCEPT", "application/json") - .send() - .await; - - match result { - Ok(response) => { - match response.json::().await { - Ok(value) => { - // println!("{:#?}", value); - let (err_code, err_msg) = get_err(&value); - - if err_code != 0 { - panic!("{err_msg}"); - } - - let raw_data = from_value::>(value).unwrap(); - - let ri = Self::format(self, raw_data); - - Ok(ri) - } - Err(_) => Err(format!( - "interface {} tranfer to json error", - self.id.clone() - )), - } - } - Err(_) => Err(format!("interface {} fetch error", self.id.clone())), - } - } - - fn format(&self, raw_data: CommonResponse) -> ResolvedInterface { - let request_json = Self::get_request_json(&raw_data); - let request_form_type = Self::get_req_body_type(&raw_data); - let response_json = raw_data.data.res_body; - let id = raw_data.data._id.to_string(); - let category_id = raw_data.data.catid.to_string(); - let path = raw_data.data.path; - let title = raw_data.data.title; - let method = raw_data.data.method; - - ResolvedInterface { - request_json, - response_json, - id, - category_id, - path, - title, - request_form_type, - method, - } - } - - pub fn write_ts(&self, ri: &ResolvedInterface) -> Result<(), String> { - // 生成全部文件夹 - let path = ri.path.clone(); - let path_arr = Self::get_path_arr(path); - let mut file_path = self.context.types_full_path.clone().unwrap(); - - for p in path_arr { - file_path.push(p); - } - - let interface_name = file_path.file_name().unwrap().to_str().unwrap().to_string(); - let dir_path = file_path.parent().unwrap(); - - fs::create_dir_all(dir_path).unwrap(); - - // 生成并写入ts - let req = &ri.request_json; - let resp = &ri.response_json; - let interface_desc = &ri.title; - let req_ts_string: String; - - match Self::is_legal(ri) { - Ok(_) => { - // request - if ri.method == "POST" { - let request_form_type = ri.request_form_type.clone(); - if let FormType::Form = request_form_type.unwrap() { - req_ts_string = FormResolver::new( - WebType::Request, - interface_name.clone(), - interface_desc.clone(), - req.to_string(), - ) - .to_ts(); - } else { - req_ts_string = JsonResolver::new( - WebType::Request, - interface_name.clone(), - interface_desc.clone(), - req.to_string(), - ) - .generate_modal() - .to_ts(); - } - } else { - req_ts_string = FormResolver::new( - WebType::Request, - interface_name.clone(), - interface_desc.clone(), - req.to_string(), - ) - .to_ts(); - } - - // response - let resp_ts_string = JsonResolver::new( - WebType::Response, - interface_name.clone(), - interface_desc.clone(), - resp.clone().unwrap(), - ) - .generate_modal() - .to_ts(); - - let ts_string = format!("{}\n{}", req_ts_string, resp_ts_string); - let file_name = format!("{}.ts", file_path.to_str().unwrap().to_string()); - - fs::write(file_name, ts_string).unwrap(); - - Ok(()) - } - Err(e) => { - return Err(format!("接口 {}: {}", ri.title, e)); - } - } - } - - fn is_legal(ri: &ResolvedInterface) -> Result<(), String> { - // if ri.request_form_type.is_none() { - // return Err("request form type is none".to_string()); - // } - if ri.response_json.is_none() { - return Err("response json is none".to_string()); - } - Ok(()) - } - - fn get_path_arr(raw_path: String) -> Vec { - let path_arr: Vec<_> = raw_path - .split("/") - .filter(|x| !x.is_empty()) - .map(|x| x.to_string()) - .collect(); - path_arr - } - - fn get_request_json(raw_data: &CommonResponse) -> String { - match Self::get_req_body_type(raw_data) { - Some(req_body_type) => { - if let FormType::Form = req_body_type { - let form = &raw_data.data.req_body_form; - - if let Some(res) = form { - if res.len() != 0 { - return json!(res).to_string(); - } - } - - let req_query = &raw_data.data.req_query; - - if let Some(res) = req_query { - if res.len() != 0 { - return json!(res).to_string(); - } - } - - let req_params = &raw_data.data.req_params; - if let Some(res) = req_params { - if res.len() != 0 { - return json!(res).to_string(); - } - } - - String::from("") - } else { - let json = &raw_data.data.req_body_other; - - if let Some(res) = json { - return res.clone(); - } else { - return String::from(""); - } - } - } - None => "".to_string(), - } - } - - fn get_req_body_type(raw_data: &CommonResponse) -> Option { - match &raw_data.data.req_body_type { - Some(req_body_type) => { - if req_body_type.as_str() == "form" { - return Some(FormType::Form); - } else { - Some(FormType::Json) - } - } - None => Some(FormType::Form), - } - } -} - -// #[cfg(test)] -// mod tests { -// use crate::structs::project::TEMP_TOKEN; - -// use super::super::context::get_test_context; -// use super::*; - -// #[tokio::test] -// async fn test_json() -> Result<(), Box> { -// let context = get_test_context(); -// let interface_id = String::from("15610"); - -// // Interface::new(TEMP_TOKEN.into(), context, interface_id) -// // .fetch_detail() -// // .await -// // .write_ts(); - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_form() -> Result<(), Box> { -// let context = get_test_context(); -// let interface_id = String::from("25384"); - -// // Interface::new(TEMP_TOKEN.into(), context, interface_id) -// // .fetch_detail() -// // .await -// // .write_ts(); - -// Ok(()) -// } -// } diff --git a/src-tauri/src/structs/project.rs b/src-tauri/src/structs/project.rs deleted file mode 100644 index 95b55c9..0000000 --- a/src-tauri/src/structs/project.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::path::PathBuf; - -use super::{ - category::Category, - context::Context, - web_response::{CategoryMenuItem, CommonResponse}, -}; -type Response = CommonResponse>; - -#[derive(Debug)] -pub struct Project { - pub id: String, - pub token: String, - context: Context, -} - -impl Project { - // 初始化project - pub fn new(id: String, token: String, context: Context) -> Self { - Self { id, token, context } - } - - pub fn from_source_path(id: String, token: String, source_path: PathBuf) -> Self { - let context = Context::from_source_path(&source_path); - Self { id, token, context } - } - - // 获取分类数据 - pub async fn fetch_categories(&self) -> Result, String> { - match self.fetch_cat_menu().await { - Ok(cat_menu) => { - let cat_menu_data = cat_menu.data; - - let categories: Vec = cat_menu_data - .iter() - .map(|cat| { - let context = self.context.clone(); - let id = cat._id.to_string(); - let name = cat.name.clone(); - Category::new(self.token.clone(), context, id, name) - }) - .collect(); - - Ok(categories) - } - Err(_) => Err("fetch cat menu error".to_owned()), - } - } - - // 获取分类目录 - async fn fetch_cat_menu(&self) -> Result> { - let get_cat_menu_url = format!( - "{}/api/interface/getCatMenu?project_id={}&token={}", - self.context.base_url.clone().unwrap(), - self.id, - self.token.clone() - ); - - let proxy = if let Some(proxy) = self.context.proxy.clone() { - if !proxy.is_empty() { - Some(reqwest::Proxy::all(proxy).unwrap()) - } else { - None - } - } else { - None - }; - let client = if let Some(proxy) = proxy.clone() { - reqwest::Client::builder().proxy(proxy).build().unwrap() - } else { - reqwest::Client::new() - }; - - let res = client - .get(get_cat_menu_url) - .send() - .await? - .json::() - .await?; - - Ok(res) - } -} - -// #[cfg(test)] -// mod test { -// use super::super::context::get_test_context; -// use super::*; - -// #[tokio::test] -// #[ignore] -// async fn test() -> Result<(), Box> { -// // let context = get_test_context(); -// // let id = "765"; - -// // let mut project = Project::new(id.to_string(), TEMP_TOKEN.into(), context); -// // project.fetch_categories().await.update_all().await; - -// Ok(()) -// } -// } diff --git a/src-tauri/src/structs/queue.rs b/src-tauri/src/structs/queue.rs deleted file mode 100644 index 5a6a1a2..0000000 --- a/src-tauri/src/structs/queue.rs +++ /dev/null @@ -1,273 +0,0 @@ -use std::{collections::VecDeque, sync::Arc, time::Duration}; - -use serde::{Deserialize, Serialize}; -use tauri::{async_runtime::Mutex, Window}; -use tokio::time::sleep; - -use crate::structs::config::InterfaceShort; - -use super::{ - config::Config, - interface::{Interface, ResolvedInterface}, -}; - -#[derive(Debug, Clone)] -pub struct Queue { - pub waiting_queue: Arc>>, - pub total_count: Arc>, - pub rate_limit: Arc>, - pub break_seconds: Arc>, - pub running: Arc>, - pub result_queue: Arc>, - pub config: Arc>>, - pub window: Arc>, -} - -#[derive(Debug, Clone)] -pub struct ResultQueue { - pub list: Vec, - pub processing_count: usize, - pub over_count: usize, - pub over_count_group: usize, // 用来做任务分组 -} - -#[derive(Serialize, Deserialize, Clone)] -struct EmitMessage { - pub msg: String, - pub success_number: usize, - pub is_success: bool, // 0:success,1:fail -} - -impl Queue { - pub fn new(rate_limit: usize, break_seconds: u64, window: Window) -> Queue { - Queue { - waiting_queue: Arc::new(Mutex::new(VecDeque::new())), - rate_limit: Arc::new(Mutex::new(rate_limit)), - break_seconds: Arc::new(Mutex::new(break_seconds)), - running: Arc::new(Mutex::new(false)), - total_count: Arc::new(Mutex::new(0)), - result_queue: Arc::new(Mutex::new(ResultQueue { - list: vec![], - over_count: 0, - processing_count: 0, - over_count_group: 0, - })), - config: Arc::new(Mutex::new(None)), - window: Arc::new(Mutex::new(window)), - } - } - - pub async fn add_task(&self, interface: Interface) { - let mut waiting_queue = self.waiting_queue.lock().await; - - waiting_queue.push_back(interface); - - let mut total_count = self.total_count.lock().await; - - *total_count = *total_count + 1; - } - - pub async fn start_task(&self) { - let running_arc = Arc::clone(&self.running); - let mut running = running_arc.lock().await; - *running = true; - - drop(running); - - self.start_execute().await; - } - - pub async fn stop_task(&self) { - let mut running = self.running.lock().await; - println!("stop!"); - *running = false; - self.clear().await; - } - - async fn start_execute(&self) { - loop { - let result_queue = &self.result_queue; - let total_count = self.total_count.lock().await.clone(); - let rate_limit = self.rate_limit.lock().await.clone(); - let break_seconds = self.break_seconds.lock().await.clone(); - let mut running = self.running.lock().await; - - if *running == false { - return; - } - - let over_count = result_queue.lock().await.over_count; - if total_count == over_count { - *running = false; - println!("over!"); - self.post_task().await; - self.clear().await; - let window = self.window.lock().await; - window.emit("task_completed", {}).unwrap(); - return; - } - - // 如果分组处理完毕,则开始新一批任务 - if result_queue.lock().await.processing_count != 0 - && result_queue.lock().await.over_count_group >= rate_limit - && result_queue.lock().await.processing_count >= rate_limit - { - result_queue.lock().await.processing_count -= rate_limit; - // 如果任务没有全部完成,等待一段时间再进行下一批任务 - if result_queue.lock().await.over_count != total_count { - sleep(Duration::from_secs(break_seconds.clone())).await; - } - } - - // 分组尚未处理完毕,循环等待 - if result_queue.lock().await.processing_count >= rate_limit { - continue; - } - - let mut waiting_queue = self.waiting_queue.lock().await; - let task = waiting_queue.pop_front(); - - match task { - None => { - continue; - } - Some(t) => { - let result_queue_clone = Arc::clone(result_queue); - result_queue_clone.lock().await.processing_count += 1; - let window = Arc::clone(&self.window); - - // 请求接口 - tokio::spawn(async move { - let result = t.fetch_detail().await; - let interface_id = t.id.clone(); - - let window_lock = window.lock().await; - - match result { - Ok(ri) => { - match t.write_ts(&ri) { - Ok(_) => { - let title = ri.title.clone(); - result_queue_clone.lock().await.list.push(ri); - result_queue_clone.lock().await.over_count += 1; - result_queue_clone.lock().await.over_count_group += 1; - window_lock - .emit( - "log", - EmitMessage { - msg: format!("interface {} completed", title), - success_number: result_queue_clone - .lock() - .await - .over_count, - is_success: true, - }, - ) - .unwrap(); - } - Err(e) => { - result_queue_clone.lock().await.list.push(ri); - result_queue_clone.lock().await.over_count += 1; - result_queue_clone.lock().await.over_count_group += 1; - - window_lock - .emit( - "log", - EmitMessage { - msg: e, - success_number: result_queue_clone - .lock() - .await - .over_count, - is_success: false, - }, - ) - .unwrap(); - } - }; - } - Err(_) => { - result_queue_clone.lock().await.over_count += 1; - result_queue_clone.lock().await.over_count_group += 1; - window_lock - .emit( - "log", - EmitMessage { - msg: format!("fetch interface {} error!", interface_id), - success_number: result_queue_clone - .lock() - .await - .over_count, - is_success: false, - }, - ) - .unwrap(); - } - } - }); - } - } - } - } - - pub async fn post_task(&self) { - let config_mutex = self.config.lock().await; - let config = config_mutex.as_ref(); - let result_queue_mutex = self.result_queue.lock().await; - let result_queue = &result_queue_mutex.list; - - match config { - Some(config) => { - let config_json_result = config.read_config(); - - match config_json_result { - Ok(mut config_json) => { - for ri in result_queue { - let insert_node_id = &ri.category_id; - - 'project: for project in &mut config_json.project_list { - for category in &mut project.categories { - if category.id.as_str() == insert_node_id.as_str() { - for interface in &mut category.interfaces { - if interface.id == ri.id { - interface.name = Some(ri.title.clone()); - interface.path = Some(ri.path.clone()); - break 'project; - } - } - - category.interfaces.push(InterfaceShort { - id: ri.id.clone(), - name: Some(ri.title.clone()), - path: Some(ri.path.clone()), - }); - - break 'project; - } - } - } - } - - config.write_config(&config_json); - } - Err(e) => { - println!("{}", e); - } - } - } - None => { - println!("no config"); - } - } - } - - async fn clear(&self) { - *self.result_queue.lock().await = ResultQueue { - list: vec![], - over_count: 0, - processing_count: 0, - over_count_group: 0, - }; - *self.total_count.lock().await = 0; - } -} diff --git a/src-tauri/src/structs/request.rs b/src-tauri/src/structs/request.rs deleted file mode 100644 index 71eaea7..0000000 --- a/src-tauri/src/structs/request.rs +++ /dev/null @@ -1,283 +0,0 @@ -use std::{ - fs::{self, File}, - io::{BufRead, BufReader, Write}, - path::{Path, PathBuf}, -}; - -use serde::{Deserialize, Serialize}; - -use crate::utils::is_string_in_file; - -use super::{config::Config, context::Context}; - -#[derive(Serialize, Deserialize, Debug)] -pub struct RequestNode { - pub full_path: PathBuf, - pub name: String, - pub children: Box>, -} - -pub struct Request { - pub context: Context, - /** - * example: - * export const getProjectList = (params: GetProjectListRequest) => - * request('/api/project/getProjectList', { method: 'post', data: params }); - * - * request_template: - * - * export const $1 = (params: $2) => - * request<$3>('/api/$4' , { method: 'post' , data: params}); - * - * replace $1,$2,$3,$4 and get complete string; - */ - pub request_template: Option, - pub header_template: Option, - pub request_full_path: Option, - // My_$1 : dirname => My_dirname.ts - pub file_name_template: Option, - /** - * example: - * import { addRequest , addResponse } from "@/types/agentAppAccount/add" - * - * type_import_template: - * import { $1 , $2 } from "$3" - */ - pub type_import_template: Option, -} - -impl Request { - pub fn from_source_path(source_path: PathBuf) -> Self { - let context = Context::from_source_path(&source_path); - let config = Config::new(&source_path); - let config_json = config.read_config().unwrap(); - - Self { - context, - request_template: config_json.request_template, - header_template: config_json.header_template, - request_full_path: config_json.request_full_path, - file_name_template: config_json.file_name_template, - type_import_template: config_json.type_import_template, - } - } - - pub fn get_node_list(&self, search_path: Option) -> Box> { - let search_path = match search_path { - Some(path) => path, - None => self.context.types_full_path.clone().unwrap(), - }; - let read_dir = fs::read_dir(search_path).unwrap(); - - let list: Vec<_> = read_dir - .into_iter() - .map(|dir_result| { - let dir = dir_result.unwrap(); - let file_type = dir.file_type().unwrap(); - let file_path = dir.path(); - let file_name = dir.file_name().into_string().unwrap(); - - if file_type.is_file() { - let node = RequestNode { - full_path: file_path.clone(), - name: file_name.clone(), - children: Box::new(vec![]), - }; - node - } else { - let node = RequestNode { - full_path: file_path.clone(), - name: file_name.clone(), - children: self.get_node_list(Some(dir.path())), - }; - node - } - }) - .collect(); - - Box::new(list) - } - - pub fn write_ts(&self, path: &PathBuf) { - let sub_path = self.get_type_relative_path(path); - let write_path = self.get_write_path(&sub_path); - - let mut ts_string = match &self.header_template { - Some(template) => format!("{}\n", template.clone()), - None => String::new(), - }; - - let mut import_list: Vec = vec![]; - let mut export_list: Vec = vec![]; - - for dirs in fs::read_dir(path).unwrap().into_iter() { - let dir = dirs.unwrap(); - let file_type = dir.file_type().unwrap(); - let file_path = dir.path(); - let file_name_with_ext = dir.file_name().into_string().unwrap(); - let file_name_osstr = Path::file_stem(&file_path).unwrap(); - let file_name_without_ext = file_name_osstr.to_os_string().into_string().unwrap(); - - if file_type.is_file() { - if self.check_file(&file_path, &file_name_without_ext) { - self.op_import_list(&sub_path, &file_name_without_ext, &mut import_list); - self.op_export_list( - &file_path, - &sub_path, - &file_name_without_ext, - &mut export_list, - ); - } - } else if file_type.is_dir() { - let new_dir = path.clone().join(file_name_with_ext); - - self.write_ts(&new_dir); - } - } - - if import_list.len() == 0 && export_list.len() == 0 { - return; - } - - for str in import_list { - ts_string += str.as_str(); - } - - for str in export_list { - ts_string += str.as_str(); - } - - let parent = write_path.parent().unwrap(); - - fs::create_dir_all(parent).unwrap(); - - let mut file = fs::File::create(format!("{}.ts", write_path.to_str().unwrap())).unwrap(); - - file.write_all(ts_string.as_bytes()).unwrap() - } - - // 获取 type 文件的相对路径 - fn get_type_relative_path(&self, path: &PathBuf) -> Option { - let types_root_path = self.context.types_full_path.clone().unwrap(); - match path == &types_root_path { - true => None, - false => Some(path.strip_prefix(&types_root_path).unwrap().to_path_buf()), - } - } - - // 获取写入 service 文件的路径 - fn get_write_path(&self, sub_path: &Option) -> PathBuf { - match &sub_path { - Some(sub_path) => { - let mut write_path = self.request_full_path.clone().unwrap().join(&sub_path); - - if let Some(template) = self.file_name_template.clone() { - if let Some(file_name) = write_path.file_name() { - let real_file_name = template.replace("$1", file_name.to_str().unwrap()); - write_path.set_file_name(real_file_name); - } - } - - write_path - } - None => self.request_full_path.clone().unwrap().join("index"), - } - } - - // 检查生成service的 type 文件是否有 Request/Response interface - fn check_file(&self, file_path: &PathBuf, file_name_without_ext: &String) -> bool { - let req = format!("{}Request", file_name_without_ext); - let resp = format!("{}Response", file_name_without_ext); - - if is_string_in_file(&file_path, &req) && is_string_in_file(&file_path, &resp) { - return true; - } - false - } - - // 把import ts 定义字符串添加进import_list - fn op_import_list( - &self, - sub_path: &Option, - file_name_string: &String, - import_list: &mut Vec, - ) { - let sub_path_unix = Self::get_sub_path_unix(sub_path); - - let import_string = self - .type_import_template - .clone() - .unwrap() - .replace("$1", &format!("{}Request", &file_name_string)) - .replace("$2", &format!("{}Response", &file_name_string)) - .replace("$3", &format!("{}/{}", sub_path_unix, file_name_string)) - + "\n"; - - import_list.push(import_string); - } - - // 把export ts 定义字符串添加进export_list - fn op_export_list( - &self, - file_path: &PathBuf, - sub_path: &Option, - file_name_string: &String, - export_list: &mut Vec, - ) { - let comment = Self::get_comment(file_path.clone()); - - let sub_path_unix = Self::get_sub_path_unix(sub_path); - - let export_string = self - .request_template - .clone() - .unwrap() - .replace("$1", &file_name_string) - .replace("$2", &format!("{}Request", &file_name_string)) - .replace("$3", &format!("{}Response", &file_name_string)) - .replace( - "$4", - &format!("{}/{}", sub_path_unix.as_str(), &file_name_string), - ) - + "\n"; - - let export_string_with_comment = format!("{}\n{}", comment, export_string); - - export_list.push(export_string_with_comment); - } - - fn get_sub_path_unix(sub_path: &Option) -> String { - match sub_path { - Some(sub_path) => format!( - "/{}", - sub_path - .to_str() - .unwrap() - .replace("\\", "/") - .replace("//", "/") - ), - None => "".to_string(), - } - } - - fn get_comment(ts_file: PathBuf) -> String { - let file = File::open(ts_file).unwrap(); - let reader = BufReader::new(file); - - let mut iteror = reader.lines().into_iter(); - let first_line = iteror.next().unwrap().unwrap(); - - if first_line.starts_with("//") { - return first_line; - } - - String::from("") - } -} - -// #[test] -// fn test() { -// let request = Request::from_source_path(PathBuf::from("D:\\work\\tmp")); -// let path = PathBuf::from("D:\\work\\tmp\\src\\types\\policy"); -// request.write_ts(&path); -// } diff --git a/src-tauri/src/structs/resolver/entities.rs b/src-tauri/src/structs/resolver/entities.rs deleted file mode 100644 index f0e12de..0000000 --- a/src-tauri/src/structs/resolver/entities.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::utils::capitalize_first_letter; -use ts_bridge_derive::TsBridge; - -use super::enums::{JsonValue, ObjectType}; - -pub trait TsBridge { - fn get_ts_name(&self) -> String; -} - -pub trait ToTs { - fn to_ts(&self) -> String; -} - -#[derive(Clone, Debug)] -pub struct Atom { - pub value: String, - pub required: bool, - pub key: String, - pub description: String, -} - -impl Atom { - pub fn new(value: String, required: bool, key: String, description: String) -> Self { - Self { - value, - required, - key, - description, - } - } - - fn format_type(value: &String) -> String { - if value.as_str() == "integer" { - "number".to_string() - } else { - value.to_string() - } - } -} - -impl ToTs for Atom { - fn to_ts(&self) -> String { - - let required_symbol = if self.required { "" } else { "?" }; - format!( - " // {}\n {}{}: {}\n", - self.description.replace("\n", ""), - self.key, - required_symbol, - Self::format_type(&self.value) - ) - } -} - -#[derive(Clone, Debug)] -pub struct Node { - pub interface_name: String, - pub key: String, - pub value: JsonValue, - pub required: bool, - pub description: String, -} - -impl Node { - pub fn new( - interface_name: String, - key: String, - value: JsonValue, - required: bool, - description: String, - ) -> Self { - Self { - interface_name, - key, - value, - required, - description, - } - } -} - -#[derive(TsBridge, Clone, Debug)] -pub struct ObjectLike { - pub interface_name: String, - pub nodes: Vec, - pub object_type: ObjectType, - pub required: bool, - pub key: String, - pub description: String, -} - -impl ObjectLike { - pub fn new( - interface_name: String, - nodes: Vec, - object_type: ObjectType, - required: bool, - key: String, - description: String, - ) -> Self { - Self { - interface_name, - nodes, - object_type, - required, - key, - description, - } - } -} - -impl ToTs for ObjectLike { - fn to_ts(&self) -> String { - let required_symbol = if self.required { "" } else { "?" }; - let object_name = self.get_ts_name(); - let array_symbol = if self.object_type == ObjectType::Array { - "[]" - } else { - "" - }; - - let t = format!( - " // {}\n {}{}: {}{}\n", - self.description, self.key, required_symbol, object_name, array_symbol - ); - - t - } -} diff --git a/src-tauri/src/structs/resolver/enums.rs b/src-tauri/src/structs/resolver/enums.rs deleted file mode 100644 index 40c9ca2..0000000 --- a/src-tauri/src/structs/resolver/enums.rs +++ /dev/null @@ -1,36 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::entities::{Atom, ObjectLike}; - -#[derive(PartialEq, Debug)] -pub enum JsonType { - Object, - Array, - Atom, - Unknown, -} - -#[derive(PartialEq, Debug, Deserialize, Serialize , Clone)] -pub enum FormType { - Form, - Json, -} - -#[derive(PartialEq, Clone, Debug)] -pub enum ObjectType { - Object, - Array, -} - -#[derive(PartialEq, Debug)] -pub enum WebType { - Request, - Response, -} - -#[derive(Clone, Debug)] -pub enum JsonValue { - ObjectLike(ObjectLike), - Atom(Atom), - Null -} diff --git a/src-tauri/src/structs/resolver/form_resolver.rs b/src-tauri/src/structs/resolver/form_resolver.rs deleted file mode 100644 index 357290b..0000000 --- a/src-tauri/src/structs/resolver/form_resolver.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde_json::Value; - -use super::{ - enums::WebType, - utils::{get_desc, get_json, get_legal_name}, -}; - -#[derive(Debug)] -pub struct FormResolver { - web_type: WebType, - interface_name: String, - interface_desc: String, - form_value: Value, -} - -impl FormResolver { - pub fn new( - web_type: WebType, - interface_name: String, - interface_desc: String, - json_string: String, - ) -> Self { - Self { - web_type, - interface_name: get_legal_name(&interface_name), - interface_desc, - form_value: get_json(json_string), - } - } - - pub fn to_ts(&self) -> String { - let header = format!("// {}", self.interface_desc); - let interface_ts_name = self.get_interface_ts_name(); - let mut res_string = format!("{}\nexport interface {} {{\n", header, interface_ts_name); - - match self.form_value.is_array() { - true => { - for value in self.form_value.as_array().unwrap() { - let name = Self::get_name(&value); - let t = Self::get_type(&value); - let desc = get_desc(&value, "desc"); - let required = Self::get_required(value); - let required_symbol = if required { "" } else { "?" }; - - let type_define: String = - format!(" // {}\n {}{}: {}\n", desc, name, required_symbol, t); - - res_string = res_string + type_define.as_str(); - } - } - false => {} - } - res_string = res_string + "}\n"; - - res_string - } - - fn get_interface_ts_name(&self) -> String { - match self.web_type { - WebType::Request => format!("{}Request", self.interface_name), - WebType::Response => format!("{}Response", self.interface_name), - } - } - - fn get_name(value: &Value) -> String { - match value.get("name") { - Some(name) => match name.as_str() { - Some(name_str) => get_legal_name(name_str), - None => String::from("unknowName"), - }, - None => String::from("unknowName"), - } - } - - fn get_required(value: &Value) -> bool { - match value.get("required") { - Some(required) => match required.as_str() { - Some(required_string) => { - if required_string == "1" { - true - } else { - false - } - } - None => false, - }, - None => false, - } - } - - fn get_type(value: &Value) -> String { - match value.get("type") { - Some(t) => match t.as_str() { - Some(t_str) => { - if t_str == "text" { - return String::from("string"); - } else { - return String::from("any"); - } - } - None => return String::from("string"), - }, - None => { - return String::from("string"); - } - }; - } -} diff --git a/src-tauri/src/structs/resolver/json_resolver.rs b/src-tauri/src/structs/resolver/json_resolver.rs deleted file mode 100644 index 7dc7995..0000000 --- a/src-tauri/src/structs/resolver/json_resolver.rs +++ /dev/null @@ -1,271 +0,0 @@ -use serde_json::Value; - -use super::{ - entities::{Atom, Node, ObjectLike}, - enums::{JsonType, JsonValue, ObjectType, WebType}, - root::Root, - utils::{get_desc, get_json, get_legal_name}, -}; - -#[derive(Debug)] -pub struct JsonResolver { - interface_name: String, - interface_desc: String, - root: Option, - web_type: WebType, - json_value: Value, -} - -impl JsonResolver { - pub fn new( - web_type: WebType, - interface_name: String, - interface_desc: String, - json_string: String, - ) -> Self { - Self { - interface_name: Self::get_name(&interface_name), - interface_desc, - json_value: get_json(json_string), - root: None, - web_type, - } - } - - pub fn to_ts(&self) -> String { - if let Some(root) = &self.root { - root.to_ts() - } else { - panic!("root is None"); - } - } - - pub fn generate_modal(&mut self) -> &Self { - let root_key = if self.web_type == WebType::Request { - String::from("request") - } else { - String::from("response") - }; - let interface_name = self.interface_name.clone(); - - if Self::is_object(&self.json_value) { - let root = self.generate_root(interface_name, root_key); - self.root = Some(root); - } else { - let root = Root::new(interface_name, self.interface_desc.clone(), root_key, None); - self.root = Some(root); - }; - - self - } - - fn generate_root(&self, interface_name: String, root_key: String) -> Root { - let properties = self.json_value.get("properties").unwrap(); - let properties_iterator = properties.as_object().unwrap().iter(); - let required_list = self.json_value.get("required"); - - let json_values: Vec<_> = properties_iterator - .map(|(key, value)| { - let node_type = Self::get_type(value); - let raw_type = Self::get_ts_type(value); - let required = Self::is_required(key, required_list); - let description = get_desc(value, "description"); - let key = Self::get_name(&key); - - match node_type { - JsonType::Object => { - let nodes = self.generate_nodes(value); - let object = ObjectLike { - interface_name: self.interface_name.clone(), - nodes, - object_type: ObjectType::Object, - required, - description, - key, - }; - - JsonValue::ObjectLike(object) - } - JsonType::Atom => { - let atom = Atom { - value: raw_type, - required, - key, - description, - }; - JsonValue::Atom(atom) - } - JsonType::Array => { - let items = value.get("items").unwrap(); - let nodes = self.generate_nodes(items); - let array = ObjectLike { - interface_name: self.interface_name.clone(), - nodes, - object_type: ObjectType::Array, - required, - description, - key, - }; - - JsonValue::ObjectLike(array) - } - _ => JsonValue::Null, - } - }) - .collect(); - - let root = Root::new( - interface_name, - self.interface_desc.clone(), - root_key, - Some(json_values), - ); - - root - } - - fn generate_nodes(&self, json_value: &Value) -> Vec { - match json_value.get("properties") { - Some(properties) => { - let properties_iterator = properties.as_object().unwrap().iter(); - let required_list = json_value.get("required"); - - let nodes: Vec<_> = properties_iterator - .map(|(key, value)| { - let node_type = Self::get_type(value); - let required = Self::is_required(key, required_list); - let raw_type = Self::get_ts_type(value); - let key = Self::get_name(&key); - let description = get_desc(value, "description"); - - let value = match node_type { - JsonType::Object => { - let nodes = self.generate_nodes(value); - JsonValue::ObjectLike(ObjectLike { - interface_name: self.interface_name.clone(), - nodes, - object_type: ObjectType::Object, - required, - description: description.clone(), - key: key.clone(), - }) - } - JsonType::Array => { - let items = value.get("items").unwrap(); - let nodes = self.generate_nodes(items); - JsonValue::ObjectLike(ObjectLike { - interface_name: self.interface_name.clone(), - nodes, - object_type: ObjectType::Array, - required, - description: description.clone(), - key: key.clone(), - }) - } - JsonType::Atom => { - let atom = Atom { - value: raw_type, - required, - key: key.clone(), - description: description.clone(), - }; - JsonValue::Atom(atom) - } - _ => JsonValue::Null, - }; - - Node::new( - self.interface_name.clone(), - key.clone(), - value, - required, - description.clone(), - ) - }) - .collect(); - - nodes - } - None => { - vec![] - } - } - } - - fn is_object(value: &Value) -> bool { - if let JsonType::Object = Self::get_type(value) { - return true; - } - - false - } - - fn is_required(key: &String, list: Option<&Value>) -> bool { - if let Some(required_list) = list { - let required_key_list = required_list.as_array().unwrap(); - - if required_key_list.iter().any(|x| x.to_string() == format!("\"{}\"", *key)) { - return true; - } - false - } else { - false - } - } - - fn get_type(value: &Value) -> JsonType { - match value.get("type") { - Some(type_value) => match type_value.as_str() { - Some(type_string) => { - if type_string == "object" { - JsonType::Object - } else if type_string == "array" { - JsonType::Array - } else { - JsonType::Atom - } - } - None => JsonType::Unknown, - }, - None => JsonType::Unknown, - } - } - - fn get_ts_type(value: &Value) -> String { - match value.get("type") { - Some(type_value) => match type_value.as_str() { - Some(type_value_str) => type_value_str.to_string(), - None => String::from("any"), - }, - None => String::from("any"), - } - } - - fn get_name(raw_name: &str) -> String { - if raw_name.is_empty() { - String::from("unknownName") - } else { - get_legal_name(raw_name) - } - } -} - -// #[cfg(test)] -// mod test { -// use std::fs; - -// use super::*; - -// #[test] -// fn test_to_ts() { -// let interface_name = "exportAgent"; -// let str = "{\"type\":\"object\",\"title\":\"empty object\",\"properties\":{\"code\":{\"type\":\"string\",\"description\":\"响应码\"},\"data\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"orderId\":{\"type\":\"string\",\"description\":\"业务订单号\"},\"title\":{\"type\":\"string\",\"description\":\"标题\"},\"customerName\":{\"type\":\"string\",\"description\":\"客户名称\"},\"projectName\":{\"type\":\"string\",\"description\":\"项目名称\"},\"productList\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"productName\":{\"type\":\"string\",\"description\":\"商品名称\"},\"orderNum\":{\"type\":\"string\",\"description\":\"商品数量\"}},\"required\":[\"productName\",\"orderNum\"]},\"description\":\"订单商品列表\"},\"orderTotalNum\":{\"type\":\"string\",\"description\":\"订单总数量\"},\"approvalStatus\":{\"type\":\"string\",\"description\":\"审批状态0-待提交 1-审批中 2-审批通过 3-已退回\"},\"orderStatus\":{\"type\":\"string\",\"description\":\"订单状态 1-未发货 2-部分发货 3-全部发货 4-部分退货 5-全部退货 6-已签收\"},\"applicantName\":{\"type\":\"string\",\"description\":\"申请人\"},\"applicant\":{\"type\":\"string\",\"description\":\"申请人id\"},\"businessPersonId\":{\"type\":\"string\",\"description\":\"业务归属人id\"},\"businessPersonName\":{\"type\":\"string\",\"description\":\"归属业务人名称\"},\"applyDate\":{\"type\":\"string\",\"description\":\"申请日期 yyyyMMdd\"},\"updateTime\":{\"type\":\"string\",\"description\":\"操作时间\"}},\"required\":[\"orderId\",\"title\",\"customerName\",\"projectName\",\"productList\",\"orderTotalNum\",\"approvalStatus\",\"applicantName\",\"applyDate\",\"updateTime\",\"applicant\",\"businessPersonId\",\"businessPersonName\"]}},\"pager\":{\"type\":\"object\",\"properties\":{\"currentNum\":{\"type\":\"string\",\"description\":\"当前页\"},\"pageSize\":{\"type\":\"string\",\"description\":\"页大小\"},\"totalPages\":{\"type\":\"string\",\"description\":\"总页数\"},\"totalSize\":{\"type\":\"string\",\"description\":\"总大小\"}},\"description\":\"分页对象\",\"required\":[\"currentNum\",\"totalSize\",\"totalPages\",\"pageSize\"]},\"msg\":{\"type\":\"string\",\"description\":\"响应信息\"},\"timestamp\":{\"type\":\"string\",\"description\":\"响应时间\"}},\"required\":[\"code\",\"data\",\"pager\",\"msg\",\"timestamp\"]}"; -// let web_type = WebType::Request; - -// let res = JsonResolver::new(web_type, interface_name.to_string(), str.to_string()) -// .generate_modal() -// .to_ts(); - -// let _ = fs::write("./test.ts", res); -// } -// } diff --git a/src-tauri/src/structs/resolver/mod.rs b/src-tauri/src/structs/resolver/mod.rs deleted file mode 100644 index aad9db9..0000000 --- a/src-tauri/src/structs/resolver/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod entities; -pub mod root; -pub mod enums; -pub mod form_resolver; -pub mod json_resolver; -pub mod utils; \ No newline at end of file diff --git a/src-tauri/src/structs/resolver/root.rs b/src-tauri/src/structs/resolver/root.rs deleted file mode 100644 index 1178a7c..0000000 --- a/src-tauri/src/structs/resolver/root.rs +++ /dev/null @@ -1,86 +0,0 @@ -use super::{ - entities::{ObjectLike, ToTs, TsBridge}, - enums::JsonValue, -}; -use crate::utils::capitalize_first_letter; -use ts_bridge_derive::TsBridge; - -#[derive(TsBridge, Debug)] -pub struct Root { - pub interface_name: String, - pub interface_desc: String, - pub key: String, - pub children: Option>, -} - -impl Root { - pub fn new( - interface_name: String, - interface_desc: String, - key: String, - children: Option>, - ) -> Self { - Self { - interface_name, - interface_desc, - key, - children, - } - } - - pub fn to_ts(&self) -> String { - let ts_name = self.get_ts_name(); - let header = format!("// {}\n", self.interface_desc); - let mut res_string = String::from(header); - - if let Some(nodes) = &self.children { - res_string = res_string + self.generate_interface(nodes, &ts_name).as_str(); - } else { - res_string = res_string + self.generate_interface(&vec![], &ts_name).as_str(); - } - - res_string - } - - fn generate_interface(&self, nodes: &Vec, ts_name: &str) -> String { - let mut sub_list = Vec::new(); - let mut res_string = format!("export interface {} {{\n", ts_name); - - for node in nodes { - let mut t = String::new(); - if let JsonValue::Atom(atom) = node { - t = atom.to_ts(); - } else if let JsonValue::ObjectLike(object_like) = node { - t = object_like.to_ts(); - sub_list.push(object_like); - } - - res_string = res_string + t.as_str(); - } - - res_string = res_string + "}\n"; - - // 递归解析子interface - let resolved_sub_list = self.resolve_sub_list(sub_list); - - for (json_values, ts_name) in resolved_sub_list { - res_string = res_string + &self.generate_interface(&json_values, &ts_name); - } - - res_string - } - - fn resolve_sub_list(&self, sub_list: Vec<&ObjectLike>) -> Vec<(Vec, String)> { - let res: Vec<_> = sub_list - .iter() - .map(|object_like| { - let ts_name = object_like.get_ts_name(); - let nodes = &object_like.nodes; - let json_values: Vec<_> = nodes.iter().map(|node| node.value.clone()).collect(); - (json_values, ts_name) - }) - .collect(); - - res - } -} diff --git a/src-tauri/src/structs/resolver/utils.rs b/src-tauri/src/structs/resolver/utils.rs deleted file mode 100644 index d3cc1dd..0000000 --- a/src-tauri/src/structs/resolver/utils.rs +++ /dev/null @@ -1,36 +0,0 @@ -use serde_json::Value; - -pub fn get_legal_name(raw_name: &str) -> String { - let chars = raw_name - .chars() - .filter(|c| c.is_ascii_alphanumeric()) - .collect::>(); - - String::from_iter(chars) -} - -pub fn get_legal_desc(raw_desc: &str) -> String { - raw_desc.replace("\n", "").to_string() -} - -pub fn get_desc(value: &Value , key: &str) -> String { - let desc = match value.get(key) { - Some(desc) => { - match desc.as_str() { - Some(desc_str) => get_legal_desc(desc_str), - None => String::from("无注释") - } - }, - None => String::from("无注释") - }; - - desc -} - - -pub fn get_json(json_str: String) -> serde_json::Value { - match serde_json::from_str(&json_str) { - Ok(json) => json, - Err(_) => serde_json::Value::Null, - } -} diff --git a/src-tauri/src/structs/web_response.rs b/src-tauri/src/structs/web_response.rs deleted file mode 100644 index 9c475fa..0000000 --- a/src-tauri/src/structs/web_response.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -// 公共返回参数 -#[derive(Debug, Deserialize, Serialize)] -pub struct CommonResponse { - pub errcode : i32, - pub errmsg : String, - pub data : T -} - -// 分类菜单列表项 -#[derive(Debug, Deserialize, Serialize)] -pub struct CategoryMenuItem { - pub _id: i32, - pub name: String, - pub interfaces : Option -} - -// 分类详情列表 -#[derive(Debug, Deserialize, Serialize)] -pub struct CategoryDataList { - pub count : i32, - pub total : i32, - pub list : Vec -} - -// 分类详情 -#[derive(Debug, Deserialize, Serialize , Clone)] -pub struct CategoryDataItem { - pub _id : i32, - pub catid : i32, - pub title : String, - pub path : String, -} - -// 接口详情 -#[derive(Debug, Deserialize, Serialize)] -pub struct InterfaceData { - pub _id : i32, - pub path : String, - pub project_id: i32, - pub title : String, - pub catid : i32, - pub req_body_other: Option, - pub req_query: Option>, - pub req_params : Option>, - pub req_body_form : Option>, - pub req_body_type : Option, - pub res_body : Option, - pub method: String, -} - - diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs deleted file mode 100644 index 733860a..0000000 --- a/src-tauri/src/utils.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{env, path::PathBuf, fs::File, io::{BufReader, BufRead}}; - -use serde_json::Value; - -pub fn get_current_working_dir() -> std::io::Result { - env::current_dir() -} - -pub fn capitalize_first_letter(s: &str) -> String { - let mut c = s.chars(); - match c.next() { - None => String::new(), - Some(f) => f.to_uppercase().collect::() + c.as_str(), - } -} - -pub fn get_err(value: &Value) -> (i64, String) { - let err_code = value.get("errcode").unwrap().as_i64().unwrap(); - let err_msg = value.get("errmsg").unwrap().as_str().unwrap().to_string(); - - (err_code, err_msg) -} - -pub fn join_path(source : &PathBuf , sub_path : String) -> PathBuf{ - let path_arr : Vec<_> = sub_path.split("/").collect(); - let mut path = source.clone(); - for sub in path_arr{ - path = path.join(sub); - } - - path -} - -pub fn is_string_in_file(ts_file: &PathBuf, string: &str) -> bool { - let file = File::open(ts_file).unwrap(); - let reader = BufReader::new(file); - - for line in reader.lines() { - if line.unwrap().contains(string) { - return true; - } - } - return false; -} - - -// #[test] -// fn test() { -// } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index b36c1fe..d7aff62 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "tauri-yapi-to-ts", - "version": "0.1.3" + "version": "0.2.0" }, "tauri": { "allowlist": { @@ -24,6 +24,14 @@ "message": true, "open": true, "save": true + }, + "path": { + "all": true + }, + "clipboard": { + "all": true, + "readText": true, + "writeText": true } }, "bundle": { diff --git a/src-tauri/ts_bridge_derive/Cargo.toml b/src-tauri/ts_bridge_derive/Cargo.toml deleted file mode 100644 index 8b9877a..0000000 --- a/src-tauri/ts_bridge_derive/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ts_bridge_derive" -version = "0.1.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -syn = "1.0" -quote = "1.0" diff --git a/src-tauri/ts_bridge_derive/src/lib.rs b/src-tauri/ts_bridge_derive/src/lib.rs deleted file mode 100644 index 5887bfd..0000000 --- a/src-tauri/ts_bridge_derive/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn; -use syn::DeriveInput; - -#[proc_macro_derive(TsBridge)] -pub fn ts_bridge(input: TokenStream) -> TokenStream { - // 基于 input 构建 AST 语法树 - let ast: DeriveInput = syn::parse(input).unwrap(); - - // 构建特征实现代码 - impl_ts(&ast) -} - -fn impl_ts(ast: &syn::DeriveInput) -> TokenStream { - let name = &ast.ident; - let gen = quote! { - impl TsBridge for #name { - - fn get_ts_name(&self) -> String { - let format_k = capitalize_first_letter(self.key.clone().as_str()); - let ts_name = self.interface_name.clone() + format_k.as_str(); - ts_name - } - - } - }; - gen.into() -} diff --git a/src/app.html b/src/app.html index d4ae80b..e314284 100644 --- a/src/app.html +++ b/src/app.html @@ -5,9 +5,16 @@ + %sveltekit.head%
%sveltekit.body%
+ diff --git a/src/lib/App.svelte b/src/lib/App.svelte deleted file mode 100644 index f856a71..0000000 --- a/src/lib/App.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - -{#if need_init || load_project} - -{:else} - -{/if} - - diff --git a/src/lib/components/Menu.svelte b/src/lib/components/Menu.svelte deleted file mode 100644 index f6c83a2..0000000 --- a/src/lib/components/Menu.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - -
- - -
- {#if activeMenu === '1'} - - {:else if activeMenu === '2'} - - {:else if activeMenu === '3'} - - {/if} -
-
- - diff --git a/src/lib/components/PreviewModal.svelte b/src/lib/components/PreviewModal.svelte new file mode 100644 index 0000000..32741cb --- /dev/null +++ b/src/lib/components/PreviewModal.svelte @@ -0,0 +1,51 @@ + + + +
+ 生成代码 + +
+ + + {@html highlightHtml} + + +
+ +
+
+ diff --git a/src/lib/components/ProcessingModal.svelte b/src/lib/components/ProcessingModal.svelte deleted file mode 100644 index 77b34f1..0000000 --- a/src/lib/components/ProcessingModal.svelte +++ /dev/null @@ -1,96 +0,0 @@ - - - -
- 日志 - -
- -
- {#each log_list as log} - {#if log.is_success} -

{log.msg}

- {:else} -

{log.msg}

- {/if} - {/each} -
- -
-
- - diff --git a/src/lib/components/Prompt.svelte b/src/lib/components/Prompt.svelte index bb788ec..ec5481f 100644 --- a/src/lib/components/Prompt.svelte +++ b/src/lib/components/Prompt.svelte @@ -1,13 +1,10 @@ + + + + diff --git a/src/lib/modules/ConfigModule/index.svelte b/src/lib/modules/ConfigModule/index.svelte deleted file mode 100644 index f9eb751..0000000 --- a/src/lib/modules/ConfigModule/index.svelte +++ /dev/null @@ -1,161 +0,0 @@ - - -
- - - - - - - - - - -

$1: 请求名

-

$2: 请求类型

-

$3: 返回类型

-

$4: 接口地址

-
-
- - - - -

$1: Request Type 类型

-

$2: Response Type 类型

-

$3: 类型文件相对地址(请在前面添加类型文件夹别名)

-
-
- - - - - - - -

$1: 文件名

-
-
- - - -
-
- -
- - diff --git a/src/lib/modules/ServiceModule/components/Node.svelte b/src/lib/modules/ServiceModule/components/Node.svelte deleted file mode 100644 index 72de6e6..0000000 --- a/src/lib/modules/ServiceModule/components/Node.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - -
-
- - - {#if children.length} - - {/if} -
- - {#if expanded} -
    - {#each children as child} -
  • - -
  • - {/each} -
- {/if} -
- - diff --git a/src/lib/modules/ServiceModule/components/TypesTree.svelte b/src/lib/modules/ServiceModule/components/TypesTree.svelte deleted file mode 100644 index 25c6bdb..0000000 --- a/src/lib/modules/ServiceModule/components/TypesTree.svelte +++ /dev/null @@ -1,121 +0,0 @@ - - -
-
-
对应的接口树:
- -
-
- - -
- - {#each list as item} - - {/each} -
- - diff --git a/src/lib/modules/ServiceModule/index.svelte b/src/lib/modules/ServiceModule/index.svelte deleted file mode 100644 index f3441c5..0000000 --- a/src/lib/modules/ServiceModule/index.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -{#if need_init} - -{/if} - -{#if load_types} - -{/if} diff --git a/src/lib/modules/TypeModule/components/ProjectTab/AddProjectModal.svelte b/src/lib/modules/TypeModule/components/ProjectTab/AddProjectModal.svelte deleted file mode 100644 index 72d0e8c..0000000 --- a/src/lib/modules/TypeModule/components/ProjectTab/AddProjectModal.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - -
- 新增项目 - -
- - - - -
- - -
-
diff --git a/src/lib/modules/TypeModule/components/ProjectTab/Interface.svelte b/src/lib/modules/TypeModule/components/ProjectTab/Interface.svelte deleted file mode 100644 index 278d0df..0000000 --- a/src/lib/modules/TypeModule/components/ProjectTab/Interface.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
- {data.name} -
-
-
update_interface(data.id)} - on:keypress|stopPropagation={() => update_interface(data.id)} - role="button" - tabindex="0" - > - 更新接口 -
-
-
- - diff --git a/src/lib/modules/TypeModule/components/ProjectTab/index.svelte b/src/lib/modules/TypeModule/components/ProjectTab/index.svelte deleted file mode 100644 index f067c19..0000000 --- a/src/lib/modules/TypeModule/components/ProjectTab/index.svelte +++ /dev/null @@ -1,155 +0,0 @@ - - - - - -
- - - -
-
- (banInitModal = false)} - label="搜索" - > - - -
- -{#if active.project_id} - project.project_id} - let:tab - bind:active - > - - - - -
-
- - -
- - {#each active.categories as category} - - {/each} - -
-{/if} diff --git a/src/lib/modules/TypeModule/index.svelte b/src/lib/modules/TypeModule/index.svelte deleted file mode 100644 index 155f874..0000000 --- a/src/lib/modules/TypeModule/index.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - -{#if need_init} - -{/if} - -{#if load_project} - -{/if} diff --git a/src/lib/store.ts b/src/lib/store.ts index 496ffef..1a56d0c 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,5 +1,18 @@ import { writable } from "svelte/store"; +import type { Config } from "./types/public"; export let processingModalOpen = writable(false); export let processingModalTotal = writable(0); -export let runningTask = writable(false) +export let sourcePath = writable("") +export let config = writable({ + base_url: "", + types_path: "", + project_list: [], + request_path: "", + request_template: "", + header_template: "", + file_name_template: "", + type_import_template: "", +}) +export let PreviewModalOpen = writable(false) +export let PreviewModalContent = writable("") \ No newline at end of file diff --git a/src/lib/types/public.ts b/src/lib/types/public.ts index fd65488..0abcd00 100644 --- a/src/lib/types/public.ts +++ b/src/lib/types/public.ts @@ -1,23 +1,25 @@ export interface Config { - source_path?: string base_url?: string - rate_limit?: number types_path?: string - types_full_path?: string, - break_seconds?: number, project_list?: ProjectList[], request_path?: string, request_template?: string, header_template?: string file_name_template?: string type_import_template?: string - proxy?:string +} + +export interface GlobalConfig { + proxy?: string + rate_limit?: number + break_seconds?: number } export interface ProjectList { token: string project_id: string + project_name:string categories: CategoryType[] } @@ -30,6 +32,8 @@ export interface CategoryType { export interface InterfaceType { id: string name?: string + path?: string + lock?: boolean } export interface SuccessResponse { @@ -38,7 +42,7 @@ export interface SuccessResponse { } export interface QueueStatus { - total:number; + total: number; // success:number; // fail:number; } @@ -47,4 +51,11 @@ export interface TypesTree { full_path: string name: string children: TypesTree[] +} + +export interface RequestString { + full_path: string + name: string + content: string + checked: boolean } \ No newline at end of file diff --git a/src/lib/types/yapi.ts b/src/lib/types/yapi.ts new file mode 100644 index 0000000..55ae94a --- /dev/null +++ b/src/lib/types/yapi.ts @@ -0,0 +1,49 @@ +export type ProjectBaseInfo = { + _id: number, + desc: string, + name: string +} + +export type CategoryMenuList = { + _id: number, + name: string, + interfaces?: CategoryDataList +}[] + +export type CategoryDataList = { + count: number, + total: number, + list: InterfaceDataItem[] +} + +export type InterfaceDataItem = { + interface_data: { + _id: number, + catid: number, + title: string, + path: string, + project_id?: number; + }, + ts: string +} + +export type QueueLog = { + msg: string, + processd_number: number, + is_success: boolean, + resolved_interface: ResolvedInterface +} + +export type ResolvedInterface = { + interface: InterfaceData, + ts_string: string, +} + +export type InterfaceData = { + _id: number, + path: string, + project_id: number, + title: string, + catid: number, + method: string, +} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 652ac6a..6b0e16f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,37 +1,26 @@ import { invoke } from "@tauri-apps/api/tauri"; +import type { Config, SuccessResponse } from "./types/public"; +import { toast } from "@zerodevx/svelte-toast"; +import { toastTheme } from "./consts"; +import { config, processingModalOpen, processingModalTotal } from "./store"; -export function request(name: string, data?: Record) { - let json = JSON.stringify(data); - return invoke(name, { data: json }); -} - -export function isEmpty(obj: Record) { - for (const prop in obj) { - if (Object.hasOwn(obj, prop)) { - return false; - } - } - - return true; -} function getComputedStyle(node?: HTMLUListElement) { - return { - height: node.offsetHeight, - width: node.offsetWidth, - }; + return { + height: node?.offsetHeight, + width: node?.offsetWidth, + }; } export function wop(node?: HTMLUListElement, params?: { duration?: number }) { const { height, width } = getComputedStyle(node); - const { duration = 300 } = params; + const { duration = 300 } = params || {}; return { duration, css: (t: number) => ` - clip-path: polygon(0 0, ${t * 100}% 0, ${t * 100}% ${t * 100}%, 0 ${ - t * 100 - }%); + clip-path: polygon(0 0, ${t * 100}% 0, ${t * 100}% ${t * 100}%, 0 ${t * 100 + }%); margin-right: calc((${t - 1})*${width}px); margin-bottom: calc((${t - 1})*${height}px); overflow-y:hidden @@ -39,4 +28,22 @@ export function wop(node?: HTMLUListElement, params?: { duration?: number }) { }; } +export function startTask() { + invoke>('start_task').then((res) => { + toast.push(res.message, toastTheme.success); + processingModalOpen.update(() => true); + processingModalTotal.update(() => res.data); + }); +} +export function loadConfig(sourcePath: string) { + return invoke>('load_project_config', { sourcePath }) + .then((res) => { + toast.push(res.message, toastTheme.success); + config.set(res.data); + return res + }) + .catch((e) => { + toast.push(JSON.stringify(e), toastTheme.error); + }); +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d4f7636..3d20e94 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,11 +1,76 @@
-
- -
+ + + {#if existProject} + +
+ +
+ {:else} + + {/if}
+ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 69da172..281c686 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,5 +1 @@ - - - +
\ No newline at end of file diff --git a/src/routes/config/+page.svelte b/src/routes/config/+page.svelte new file mode 100644 index 0000000..6538237 --- /dev/null +++ b/src/routes/config/+page.svelte @@ -0,0 +1,187 @@ + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + { + update_project_config(); + }} + > + +

$1: 请求名

+

$2: 请求类型

+

$3: 返回类型

+

$4: 接口地址

+
+
+
+
+ + { + update_project_config(); + }} + > + +

$1: Request Type 类型

+

$2: Response Type 类型

+

$3: 类型文件相对地址(请在前面添加类型文件夹别名)

+
+
+
+ +
+ +
+ +
+ + { + update_project_config(); + }} + > + + +

$1: 文件名

+
+
+
+ +
+ +
+ +
+ + diff --git a/src/routes/global.css b/src/routes/global.css index f89ccc4..4616249 100644 --- a/src/routes/global.css +++ b/src/routes/global.css @@ -1,40 +1,40 @@ -.flex{ - display: flex; - } - - .flex-inline { - display: inline-flex; - } - - .flex-1{ - flex: 1; - } - - .items-center{ - align-items: center; - } - - .items-end { - align-items: end; - } - - .justify-center{ - justify-content: center; - } - - .justify-between{ - justify-content: space-between; - } - - .justify-arround { - justify-content: space-around; - } - - .justify-end { - justify-content: end; - } - - .w-full { - width: 100%; - } - \ No newline at end of file + +.flex { + display: flex; +} + +.flex-inline { + display: inline-flex; +} + +.flex-1 { + flex: 1; +} + +.items-center { + align-items: center; +} + +.items-end { + align-items: end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-arround { + justify-content: space-around; +} + +.justify-end { + justify-content: end; +} + +.w-full { + width: 100%; +} \ No newline at end of file diff --git a/src/routes/other/+page.svelte b/src/routes/other/+page.svelte new file mode 100644 index 0000000..425ff60 --- /dev/null +++ b/src/routes/other/+page.svelte @@ -0,0 +1,23 @@ + + +
+
日志文件夹:{logDir}
+
+
配置文件夹:{configDir}
+
diff --git a/src/routes/request/+page.svelte b/src/routes/request/+page.svelte new file mode 100644 index 0000000..b7bad67 --- /dev/null +++ b/src/routes/request/+page.svelte @@ -0,0 +1,25 @@ + + +{#if need_init} + +{/if} + +{#if load_types} + +{/if} diff --git a/src/routes/request/components/CheckListModal.svelte b/src/routes/request/components/CheckListModal.svelte new file mode 100644 index 0000000..1e4990f --- /dev/null +++ b/src/routes/request/components/CheckListModal.svelte @@ -0,0 +1,110 @@ + + + +
+ 日志 + +
+ +
请勾选想要生成 ts 类型的接口:
+
+ {#each checkList as log} +
+
+ +
+
{log.name}
+
{log.full_path.replaceAll('\\', '/')}
+
+
+ +
+ {/each} +
+ +
+ +
+
+
+ + diff --git a/src/lib/modules/ServiceModule/components/ConfigModal.svelte b/src/routes/request/components/ConfigModal.svelte similarity index 87% rename from src/lib/modules/ServiceModule/components/ConfigModal.svelte rename to src/routes/request/components/ConfigModal.svelte index d91bab1..245d8bb 100644 --- a/src/lib/modules/ServiceModule/components/ConfigModal.svelte +++ b/src/routes/request/components/ConfigModal.svelte @@ -1,12 +1,13 @@ + +
+
+ + + {#if data.children.length} + + + + {/if} +
+ + {#if expanded} +
    + {#each data.children as child} +
  • + +
  • + {/each} +
+ {/if} +
+ + diff --git a/src/routes/request/components/TypesTree.svelte b/src/routes/request/components/TypesTree.svelte new file mode 100644 index 0000000..51333f9 --- /dev/null +++ b/src/routes/request/components/TypesTree.svelte @@ -0,0 +1,188 @@ + + + +
+
+
+
根据文件夹架构生成request:
+
+ + +
+
+
+ +
+
+ +
+ {#each filtered_list as item} + + {/each} +
+
+ + diff --git a/src/routes/type/+page.svelte b/src/routes/type/+page.svelte new file mode 100644 index 0000000..5608575 --- /dev/null +++ b/src/routes/type/+page.svelte @@ -0,0 +1,33 @@ + + +{#if need_init} + +{/if} + +{#if load_project} + +{/if} diff --git a/src/lib/modules/TypeModule/components/ConfigModal.svelte b/src/routes/type/components/ConfigModal.svelte similarity index 66% rename from src/lib/modules/TypeModule/components/ConfigModal.svelte rename to src/routes/type/components/ConfigModal.svelte index 08d3861..8501560 100644 --- a/src/lib/modules/TypeModule/components/ConfigModal.svelte +++ b/src/routes/type/components/ConfigModal.svelte @@ -1,20 +1,20 @@ + + +
+ 新增分类 + +
+ + + +
+ + +
+
diff --git a/src/routes/type/components/ProjectTab/AddInterfaceModal.svelte b/src/routes/type/components/ProjectTab/AddInterfaceModal.svelte new file mode 100644 index 0000000..281d0f3 --- /dev/null +++ b/src/routes/type/components/ProjectTab/AddInterfaceModal.svelte @@ -0,0 +1,97 @@ + + + +
+ 新增接口 + +
+ + + +
+ + +
+
diff --git a/src/routes/type/components/ProjectTab/AddProjectModal.svelte b/src/routes/type/components/ProjectTab/AddProjectModal.svelte new file mode 100644 index 0000000..133c67a --- /dev/null +++ b/src/routes/type/components/ProjectTab/AddProjectModal.svelte @@ -0,0 +1,108 @@ + + + +
+ 新增项目 + +
+ + + +
+ + +
+
diff --git a/src/lib/modules/TypeModule/components/ProjectTab/Category.svelte b/src/routes/type/components/ProjectTab/Category.svelte similarity index 56% rename from src/lib/modules/TypeModule/components/ProjectTab/Category.svelte rename to src/routes/type/components/ProjectTab/Category.svelte index f22ba1f..7a28c07 100644 --- a/src/lib/modules/TypeModule/components/ProjectTab/Category.svelte +++ b/src/routes/type/components/ProjectTab/Category.svelte @@ -2,12 +2,13 @@ import { Panel, Header, Content } from '@smui-extra/accordion'; import type { CategoryType, SuccessResponse } from '@/types/public'; import Interface from './Interface.svelte'; - import { request } from '@/utils'; import { toast } from '@zerodevx/svelte-toast'; import { toastTheme } from '@/consts'; - import { processingModalTotal, processingModalOpen, runningTask } from '@/store'; - import { confirm } from '@tauri-apps/api/dialog'; + import { sourcePath } from '@/store'; import Tooltip, { Wrapper } from '@smui/tooltip'; + import { invoke } from '@tauri-apps/api'; + import type { CategoryDataList } from '@/types/yapi'; + import { loadConfig, startTask } from '@/utils'; export let data: CategoryType; export let token: string; @@ -15,38 +16,42 @@ $: interfaces = data.interfaces; async function update_category(id: string, name: string, is_full_update: boolean) { - if ($runningTask) { - toast.push('正在执行任务...请稍等', toastTheme.error); - return; - } - - const confirmed = await confirm('操作将重新生成ts文件,是否确定?'); - - if (!confirmed) return; + try { + toast.push(`正在获取分类${name}下接口...`, toastTheme.success); + const interfaceList = await invoke>( + 'get_cat_interface_list', + { + token, + sourcePath: $sourcePath, + catId: Number(id) + } + ); + loadConfig($sourcePath); - let categories = [ - { - id, - name - } - ]; + let num = 0; - toast.push('正在添加任务...'); - request('update_categories', { categories, token, is_full_update }) - // @ts-expect-error - .then((res: SuccessResponse) => { - if (res.data === 0) { - toast.push('无待执行的任务'); - } else { - toast.push(res.message, toastTheme.success); - processingModalOpen.update(() => true); - processingModalTotal.update(() => res.data); - runningTask.update(() => true); + for await (let i of interfaceList.data.list) { + if (!is_full_update) { + let oldCategory = data; + if (oldCategory.interfaces.find((old_i) => old_i.id === String(i._id))) continue; } - }) - .catch((e) => { - toast.push(JSON.stringify(e), toastTheme.error); - }); + num++; + await invoke('add_interface_task', { + data: { + token, + source_path: $sourcePath, + interface_id: i._id + } + }); + } + if (num) { + startTask(); + } else { + toast.push(`无需更新`, toastTheme.success); + } + } catch (e) { + toast.push(JSON.stringify(e), toastTheme.error); + } } @@ -66,8 +71,8 @@ > 全量更新分类下的接口 - 全量更新分类下的接口 - + 更新分类下的所有接口 +
- - 增量更新分类下的接口 - 增量更新分类下的接口 - + + 增量更新分类下的接口 + 更新分类下新增的接口 +
diff --git a/src/routes/type/components/ProjectTab/Interface.svelte b/src/routes/type/components/ProjectTab/Interface.svelte new file mode 100644 index 0000000..1f962f0 --- /dev/null +++ b/src/routes/type/components/ProjectTab/Interface.svelte @@ -0,0 +1,81 @@ + + +
+
+ {data.name} +
+
+
update_interface(data.id)} + on:keypress|stopPropagation={() => update_interface(data.id)} + role="button" + tabindex="0" + > + + 更新接口 + 更新接口 + +
+ +
+
+ + diff --git a/src/routes/type/components/ProjectTab/ProcessingModal.svelte b/src/routes/type/components/ProjectTab/ProcessingModal.svelte new file mode 100644 index 0000000..c87bdd6 --- /dev/null +++ b/src/routes/type/components/ProjectTab/ProcessingModal.svelte @@ -0,0 +1,120 @@ + + + +
+ 日志 + +
+ +
请勾选想要生成 ts 类型的接口:
+
+ {#each checkList as log} +
+ + {log.interface.title} + {log.interface.path} +
+ {/each} +
+ +
+ +
+
+
+ + diff --git a/src/routes/type/components/ProjectTab/Project.svelte b/src/routes/type/components/ProjectTab/Project.svelte new file mode 100644 index 0000000..a60ed94 --- /dev/null +++ b/src/routes/type/components/ProjectTab/Project.svelte @@ -0,0 +1,113 @@ + + + +
+ + +
+ + {#each filtered_category_list as category} + + {/each} + diff --git a/src/routes/type/components/ProjectTab/index.svelte b/src/routes/type/components/ProjectTab/index.svelte new file mode 100644 index 0000000..67a25ac --- /dev/null +++ b/src/routes/type/components/ProjectTab/index.svelte @@ -0,0 +1,92 @@ + + + + + + + +
+
+
+ + + +
+ {#if active?.project_id} + project.project_id} + let:tab + bind:active + > + + + + + {/if} +
+ {#key active.categories} + {#if active?.project_id} + + {/if} + {/key} +
diff --git a/static/preview.svg b/static/preview.svg new file mode 100644 index 0000000..0dc3eeb --- /dev/null +++ b/static/preview.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a53a86d..01350b9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, + "noImplicitAny": false, "moduleResolution": "bundler", "baseUrl": "./", "paths": {