【IT】Rust实现win下系统信息采集工具的开发

一、 项目目标

编写一个 Windows 客户端程序,双击运行后静默生成 设备信息登记表.xlsx,自动提取硬件信息,并通过红色底纹提示使用者需要人工补充或排查自动识别失败的数据。

技术栈: Rust (2021 Edition) + sysinfo + wmi + rust_xlsxwriter

兼容性: Win10 / Win11 (64位)


二、 项目目录结构

text 复制代码
system_info_excel/
├── Cargo.toml            # 项目依赖配置文件
├── src/
│   └── main.rs           # 核心逻辑代码
└── target/               # (编译后自动生成,包含 release 版本的 .exe)

三、 依赖配置文件 (Cargo.toml)

toml 复制代码
[package]
name = "system_info_excel"
version = "1.0.0"
edition = "2021"

[dependencies]
sysinfo = "0.30"          # 获取 CPU、内存、网络、操作系统
wmi = "0.13"              # 获取底层硬件信息(如硬盘序列号、显卡、SN)
serde = { version = "1.0", features = ["derive"] } # wmi 的反序列化依赖
rust_xlsxwriter = "0.68"  # 生成 Excel 表格

四、 核心逻辑代码 (src/main.rs)

rust 复制代码
// 隐藏控制台黑框,双击直接运行
#![windows_subsystem = "windows"]

use rust_xlsxwriter::{Color, Format, FormatAlign, Workbook};
use serde::Deserialize;
use std::fs;
use sysinfo::{CpuExt, Networks, System, SystemExt};

// ================= WMI 数据结构定义 =================

#[derive(Deserialize)]
struct Win32_ComputerSystem {
    Manufacturer: String,
    Model: String,
}

#[derive(Deserialize)]
struct Win32_BIOS {
    SerialNumber: Option<String>,
}

#[derive(Deserialize)]
struct Win32_PhysicalMedia {
    SerialNumber: Option<String>,
}

#[derive(Deserialize)]
struct Win32_DiskDrive {
    Size: Option<u64>,
}

#[derive(Deserialize)]
struct Win32_VideoController {
    Name: String,
}

// ================= 错误处理辅助函数 =================
fn save_error_to_file(msg: &str) {
    let _ = fs::write("System_Info_Error.log", msg);
}

// ================= 核心主函数 =================
fn main() {
    let mut sys = System::new_all();
    sys.refresh_all();
    let networks = Networks::new_with_refreshed_list();

    let wmi_con = match WMIConnection::new() {
        Ok(con) => con,
        Err(e) => {
            save_error_to_file(&format!("WMI 连接失败: {:?}", e));
            return;
        }
    };

    // 1. 获取品牌及型号
    let brand_model = match wmi_con.query::<Vec<Win32_ComputerSystem>>() {
        Ok(data) if !data.is_empty() => format!("{} {}", data[0].Manufacturer, data[0].Model),
        _ => "未知".to_string(),
    };

    // 2. 获取设备SN(放最后一列)
    let device_sn = match wmi_con.query::<Vec<Win32_BIOS>>() {
        Ok(data) if !data.is_empty() => {
            data[0].SerialNumber.clone().unwrap_or_else(|| "未知".to_string())
        }
        _ => "未知".to_string(),
    };

    // 3. 获取 CPU
    let cpu = sys.cpus().first().map(|c| c.brand().trim().to_string()).unwrap_or_else(|| "未知".to_string());

    // 4. 获取内存 (GB)
    let total_ram_gb = (sys.total_memory() as f64) / 1024.0 / 1024.0;
    let ram = format!("{:.1} GB", total_ram_gb);

    // 5. 获取显卡 (多个显卡用 + 隔离)
    let gpu = match wmi_con.query::<Vec<Win32_VideoController>>() {
        Ok(gpus) if !gpus.is_empty() => {
            gpus.iter().map(|g| g.Name.clone()).collect::<Vec<String>>().join(" + ")
        }
        _ => "未知".to_string(),
    };

    // 6. 获取硬盘容量 (多个用 + 隔离)
    let disk_cap = match wmi_con.query::<Vec<Win32_DiskDrive>>() {
        Ok(disks) if !disks.is_empty() => {
            disks.iter()
                .map(|d| {
                    let size_gb = d.Size.unwrap_or(0) as f64 / 1_073_741_824.0;
                    format!("{:.1} GB", size_gb)
                })
                .collect::<Vec<String>>()
                .join(" + ")
        }
        _ => "未知".to_string(),
    };

    // 7. 获取硬盘序列号 (多个用 换行符 隔离)
    let disk_serial = match wmi_con.query::<Vec<Win32_PhysicalMedia>>() {
        Ok(media) if !media.is_empty() => {
            let serials: Vec<String> = media
                .iter()
                .filter_map(|m| m.SerialNumber.clone())
                .map(|s| s.replace('*', "").trim().to_string())
                .filter(|s| !s.is_empty())
                .collect();
            if serials.is_empty() {
                "未知".to_string()
            } else {
                serials.join("\n")
            }
        }
        _ => "未知".to_string(),
    };

    // 8. 获取 MAC 地址
    let mut mac_list: Vec<String> = Vec::new();
    for (name, data) in &networks {
        let mac = data.mac_address().to_string();
        if mac == "00:00:00:00:00:00" || name.to_lowercase().contains("loopback") {
            continue;
        }
        let formatted_mac = mac.replace(':', "-").to_uppercase();
        let lower_name = name.to_lowercase();

        // 判断网卡类型加中文后缀
        let entry = if lower_name.contains("wi-fi") || lower_name.contains("wlan") || lower_name.contains("wireless") {
            format!("{} 无线", formatted_mac)
        } else if lower_name.contains("bluetooth") || lower_name.contains("蓝牙") {
            format!("{} 蓝牙", formatted_mac)
        } else if lower_name.contains("tap") || lower_name.contains("virtual") || lower_name.contains("tunnel") {
            continue; // 排除虚拟网卡
        } else {
            formatted_mac
        };
        mac_list.push(entry);
    }
    let mac_addr = if mac_list.is_empty() { "未知".to_string() } else { mac_list.join("\n") };

    // 9. 获取操作系统及版本
    let os_version = sys.long_os_version().unwrap_or_else(|| "未知".to_string());

    // ================= 生成 Excel =================
    let mut workbook = Workbook::new();

    // 普通表头格式
    let header_format = Format::new().set_bold().set_align(FormatAlign::Center);
    
    // 红色提醒格式(用于人工填写列,或者自动识别失败的值)
    let red_alert_format = Format::new()
        .set_align(FormatAlign::Center)
        .set_background_color(Color::RGB(0xFFC7CE)) // 浅红色背景
        .set_font_color(Color::RGB(0x9C0006));       // 深红色字体

    let worksheet = workbook.add_worksheet();

    // 完整表头(20列)
    let headers = [
        "序号", "设备编号", "设备名称", "部门", "责任人", 
        "品牌及型号", "CPU", "内存", "显卡", "硬盘容量", "硬盘序列号", 
        "MAC地址", "操作系统及版本", "操作系统安装时间", "密级", "用途", 
        "放置地点", "使用情况", "购机日期(启用日期)", "设备SN"
    ];

    // 写入表头
    for (col, header) in headers.iter().enumerate() {
        let _ = worksheet.write_with_format(0, col as u16, *header, &header_format);
    }

    // 辅助函数:判断如果数据是"未知"或空,则标红提示
    fn write_with_red_if_fail(
        worksheet: &rust_xlsxwriter::Worksheet,
        row: u32,
        col: u16,
        value: &str,
        red_format: &Format,
    ) {
        if value.is_empty() || value == "未知" {
            let _ = worksheet.write_with_format(row, col, value, red_format);
        } else {
            let _ = worksheet.write(row, col, value);
        }
    }

    // 填写数据行
    let _ = worksheet.write(1, 0, 1); // 序号默认填1

    // 自动填入列(识别失败则套用红色格式)
    write_with_red_if_fail(&worksheet, 1, 5, &brand_model, &red_alert_format);  // 品牌及型号
    write_with_red_if_fail(&worksheet, 1, 6, &cpu, &red_alert_format);          // CPU
    write_with_red_if_fail(&worksheet, 1, 7, &ram, &red_alert_format);          // 内存
    write_with_red_if_fail(&worksheet, 1, 8, &gpu, &red_alert_format);          // 显卡
    write_with_red_if_fail(&worksheet, 1, 9, &disk_cap, &red_alert_format);     // 硬盘容量
    write_with_red_if_fail(&worksheet, 1, 10, &disk_serial, &red_alert_format); // 硬盘序列号
    write_with_red_if_fail(&worksheet, 1, 11, &mac_addr, &red_alert_format);    // MAC地址
    write_with_red_if_fail(&worksheet, 1, 12, &os_version, &red_alert_format);  // 操作系统及版本
    write_with_red_if_fail(&worksheet, 1, 19, &device_sn, &red_alert_format);   // 设备SN放最后

    // 默认填入固定值的字段
    let _ = worksheet.write(1, 13, "-");       // 操作系统安装时间
    let _ = worksheet.write(1, 14, "互联网");    // 密级
    let _ = worksheet.write(1, 15, "办公");     // 用途
    let _ = worksheet.write(1, 17, "在用");     // 使用情况

    // 必须人工填写的列,保持空字符串,并强制加红底提醒(设备编号、设备名称、部门、责任人、放置地点、购机日期)
    for col in [1, 2, 3, 4, 16, 18] {
        let _ = worksheet.write_with_format(1, col, "", &red_alert_format);
    }

    // 设置列宽,优化阅读体验
    let _ = worksheet.set_column_width(0, 6);   // 序号
    let _ = worksheet.set_column_width(1, 12);  // 设备编号
    let _ = worksheet.set_column_width(2, 15);  // 设备名称
    let _ = worksheet.set_column_width(3, 12);  // 部门
    let _ = worksheet.set_column_width(4, 12);  // 责任人
    let _ = worksheet.set_column_width(5, 25);  // 品牌及型号
    let _ = worksheet.set_column_width(6, 30);  // CPU
    let _ = worksheet.set_column_width(7, 15);  // 内存
    let _ = worksheet.set_column_width(8, 35);  // 显卡
    let _ = worksheet.set_column_width(9, 25);  // 硬盘容量
    let _ = worksheet.set_column_width(10, 35); // 硬盘序列号
    let _ = worksheet.set_column_width(11, 35); // MAC地址
    let _ = worksheet.set_column_width(12, 30); // 操作系统及版本
    let _ = worksheet.set_column_width(13, 20); // 操作系统安装时间
    let _ = worksheet.set_column_width(14, 10); // 密级
    let _ = worksheet.set_column_width(15, 15); // 用途
    let _ = worksheet.set_column_width(16, 20); // 放置地点
    let _ = worksheet.set_column_width(17, 15); // 使用情况
    let _ = worksheet.set_column_width(18, 25); // 购机日期
    let _ = worksheet.set_column_width(19, 25); // 设备SN

    // 保存文件
    if let Err(e) = workbook.save("设备信息登记表.xlsx") {
        save_error_to_file(&format!("保存 Excel 失败: {:?}", e));
    }
}

五、 编译与使用说明

  1. 环境准备 :确保电脑安装了 Rust 开发环境(rustup),并且安装了 Visual Studio 2022 的 C++ 桌面开发环境(用于 MSVC 链接器)。
  2. 创建项目 :在终端运行 cargo new system_info_excel 并进入目录。
  3. 替换文件 :将上面的代码分别填入 Cargo.tomlsrc/main.rs
  4. 编译 :运行 cargo build --release
  5. 分发 :在 target/release/ 目录下找到 system_info_excel.exe,把这个文件发给使用 Win10/11 的同事。同事双击后,同一个目录下会直接生成填好硬件信息的 设备信息登记表.xlsx。如果运行中遇到 WMI 权限问题或提取失败,会自动生成 System_Info_Error.log 日志文件供排查。
相关推荐
MC皮蛋侠客13 小时前
Tauri 2.x 系列(八):与其他语言结合——Sidecar、服务、FFI 与插件的选择
rust·tauri
MC皮蛋侠客14 小时前
Tauri 2.x 系列(五):调用系统 API——官方插件、Rust crate 与原生能力
开发语言·后端·rust
磐链科技19 小时前
钱包开发中的跨平台架构:Flutter与Rust构建高性能移动端钱包
flutter·架构·rust
MC皮蛋侠客21 小时前
Tauri 2.x 系列(四):窗口、菜单、托盘与应用生命周期——做出真正的桌面体验
rust·tauri
MC皮蛋侠客21 小时前
Tauri 2.x 系列(一):架构全景与最小闭环——从 WebView 到 EMS 后台
架构·rust·tauri
今天AI了吗1 天前
从“金鱼脑”到“大象记忆”:AI Agent 短期记忆与长期记忆的存储与检索全解
数据库·人工智能·python·sql·rust
MC皮蛋侠客1 天前
Tauri 2.x 系列(二):项目结构与前端框架——Vue、React 如何成为桌面前台
rust·tauri