1125-2142
This commit is contained in:
14
src-tauri/src/lib.rs
Normal file
14
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
360
src-tauri/src/main.rs
Normal file
360
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,360 @@
|
||||
// 全局允许非标准命名风格 (因为 WMI 结构体必须匹配 Windows API 的命名)
|
||||
#![allow(non_camel_case_types, non_snake_case)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use serde::Serialize;
|
||||
use sysinfo::{System, Disks};
|
||||
use wmi::{COMLibrary, WMIConnection};
|
||||
use std::fs;
|
||||
// 引入 chrono 用于时间格式化
|
||||
use chrono::{FixedOffset, Local, NaiveDate, TimeZone};
|
||||
|
||||
// --- 1. 数据结构 ---
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SystemHealthReport {
|
||||
hardware: HardwareSummary,
|
||||
storage: Vec<StorageDevice>,
|
||||
events: Vec<SystemEvent>,
|
||||
minidumps: MinidumpInfo,
|
||||
drivers: Vec<DriverIssue>,
|
||||
battery: Option<BatteryInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HardwareSummary {
|
||||
cpu_name: String,
|
||||
|
||||
// [新增] 1. 整机信息 (适合品牌机/笔记本)
|
||||
sys_vendor: String,
|
||||
sys_product: String,
|
||||
|
||||
// [新增] 2. 主板信息 (适合 DIY 组装机)
|
||||
mobo_vendor: String,
|
||||
mobo_product: String,
|
||||
|
||||
memory_total_gb: u64,
|
||||
memory_used_gb: u64,
|
||||
os_version: String,
|
||||
bios_version: String,
|
||||
c_drive_total_gb: u64,
|
||||
c_drive_used_gb: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StorageDevice {
|
||||
model: String,
|
||||
health_status: String,
|
||||
human_explanation: String,
|
||||
is_danger: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SystemEvent {
|
||||
time_generated: String,
|
||||
event_id: u32,
|
||||
source: String,
|
||||
analysis_hint: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MinidumpInfo {
|
||||
found: bool,
|
||||
count: usize,
|
||||
explanation: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DriverIssue {
|
||||
device_name: String,
|
||||
error_code: u32,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BatteryInfo {
|
||||
health_percentage: u32,
|
||||
is_ac_connected: bool,
|
||||
explanation: String,
|
||||
}
|
||||
|
||||
// --- WMI 反序列化结构 ---
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_DiskDrive {
|
||||
Model: Option<String>,
|
||||
Status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_NTLogEvent {
|
||||
TimeGenerated: String,
|
||||
EventCode: u32,
|
||||
SourceName: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_PnPEntity {
|
||||
Name: Option<String>,
|
||||
ConfigManagerErrorCode: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_Battery {
|
||||
DesignCapacity: Option<u32>,
|
||||
FullChargeCapacity: Option<u32>,
|
||||
BatteryStatus: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_BIOS {
|
||||
SMBIOSBIOSVersion: Option<String>,
|
||||
}
|
||||
|
||||
// 主板 WMI 结构 (DIY看这个)
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_BaseBoard {
|
||||
Manufacturer: Option<String>,
|
||||
Product: Option<String>,
|
||||
}
|
||||
|
||||
// [新增] 整机 WMI 结构 (品牌机看这个)
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_ComputerSystem {
|
||||
Manufacturer: Option<String>,
|
||||
Model: Option<String>,
|
||||
}
|
||||
|
||||
// --- 辅助函数:格式化 WMI 时间 ---
|
||||
fn format_wmi_time(wmi_str: &str) -> String {
|
||||
if wmi_str.len() < 25 {
|
||||
return wmi_str.to_string();
|
||||
}
|
||||
|
||||
let year = wmi_str[0..4].parse::<i32>().unwrap_or(1970);
|
||||
let month = wmi_str[4..6].parse::<u32>().unwrap_or(1);
|
||||
let day = wmi_str[6..8].parse::<u32>().unwrap_or(1);
|
||||
let hour = wmi_str[8..10].parse::<u32>().unwrap_or(0);
|
||||
let min = wmi_str[10..12].parse::<u32>().unwrap_or(0);
|
||||
let sec = wmi_str[12..14].parse::<u32>().unwrap_or(0);
|
||||
|
||||
let sign = &wmi_str[21..22];
|
||||
let offset_val = wmi_str[22..25].parse::<i32>().unwrap_or(0);
|
||||
let offset_mins = if sign == "-" { -offset_val } else { offset_val };
|
||||
|
||||
let offset = FixedOffset::east_opt(offset_mins * 60).unwrap_or(FixedOffset::east_opt(0).unwrap());
|
||||
|
||||
let naive_date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap());
|
||||
let naive_dt = naive_date.and_hms_opt(hour, min, sec).unwrap_or_default();
|
||||
|
||||
match offset.from_local_datetime(&naive_dt).single() {
|
||||
Some(dt) => {
|
||||
dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
},
|
||||
None => format!("{}-{:02}-{:02} {:02}:{:02}:{:02}", year, month, day, hour, min, sec)
|
||||
}
|
||||
}
|
||||
|
||||
// --- 核心逻辑 ---
|
||||
|
||||
#[tauri::command]
|
||||
async fn run_diagnosis() -> Result<SystemHealthReport, String> {
|
||||
let report = tokio::task::spawn_blocking(|| {
|
||||
let mut sys = System::new_all();
|
||||
sys.refresh_all();
|
||||
|
||||
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
||||
|
||||
// 1. 硬件基础信息
|
||||
let mut bios_ver = "Unknown".to_string();
|
||||
let mut mobo_vendor = "Unknown".to_string();
|
||||
let mut mobo_product = "Unknown".to_string();
|
||||
let mut sys_vendor = "Unknown".to_string();
|
||||
let mut sys_product = "Unknown".to_string();
|
||||
|
||||
if let Some(con) = &wmi_con {
|
||||
// 获取 BIOS 版本
|
||||
if let Ok(results) = con.raw_query::<Win32_BIOS>("SELECT SMBIOSBIOSVersion FROM Win32_BIOS") {
|
||||
if let Some(bios) = results.first() {
|
||||
bios_ver = bios.SMBIOSBIOSVersion.clone().unwrap_or("Unknown".to_string());
|
||||
}
|
||||
}
|
||||
// 获取主板信息 (BaseBoard)
|
||||
if let Ok(results) = con.raw_query::<Win32_BaseBoard>("SELECT Manufacturer, Product FROM Win32_BaseBoard") {
|
||||
if let Some(board) = results.first() {
|
||||
mobo_vendor = board.Manufacturer.clone().unwrap_or("Unknown".to_string());
|
||||
mobo_product = board.Product.clone().unwrap_or("Unknown".to_string());
|
||||
}
|
||||
}
|
||||
// [新增] 获取整机信息 (ComputerSystem)
|
||||
if let Ok(results) = con.raw_query::<Win32_ComputerSystem>("SELECT Manufacturer, Model FROM Win32_ComputerSystem") {
|
||||
if let Some(cs) = results.first() {
|
||||
sys_vendor = cs.Manufacturer.clone().unwrap_or("Unknown".to_string());
|
||||
sys_product = cs.Model.clone().unwrap_or("Unknown".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- C 盘空间检查 ---
|
||||
let mut c_total = 0u64;
|
||||
let mut c_used = 0u64;
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
for disk in &disks {
|
||||
if disk.mount_point().to_string_lossy().starts_with("C:") {
|
||||
c_total = disk.total_space() / 1024 / 1024 / 1024;
|
||||
let free = disk.available_space() / 1024 / 1024 / 1024;
|
||||
c_used = c_total - free;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let cpu_brand = if let Some(cpu) = sys.cpus().first() {
|
||||
cpu.brand().trim().to_string()
|
||||
} else {
|
||||
"Unknown CPU".to_string()
|
||||
};
|
||||
|
||||
let hardware = HardwareSummary {
|
||||
cpu_name: cpu_brand,
|
||||
mobo_vendor,
|
||||
mobo_product,
|
||||
// [新增] 填入整机数据
|
||||
sys_vendor,
|
||||
sys_product,
|
||||
memory_total_gb: sys.total_memory() / 1024 / 1024 / 1024,
|
||||
memory_used_gb: sys.used_memory() / 1024 / 1024 / 1024,
|
||||
os_version: System::long_os_version().unwrap_or("Unknown".to_string()),
|
||||
bios_version: bios_ver,
|
||||
c_drive_total_gb: c_total,
|
||||
c_drive_used_gb: c_used,
|
||||
};
|
||||
|
||||
// 2. 存储设备健康度
|
||||
let mut storage = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
let query = "SELECT Model, Status FROM Win32_DiskDrive";
|
||||
if let Ok(results) = con.raw_query::<Win32_DiskDrive>(query) {
|
||||
for disk in results {
|
||||
let status = disk.Status.unwrap_or("Unknown".to_string());
|
||||
let (explanation, is_danger) = match status.as_str() {
|
||||
"OK" => ("健康".to_string(), false),
|
||||
"Pred Fail" => ("预测即将损坏".to_string(), true),
|
||||
_ => ("状态异常".to_string(), true),
|
||||
};
|
||||
storage.push(StorageDevice {
|
||||
model: disk.Model.unwrap_or("Generic Disk".to_string()),
|
||||
health_status: status,
|
||||
human_explanation: explanation,
|
||||
is_danger,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 关键日志
|
||||
let mut events = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
let query = "SELECT TimeGenerated, EventCode, SourceName FROM Win32_NTLogEvent WHERE Logfile = 'System' AND (EventCode = 41 OR EventCode = 18 OR EventCode = 19)";
|
||||
if let Ok(mut results) = con.raw_query::<Win32_NTLogEvent>(query) {
|
||||
results.truncate(5);
|
||||
for event in results {
|
||||
let hint = match event.EventCode {
|
||||
41 => "系统意外断电 (电源/强关)",
|
||||
18 | 19 => "WHEA 硬件致命错误 (CPU/超频)",
|
||||
_ => "系统关键错误",
|
||||
};
|
||||
events.push(SystemEvent {
|
||||
time_generated: format_wmi_time(&event.TimeGenerated),
|
||||
event_id: event.EventCode,
|
||||
source: event.SourceName,
|
||||
analysis_hint: hint.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Minidump
|
||||
let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() };
|
||||
if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") {
|
||||
let count = entries.count();
|
||||
if count > 0 {
|
||||
minidump = MinidumpInfo {
|
||||
found: true,
|
||||
count,
|
||||
explanation: format!("发现 {} 次蓝屏崩溃", count),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 驱动设备检查
|
||||
let mut driver_issues = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
let query = "SELECT Name, ConfigManagerErrorCode FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0";
|
||||
if let Ok(results) = con.raw_query::<Win32_PnPEntity>(query) {
|
||||
for dev in results {
|
||||
let code = dev.ConfigManagerErrorCode.unwrap_or(0);
|
||||
let desc = match code {
|
||||
10 => "设备无法启动 (Code 10)。通常是驱动不兼容。",
|
||||
28 => "驱动程序未安装 (Code 28)。",
|
||||
43 => "硬件报告问题已被停止 (Code 43)。显卡常见,可能虚焊。",
|
||||
_ => "设备状态异常。",
|
||||
};
|
||||
driver_issues.push(DriverIssue {
|
||||
device_name: dev.Name.unwrap_or("未知设备".to_string()),
|
||||
error_code: code,
|
||||
description: desc.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 电池健康度
|
||||
let mut battery_info = None;
|
||||
if let Some(con) = &wmi_con {
|
||||
if let Ok(results) = con.raw_query::<Win32_Battery>("SELECT DesignCapacity, FullChargeCapacity, BatteryStatus FROM Win32_Battery") {
|
||||
if let Some(bat) = results.first() {
|
||||
let design = bat.DesignCapacity.unwrap_or(0);
|
||||
let full = bat.FullChargeCapacity.unwrap_or(0);
|
||||
let status = bat.BatteryStatus.unwrap_or(0);
|
||||
|
||||
if design > 0 {
|
||||
let health = ((full as f64 / design as f64) * 100.0) as u32;
|
||||
let ac_plugged = status == 2 || status == 6 || status == 1;
|
||||
|
||||
let explain = if health < 60 {
|
||||
"电池老化严重,建议更换,否则可能导致供电不稳。".to_string()
|
||||
} else {
|
||||
"电池状态良好。".to_string()
|
||||
};
|
||||
|
||||
battery_info = Some(BatteryInfo {
|
||||
health_percentage: health,
|
||||
is_ac_connected: ac_plugged,
|
||||
explanation: explain,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SystemHealthReport {
|
||||
hardware,
|
||||
storage,
|
||||
events,
|
||||
minidumps: minidump,
|
||||
drivers: driver_issues,
|
||||
battery: battery_info
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![run_diagnosis])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
Reference in New Issue
Block a user