fix: 改为跨平台管理桌面端后端进程
This commit is contained in:
Generated
+96
-11
@@ -239,6 +239,7 @@ version = "3.0.0"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"sysinfo",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
@@ -1853,6 +1854,15 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
@@ -2018,6 +2028,16 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.2"
|
||||
@@ -3043,6 +3063,20 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -3090,7 +3124,7 @@ dependencies = [
|
||||
"tao-macros",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
@@ -3161,7 +3195,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"window-vibrancy",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3327,7 +3361,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3352,7 +3386,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"wry",
|
||||
]
|
||||
|
||||
@@ -4121,7 +4155,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
|
||||
dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
@@ -4145,7 +4179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||
dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
@@ -4201,11 +4235,23 @@ version = "0.61.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-collections 0.2.0",
|
||||
"windows-core 0.61.2",
|
||||
"windows-future",
|
||||
"windows-future 0.2.1",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
"windows-numerics 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections 0.3.2",
|
||||
"windows-core 0.62.2",
|
||||
"windows-future 0.3.2",
|
||||
"windows-numerics 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4217,6 +4263,15 @@ dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
@@ -4251,7 +4306,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-threading",
|
||||
"windows-threading 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
"windows-threading 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4298,6 +4364,16 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -4427,6 +4503,15 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-version"
|
||||
version = "0.1.7"
|
||||
@@ -4746,7 +4831,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
|
||||
@@ -11,6 +11,7 @@ tauri-build = { version = "2", features = [] }
|
||||
[dependencies]
|
||||
reqwest = { version = "0.13", default-features = false, features = ["stream"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sysinfo = { version = "0.38.4", default-features = false, features = ["system"] }
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
|
||||
+63
-48
@@ -9,6 +9,7 @@ use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use sysinfo::{Pid, ProcessesToUpdate, Signal, System};
|
||||
use tauri::{Manager, Runtime};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_shell::process::CommandChild;
|
||||
@@ -17,12 +18,12 @@ use tauri_plugin_shell::ShellExt;
|
||||
const SERVER_PORT: u16 = 9081;
|
||||
const SERVER_ADDR: &str = "127.0.0.1:9081";
|
||||
const HTTP_ERROR_PREFIX: &str = "CAPAREPORT_HTTP_ERROR:";
|
||||
#[cfg(target_os = "windows")]
|
||||
const SERVER_PROCESS_NAME: &str = "capareport-server.exe";
|
||||
const SERVER_PROCESS_NAME: &str = "capareport-server";
|
||||
const SERVER_PID_FILE: &str = "server.pid";
|
||||
|
||||
struct ServerProcess {
|
||||
child: CommandChild,
|
||||
pid: u32,
|
||||
pid_file: PathBuf,
|
||||
}
|
||||
|
||||
struct ServerState(Mutex<Option<ServerProcess>>);
|
||||
@@ -98,7 +99,9 @@ fn start_server<R: Runtime>(app: &tauri::App<R>) -> Result<(), Box<dyn Error>> {
|
||||
fs::create_dir_all(data_dir.join("logs"))?;
|
||||
copy_resource(app, "Configure.json", &data_dir)?;
|
||||
copy_resource(app, "ReportScript.sql", &data_dir)?;
|
||||
stop_existing_server_on_port(SERVER_PORT);
|
||||
let pid_file = data_dir.join(SERVER_PID_FILE);
|
||||
stop_recorded_server(&pid_file);
|
||||
ensure_server_port_available(SERVER_ADDR)?;
|
||||
|
||||
let port_arg = SERVER_PORT.to_string();
|
||||
let (mut rx, child) = app
|
||||
@@ -110,17 +113,27 @@ fn start_server<R: Runtime>(app: &tauri::App<R>) -> Result<(), Box<dyn Error>> {
|
||||
)
|
||||
.args(["--host", "127.0.0.1", "--port", &port_arg])
|
||||
.spawn()?;
|
||||
let pid = child.pid();
|
||||
fs::write(&pid_file, pid.to_string())?;
|
||||
|
||||
if let Err(error) = wait_for_server(SERVER_ADDR, Duration::from_secs(60)) {
|
||||
let _ = child.kill();
|
||||
let _ = fs::remove_file(&pid_file);
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(async move { while rx.recv().await.is_some() {} });
|
||||
|
||||
let pid = child.pid();
|
||||
let state = app.state::<ServerState>();
|
||||
*state.0.lock().expect("server state lock poisoned") = Some(ServerProcess { child, pid });
|
||||
*state.0.lock().expect("server state lock poisoned") = Some(ServerProcess { child, pid_file });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_server_port_available(addr: &str) -> Result<(), Box<dyn Error>> {
|
||||
let socket_addr: SocketAddr = addr.parse()?;
|
||||
if TcpStream::connect_timeout(&socket_addr, Duration::from_millis(300)).is_ok() {
|
||||
return Err(format!("Port {SERVER_PORT} is already in use").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -146,66 +159,68 @@ fn health_check(socket_addr: &SocketAddr) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(response.starts_with("HTTP/1.1 200") || response.starts_with("HTTP/1.0 200"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn stop_existing_server_on_port(port: u16) {
|
||||
let Ok(output) = std::process::Command::new("netstat")
|
||||
.args(["-ano", "-p", "tcp"])
|
||||
.output()
|
||||
else {
|
||||
fn stop_recorded_server(pid_file: &Path) {
|
||||
let Ok(content) = fs::read_to_string(pid_file) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let marker = format!(":{port}");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
if !line.contains(&marker) || !line.to_ascii_uppercase().contains("LISTENING") {
|
||||
continue;
|
||||
if let Ok(pid) = content.trim().parse::<u32>() {
|
||||
if !terminate_server_process(pid) {
|
||||
let _ = fs::remove_file(pid_file);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(pid) = line.split_whitespace().last() else {
|
||||
continue;
|
||||
};
|
||||
if pid.parse::<u32>().is_err() {
|
||||
continue;
|
||||
}
|
||||
if !is_capareport_server_pid(pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = std::process::Command::new("taskkill")
|
||||
.args(["/F", "/T", "/PID", pid])
|
||||
.status();
|
||||
wait_for_process_exit(pid, Duration::from_secs(5));
|
||||
}
|
||||
let _ = fs::remove_file(pid_file);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn is_capareport_server_pid(pid: &str) -> bool {
|
||||
let Ok(output) = std::process::Command::new("tasklist")
|
||||
.args(["/FI", &format!("PID eq {pid}"), "/FO", "CSV", "/NH"])
|
||||
.output()
|
||||
else {
|
||||
fn terminate_server_process(pid: u32) -> bool {
|
||||
let mut system = System::new();
|
||||
let pid = Pid::from_u32(pid);
|
||||
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
||||
|
||||
let Some(process) = system.process(pid) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
.to_ascii_lowercase()
|
||||
.contains(SERVER_PROCESS_NAME)
|
||||
if !is_server_process_name(process.name()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if process.kill_with(Signal::Kill) != Some(true) {
|
||||
let _ = process.kill();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn stop_existing_server_on_port(_port: u16) {}
|
||||
fn wait_for_process_exit(pid: u32, timeout: Duration) {
|
||||
let started_at = Instant::now();
|
||||
while started_at.elapsed() < timeout {
|
||||
if !is_process_running(pid) {
|
||||
return;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_process_running(pid: u32) -> bool {
|
||||
let mut system = System::new();
|
||||
let pid = Pid::from_u32(pid);
|
||||
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
||||
system.process(pid).is_some()
|
||||
}
|
||||
|
||||
fn is_server_process_name(name: &std::ffi::OsStr) -> bool {
|
||||
let normalized = name.to_string_lossy().to_ascii_lowercase();
|
||||
normalized == SERVER_PROCESS_NAME || normalized == format!("{SERVER_PROCESS_NAME}.exe")
|
||||
}
|
||||
|
||||
fn stop_server<R: Runtime>(app: &tauri::AppHandle<R>) {
|
||||
let state = app.state::<ServerState>();
|
||||
let child = state.0.lock().expect("server state lock poisoned").take();
|
||||
if let Some(process) = child {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = std::process::Command::new("taskkill")
|
||||
.args(["/F", "/T", "/PID", &process.pid.to_string()])
|
||||
.status();
|
||||
}
|
||||
let _ = process.child.kill();
|
||||
let _ = fs::remove_file(process.pid_file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user