Skip to content

Commit 0f107ab

Browse files
committed
🐛 重构管理员权限重启逻辑
1 parent 46cf407 commit 0f107ab

5 files changed

Lines changed: 127 additions & 69 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tauri-build = { version = "2.5.3", features = [] }
2121

2222
[dependencies]
2323
chrono = "0.4.42"
24-
log = "0.4.28"
24+
log = "0.4.29"
2525
prost = "0.14.1"
2626
prost-types = "0.14.1"
2727
serde = { version = "1.0.228", features = ["derive"] }

src-tauri/src/commands.rs

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! 命令模块,负责处理命令
2-
//! @since Beta v0.7.8
2+
//! @since Beta v0.8.8
33
44
use tauri::{AppHandle, Emitter, Manager, WebviewWindowBuilder};
55
use tauri_utils::config::{WebviewUrl, WindowConfig};
@@ -128,57 +128,89 @@ pub fn is_in_admin() -> bool {
128128
}
129129
}
130130

131+
#[cfg(target_os = "windows")]
132+
pub fn shell_runas_with_args(args: &str) -> Result<(), String> {
133+
use std::ffi::OsStr;
134+
use std::iter::once;
135+
use std::os::windows::ffi::OsStrExt;
136+
use std::ptr::null_mut;
137+
138+
use windows_sys::Win32::Foundation::HWND;
139+
use windows_sys::Win32::UI::Shell::ShellExecuteW;
140+
use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
141+
142+
fn to_wide(s: &OsStr) -> Vec<u16> {
143+
s.encode_wide().chain(once(0)).collect()
144+
}
145+
146+
let exe_path = std::env::current_exe().map_err(|e| e.to_string())?;
147+
let exe_wide = to_wide(exe_path.as_os_str());
148+
let args_wide = to_wide(OsStr::new(args));
149+
let cwd_wide =
150+
exe_path.parent().map(|p| to_wide(p.as_os_str())).unwrap_or_else(|| to_wide(OsStr::new("")));
151+
152+
unsafe {
153+
let result = ShellExecuteW(
154+
0 as HWND,
155+
to_wide(OsStr::new("runas")).as_ptr(),
156+
exe_wide.as_ptr(),
157+
args_wide.as_ptr(),
158+
if cwd_wide.len() > 1 { cwd_wide.as_ptr() } else { null_mut() },
159+
SW_SHOWNORMAL,
160+
);
161+
if (result as usize) > 32 {
162+
Ok(())
163+
} else {
164+
Err("Failed to ShellExecuteW runas".into())
165+
}
166+
}
167+
}
168+
169+
// 等待父进程退出(释放单例锁)后,再以管理员身份启动新实例
170+
#[cfg(target_os = "windows")]
171+
pub fn run_watchdog(parent_pid: u32, args_to_pass: &str) -> Result<(), String> {
172+
use std::time::Duration;
173+
use windows_sys::Win32::Foundation::HANDLE;
174+
use windows_sys::Win32::Storage::FileSystem::SYNCHRONIZE;
175+
use windows_sys::Win32::System::Threading::{OpenProcess, WaitForSingleObject, INFINITE};
176+
177+
// 打开父进程句柄用于等待
178+
let handle: HANDLE = unsafe { OpenProcess(SYNCHRONIZE, 0, parent_pid) };
179+
if handle == std::ptr::null_mut() {
180+
// 如果拿不到句柄,可能父进程已退出,稍作等待后继续
181+
std::thread::sleep(Duration::from_millis(300));
182+
} else {
183+
unsafe {
184+
WaitForSingleObject(handle, INFINITE);
185+
}
186+
}
187+
188+
// 父进程已退出 → 触发 UAC 提权启动新实例
189+
shell_runas_with_args(args_to_pass)
190+
}
191+
131192
// 以管理员权限重启应用
132193
#[tauri::command]
133-
pub fn run_with_admin() -> Result<(), String> {
194+
pub fn run_with_admin(app_handle: AppHandle) -> Result<(), String> {
134195
#[cfg(not(target_os = "windows"))]
135196
{
136197
return Err("This function is only supported on Windows.".into());
137198
}
199+
138200
#[cfg(target_os = "windows")]
139201
{
140-
use std::ffi::OsStr;
141-
use std::iter::once;
142-
use std::os::windows::ffi::OsStrExt;
143-
use std::ptr::null_mut;
144-
use windows_sys::Win32::Foundation::HWND;
145-
use windows_sys::Win32::UI::Shell::ShellExecuteW;
146-
use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
147-
148-
fn to_wide(s: &OsStr) -> Vec<u16> {
149-
s.encode_wide().chain(once(0)).collect()
150-
}
151-
152-
let exe_path = std::env::current_exe().map_err(|e| e.to_string())?;
153-
if !exe_path.exists() {
154-
return Err(format!("executable not found: {}", exe_path.display()));
155-
}
156-
157-
let elevated_arg = "--elevated";
158-
// /C start "" "<full_path>" --elevated-action=post_install
159-
let params = format!("/C start \"\" \"{}\" {}", exe_path.display(), elevated_arg);
160-
161-
let cmd_w = to_wide(OsStr::new("cmd.exe"));
162-
let verb_w = to_wide(OsStr::new("runas"));
163-
let params_w = to_wide(OsStr::new(&params));
164-
let workdir_w =
165-
exe_path.parent().map(|p| to_wide(p.as_os_str())).unwrap_or_else(|| to_wide(OsStr::new("")));
166-
167-
unsafe {
168-
let result = ShellExecuteW(
169-
0 as HWND,
170-
verb_w.as_ptr(),
171-
cmd_w.as_ptr(),
172-
params_w.as_ptr(),
173-
if workdir_w.len() > 1 { workdir_w.as_ptr() } else { null_mut() },
174-
SW_SHOWNORMAL,
175-
);
176-
177-
if (result as usize) > 32 {
178-
Ok(())
179-
} else {
180-
Err("Failed to restart as administrator via cmd.".into())
181-
}
182-
}
202+
let parent_pid = std::process::id();
203+
let exe = std::env::current_exe().map_err(|e| e.to_string())?;
204+
let mut cmd = std::process::Command::new(exe);
205+
cmd
206+
.arg("--watchdog")
207+
.arg(format!("--ppid={}", parent_pid))
208+
// 看门狗不加载单例插件(通过参数决定 main 的初始化)
209+
.spawn()
210+
.map_err(|e| format!("spawn watchdog failed: {e}"))?;
211+
212+
// 立即退出:单例锁释放
213+
app_handle.exit(0);
214+
Ok(())
183215
}
184216
}

src-tauri/src/lib.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! 主模块,用于启动应用
2-
//! @since Beta v0.7.8
2+
//! @since Beta v0.8.8
33
44
mod client;
55
mod commands;
@@ -10,12 +10,12 @@ mod yae;
1010

1111
use crate::client::create_mhy_client;
1212
use crate::commands::{
13-
create_window, execute_js, get_dir_size, init_app, is_in_admin, run_with_admin,
13+
create_window, execute_js, get_dir_size, init_app, is_in_admin, run_watchdog, run_with_admin,
1414
};
1515
use crate::plugins::{build_log_plugin, build_si_plugin};
1616
#[cfg(target_os = "windows")]
1717
use crate::yae::call_yae_dll;
18-
use tauri::{generate_context, generate_handler, Builder, Manager, Window, WindowEvent};
18+
use tauri::{generate_context, generate_handler, Manager, Window, WindowEvent};
1919

2020
// 窗口事件处理
2121
fn window_event_handler(app: &Window, event: &WindowEvent) {
@@ -40,9 +40,35 @@ fn window_event_handler(app: &Window, event: &WindowEvent) {
4040

4141
#[cfg_attr(mobile, tauri::mobile_entry_point)]
4242
pub fn run() {
43-
Builder::default()
43+
#[cfg(target_os = "windows")]
44+
{
45+
let args: Vec<String> = std::env::args().collect();
46+
let is_watchdog = args.iter().any(|a| a == "--watchdog");
47+
// 看门狗模式:不初始化 Tauri,不加载单例,纯等待 + 提权启动
48+
if is_watchdog {
49+
// 解析父进程 PID
50+
let mut ppid: u32 = 0;
51+
for a in &args {
52+
if let Some(rest) = a.strip_prefix("--ppid=") {
53+
if let Ok(v) = rest.parse::<u32>() {
54+
ppid = v;
55+
}
56+
}
57+
}
58+
// 等父进程退出后再 runas 启动管理员实例,传入 --elevated 标志
59+
let _ = run_watchdog(ppid, "--elevated");
60+
// 看门狗退出
61+
return;
62+
}
63+
}
64+
65+
// 正常应用实例:加载单例插件,防止多实例
66+
let mut builder = tauri::Builder::default();
67+
68+
// 只有在正常/管理员实例下才加载单例插件;看门狗不加载
69+
builder = builder.plugin(build_si_plugin());
70+
builder
4471
.on_window_event(move |app, event| window_event_handler(app, event))
45-
.plugin(build_si_plugin())
4672
.plugin(tauri_plugin_deep_link::init())
4773
.plugin(tauri_plugin_dialog::init())
4874
.plugin(tauri_plugin_fs::init())

src-tauri/src/plugins.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ use tauri_plugin_single_instance::init;
1212
pub fn build_si_plugin<R: Runtime>() -> TauriPlugin<R> {
1313
init(move |app, argv, _cwd| {
1414
// 把 argv 转成 Vec<String>
15-
let args: Vec<String> = argv.iter().map(|s| s.to_string()).collect();
15+
// let args: Vec<String> = argv.iter().map(|s| s.to_string()).collect();
1616

1717
// 如果包含提升约定参数,发出专门事件并短路退出
18-
if args.iter().any(|a| a == "--elevated") {
19-
// 提升实例通常只负责传参或执行一次性任务,退出避免与主实例冲突
20-
std::process::exit(0);
21-
}
18+
// if args.iter().any(|a| a == "--elevated") {
19+
// 提升实例通常只负责传参或执行一次性任务,退出避免与主实例冲突
20+
// std::process::exit(0);
21+
// }
2222

2323
// 非提升启动:按原逻辑广播 deep link
2424
if let Err(e) = app.emit("active_deep_link", argv) {

0 commit comments

Comments
 (0)