This commit is contained in:
Julian Freeman
2025-11-26 09:04:52 -04:00
parent 4fc501f941
commit 19b18d091a
4 changed files with 886 additions and 486 deletions

View File

@@ -8,13 +8,15 @@ use serde::Serialize;
use sysinfo::{System, Disks};
use wmi::{COMLibrary, WMIConnection};
use std::fs;
// 引入 tauri::Emitter 用于发送事件 (Tauri v2)
use std::path::Path;
use tauri::Emitter;
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone};
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
// [修改] 只引入 minidump 基础库,移除 processor 依赖以避免 API 冲突
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
// --- 1. 数据结构 (保持不变,用于序列化部分数据) ---
// --- 1. 数据结构 (保持不变) ---
#[derive(Serialize, Clone)] // 增加 Clone trait 方便使用
#[derive(Serialize, Clone)]
struct HardwareSummary {
cpu_name: String,
sys_vendor: String,
@@ -67,51 +69,39 @@ struct BatteryInfo {
explanation: String,
}
#[derive(Serialize, Clone)]
struct BsodFileItem {
filename: String,
path: String,
size_kb: u64,
created_time: String,
}
#[derive(Serialize, Clone)]
struct BsodAnalysisReport {
crash_reason: String,
crash_address: String,
bug_check_code: String,
crashing_thread: Option<String>,
human_analysis: String,
recommendation: String,
}
// --- WMI 反序列化结构 ---
#[derive(serde::Deserialize, Debug)]
struct Win32_DiskDrive {
Model: Option<String>,
Status: Option<String>,
}
struct Win32_DiskDrive { Model: Option<String>, Status: Option<String> }
#[derive(serde::Deserialize, Debug)]
struct Win32_NTLogEvent {
TimeGenerated: String,
EventCode: u32,
SourceName: String,
Message: Option<String>,
}
struct Win32_NTLogEvent { TimeGenerated: String, EventCode: u32, SourceName: String, Message: Option<String> }
#[derive(serde::Deserialize, Debug)]
struct Win32_PnPEntity {
Name: Option<String>,
ConfigManagerErrorCode: Option<u32>,
}
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>,
}
struct Win32_Battery { DesignCapacity: Option<u32>, FullChargeCapacity: Option<u32>, BatteryStatus: Option<u16> }
#[derive(serde::Deserialize, Debug)]
struct Win32_BIOS {
SMBIOSBIOSVersion: Option<String>,
}
struct Win32_BIOS { SMBIOSBIOSVersion: Option<String> }
#[derive(serde::Deserialize, Debug)]
struct Win32_BaseBoard {
Manufacturer: Option<String>,
Product: Option<String>,
}
struct Win32_BaseBoard { Manufacturer: Option<String>, Product: Option<String> }
#[derive(serde::Deserialize, Debug)]
struct Win32_ComputerSystem {
Manufacturer: Option<String>,
Model: Option<String>,
}
struct Win32_ComputerSystem { Manufacturer: Option<String>, Model: Option<String> }
// --- 辅助函数 ---
fn get_wmi_query_time(days_ago: i64) -> String {
@@ -120,9 +110,7 @@ fn get_wmi_query_time(days_ago: i64) -> String {
}
fn format_wmi_time(wmi_str: &str) -> String {
if wmi_str.len() < 25 {
return wmi_str.to_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);
@@ -141,14 +129,146 @@ fn format_wmi_time(wmi_str: &str) -> String {
}
}
// --- 核心逻辑:分步流式传输 ---
// --- 新增功能:翻译蓝屏代码为人话 (手动映射常见代码) ---
fn translate_bugcheck_u32(code: u32) -> (String, String) {
// 这些是 Windows 最常见的 BSOD 代码
match code {
0x000000D1 => (
"DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)".to_string(),
"驱动程序使用了不正确的内存地址。通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等),尝试回滚或更新驱动。"
.to_string(),
),
0x0000007E | 0x1000007E => (
"SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (0x7E)".to_string(),
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容或者BIOS设置问题。建议重装显卡驱动或恢复BIOS默认设置。"
.to_string(),
),
0x0000001A => (
"MEMORY_MANAGEMENT (0x1A)".to_string(),
"严重的内存管理错误。高度怀疑内存条物理故障。建议立即运行 Windows 内存诊断工具,或者尝试拔插内存条。"
.to_string(),
),
0x000000EF => (
"CRITICAL_PROCESS_DIED (0xEF)".to_string(),
"Windows 核心进程意外终止。通常是系统文件损坏或硬盘故障。建议运行 'sfc /scannow' 修复系统,并检查硬盘健康度。"
.to_string(),
),
0x00000124 => (
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足、超频失败、过热,或者主板/PCIe设备如 NVMe 硬盘)故障。"
.to_string(),
),
0x00000116 => (
"VIDEO_TDR_FAILURE (0x116)".to_string(),
"显卡响应超时。显卡驱动崩溃或显卡过热。如果你在玩游戏,可能是显卡超频不稳定或散热硅脂干了。"
.to_string(),
),
0x00000050 => (
"PAGE_FAULT_IN_NONPAGED_AREA (0x50)".to_string(),
"试图访问无效的内存地址。可能是内存条故障,或者是防病毒软件/驱动程序冲突。建议检查内存。"
.to_string(),
),
0x0000000A => (
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序或硬件兼容性问题引起。"
.to_string(),
),
0x0000003B => (
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
"系统服务执行异常。通常与图形驱动程序或过时的系统文件有关。"
.to_string(),
),
0x00000133 => (
"DPC_WATCHDOG_VIOLATION (0x133)".to_string(),
"DPC 看门狗超时。通常是 SSD 固件过旧或无线网卡驱动冲突导致系统卡死时间过长。"
.to_string(),
),
_ => (
format!("未知错误代码: 0x{:X}", code),
"请尝试在搜索引擎中搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
),
}
}
// --- 命令:列出 Minidump 文件 ---
#[tauri::command]
async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
let path = Path::new("C:\\Windows\\Minidump");
let mut files = Vec::new();
if path.exists() {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries {
if let Ok(entry) = entry {
// [修复] 使用 .as_ref() 防止 metadata 所有权被移动
let metadata = entry.metadata().ok();
let created = metadata.as_ref()
.and_then(|m| m.modified().ok()) // 通常用修改时间
.map(|t| {
let dt: DateTime<Local> = t.into();
dt.format("%Y-%m-%d %H:%M:%S").to_string()
})
.unwrap_or("Unknown".to_string());
// [修复] 使用 .as_ref() 再次访问 metadata
let size = metadata.as_ref().map(|m| m.len() / 1024).unwrap_or(0);
files.push(BsodFileItem {
filename: entry.file_name().to_string_lossy().to_string(),
path: entry.path().to_string_lossy().to_string(),
size_kb: size,
created_time: created,
});
}
}
}
}
// 按时间倒序排列
files.sort_by(|a, b| b.created_time.cmp(&a.created_time));
Ok(files)
}
// --- 命令:分析指定的 Minidump 文件 ---
#[tauri::command]
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
let path = Path::new(&filepath);
// 1. 读取文件 (使用基础 minidump 库)
let dump = Minidump::read_path(path).map_err(|e| format!("无法读取文件: {}", e))?;
// 2. 直接获取异常流 (Exception Stream)
let exception_stream = dump.get_stream::<MinidumpException>()
.map_err(|_| "无法找到异常信息流 (No Exception Stream)".to_string())?;
// [修复] 使用 .raw 访问内部原始结构
let exception_code = exception_stream.raw.exception_record.exception_code;
let exception_address = exception_stream.raw.exception_record.exception_address;
// 3. 尝试获取系统信息 (OS Version)
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
// [修复] 使用 .raw 访问 build_number
Ok(info) => format!("Windows Build {}", info.raw.build_number),
Err(_) => "Unknown OS".to_string(),
};
// 4. 翻译
let (reason_str, recommend) = translate_bugcheck_u32(exception_code);
Ok(BsodAnalysisReport {
crash_reason: reason_str,
crash_address: format!("0x{:X}", exception_address),
bug_check_code: format!("0x{:X} ({})", exception_code, sys_info_str),
crashing_thread: None, // 基础解析不包含线程栈回溯
human_analysis: "根据错误代码自动匹配的分析结果。".to_string(),
recommendation: recommend,
})
}
// --- 现有命令run_diagnosis (保持不变) ---
#[tauri::command]
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
// 使用 spawn 在后台线程运行,避免阻塞 Tauri 主线程
// 注意:这里不等待 join而是让它在后台跑通过 window.emit 发送进度
std::thread::spawn(move || {
// 1. 硬件概览 (最快)
// 1. 硬件概览
{
let mut sys = System::new();
sys.refresh_memory();
@@ -180,7 +300,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
}
// C盘
let mut c_total = 0u64;
let mut c_used = 0u64;
let disks = Disks::new_with_refreshed_list();
@@ -209,15 +328,12 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
c_drive_total_gb: c_total,
c_drive_used_gb: c_used,
};
// 发送硬件事件
let _ = window.emit("report-hardware", hardware);
}
// WMI 连接复用 (如果需要) 或者重新建立
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
// 2. 存储设备 (较快)
// 2. 存储设备
{
let mut storage = Vec::new();
if let Some(con) = &wmi_con {
@@ -242,7 +358,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
let _ = window.emit("report-storage", storage);
}
// 3. 驱动 (快)
// 3. 驱动
{
let mut driver_issues = Vec::new();
if let Some(con) = &wmi_con {
@@ -267,7 +383,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
let _ = window.emit("report-drivers", driver_issues);
}
// 4. Minidump (快)
// 4. Minidump
{
let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() };
if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") {
@@ -283,7 +399,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
let _ = window.emit("report-minidumps", minidump);
}
// 5. 电池 (快)
// 5. 电池
{
let mut battery_info = None;
if let Some(con) = &wmi_con {
@@ -309,12 +425,10 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
}
}
// 即使是 None 也要发,以便前端知道检查完成了
// 这里为了简化,直接发 Option
let _ = window.emit("report-battery", battery_info);
}
// 6. 日志 (最慢,放在最后)
// 6. 日志
{
let mut events = Vec::new();
if let Some(con) = &wmi_con {
@@ -366,7 +480,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![run_diagnosis])
.invoke_handler(tauri::generate_handler![run_diagnosis, list_minidumps, analyze_minidump])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}