feat(hardware): 自 go-hua 迁入硬件信息采集与变更检测

- WMI 采集主板/BIOS/显卡/内存/CPU/硬盘等 11 类设备 + 变更比对
- 新增依赖 gopsutil/yusufpapurcu/wmi, 附测试、例程与 README
This commit is contained in:
2026-09-20 12:29:36 +08:00
parent ef7cd3e3c4
commit 138c929bd4
7 changed files with 708 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# hardware
硬件信息采集(WMI)与硬件变更检测:主板/BIOS/显卡/内存/CPU/硬盘/声卡/网卡/
显示器/鼠标/键盘,一次 `GetHardwareInfo` 全量采集。
> 迁移自 go-hua/hardware,代码保持原样。Windows 走 WMI;Unix 侧为精简实现。
## 用法
```go
import "git.zeroonesoft.cn/golib/zogo/hardware"
// 全量采集(结构体字段按设备分组)
hw, err := hardware.GetHardwareInfo()
fmt.Println(len(hw.Processor), len(hw.PhysicalMemory), len(hw.DiskDrive))
// 单项采集
boards, err := hardware.GetBaseBoardInfo()
bios, err := hardware.GetBiosInfo()
disks, err := hardware.GetDiskDriveInfo()
// 变更检测(与上次采集结果比对,任一字段变化即视为变更)
changed := hardware.CheckBaseBoardChanged(oldBoards, newBoards)
```
完整可运行例程:[examples/hardware/main.go](../examples/hardware/main.go)
## 注意
- WMI 查询有秒级耗时,勿在请求热路径上全量采集;建议进程启动或低频定时任务使用。
- `CheckBaseBoardChanged` 按字段值比较,不比对顺序。
+297
View File
@@ -0,0 +1,297 @@
package hardware
// CheckBaseBoardChanged 检测主板是否发生变化(对比两次采集结果)
func CheckBaseBoardChanged(now []BaseBoard, newest []BaseBoard) bool {
if len(now) != len(newest) {
return true
}
for _, n1 := range now {
in := false
for _, n2 := range newest {
if n1.Product != n2.Product {
continue
}
if n1.Manufacturer != n2.Manufacturer {
continue
}
in = true
break
}
if !in {
return true
}
}
return false
}
// CheckBiosChanged 检测 BIOS 是否发生变化
func CheckBiosChanged(now []Bios, newest []Bios) bool {
if len(now) != len(newest) {
return true
}
for _, n1 := range now {
in := false
for _, n2 := range newest {
if n1.SMBIOSBIOSVersion != n2.SMBIOSBIOSVersion {
continue
}
in = true
break
}
if !in {
return true
}
}
return false
}
// CheckProcessorChanged 检测 CPU 是否发生变化(按名称对比)
func CheckProcessorChanged(now []Processor, newest []Processor) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Name != n2.Name {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
}
if !in {
return true
}
}
return false
}
// CheckPhysicalMemoryChanged 检测内存条是否发生变化(按容量对比)
func CheckPhysicalMemoryChanged(now []PhysicalMemory, newest []PhysicalMemory) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Capacity != n2.Capacity {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckVideoControllerChanged 检测显卡是否发生变化(按名称对比)
func CheckVideoControllerChanged(now []VideoController, newest []VideoController) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Name != n2.Name {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckSoundDeviceChanged 检测声卡是否发生变化(按名称对比)
func CheckSoundDeviceChanged(now []SoundDevice, newest []SoundDevice) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Name != n2.Name {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckNetworkAdapterChanged 检测网卡是否发生变化(按名称对比)
func CheckNetworkAdapterChanged(now []NetworkAdapter, newest []NetworkAdapter) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Name != n2.Name {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckDiskDriveChanged 检测硬盘是否发生变化(按型号对比)
func CheckDiskDriveChanged(now []DiskDrive, newest []DiskDrive) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Caption != n2.Caption {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckDesktopMonitorChanged 检测显示器是否发生变化(按 PNP 设备 ID 对比)
func CheckDesktopMonitorChanged(now []DesktopMonitor, newest []DesktopMonitor) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.PNPDeviceID != n2.PNPDeviceID {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckKeyboardChanged 检测键盘是否发生变化(按描述对比)
func CheckKeyboardChanged(now []Keyboard, newest []Keyboard) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Description != n2.Description {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
// CheckPointingDeviceChanged 检测指点设备是否发生变化(按描述对比)
func CheckPointingDeviceChanged(now []PointingDevice, newest []PointingDevice) bool {
if len(now) != len(newest) {
return true
}
// 有多个相同硬件时,顺序可能是乱序,对比过的放入索引列表中,防止重复对比
// 例如: now = [1,1,1] newest = [1,2,3] 一定要将已对比的排除掉
index := make(map[int]int)
for _, n1 := range now {
in := false
for i2, n2 := range newest {
if n1.Description != n2.Description {
continue
}
if _, ok := index[i2]; ok {
continue
}
in = true
index[i2] = i2
break
}
if !in {
return true
}
}
return false
}
+98
View File
@@ -0,0 +1,98 @@
package hardware
import "testing"
func TestCheckBaseBoardChanged(t *testing.T) {
now := []BaseBoard{{Product: "X570", Manufacturer: "ASUS"}}
if CheckBaseBoardChanged(now, []BaseBoard{{Product: "X570", Manufacturer: "ASUS"}}) {
t.Error("相同主板不应判定变更")
}
if !CheckBaseBoardChanged(now, []BaseBoard{{Product: "B550", Manufacturer: "ASUS"}}) {
t.Error("主板型号变化应判定变更")
}
if !CheckBaseBoardChanged(now, []BaseBoard{{Product: "X570", Manufacturer: "ASUS"}, {Product: "B550", Manufacturer: "MSI"}}) {
t.Error("数量变化应判定变更")
}
if !CheckBaseBoardChanged(now, nil) {
t.Error("数量变化应判定变更")
}
}
func TestCheckBiosChanged(t *testing.T) {
now := []Bios{{SMBIOSBIOSVersion: "1.0"}}
if CheckBiosChanged(now, []Bios{{SMBIOSBIOSVersion: "1.0"}}) {
t.Error("相同 BIOS 不应判定变更")
}
if !CheckBiosChanged(now, []Bios{{SMBIOSBIOSVersion: "2.0"}}) {
t.Error("BIOS 版本变化应判定变更")
}
}
func TestCheckProcessorChanged(t *testing.T) {
now := []Processor{{Name: "Ryzen 5800X"}}
if CheckProcessorChanged(now, []Processor{{Name: "Ryzen 5800X"}}) {
t.Error("相同 CPU 不应判定变更")
}
if !CheckProcessorChanged(now, []Processor{{Name: "Ryzen 5900X"}}) {
t.Error("CPU 型号变化应判定变更")
}
// 多个相同硬件乱序
now2 := []Processor{{Name: "A"}, {Name: "B"}}
if CheckProcessorChanged(now2, []Processor{{Name: "B"}, {Name: "A"}}) {
t.Error("乱序但集合相同不应判定变更")
}
// 重复硬件与新增硬件
if !CheckProcessorChanged([]Processor{{Name: "A"}, {Name: "A"}}, []Processor{{Name: "A"}, {Name: "B"}}) {
t.Error("重复硬件与不同硬件混合应判定变更")
}
}
func TestCheckPhysicalMemoryChanged(t *testing.T) {
now := []PhysicalMemory{{Capacity: 8589934592}}
if CheckPhysicalMemoryChanged(now, []PhysicalMemory{{Capacity: 8589934592}}) {
t.Error("相同容量不应判定变更")
}
if !CheckPhysicalMemoryChanged(now, []PhysicalMemory{{Capacity: 17179869184}}) {
t.Error("容量变化应判定变更")
}
}
func TestCheckVideoControllerChanged(t *testing.T) {
now := []VideoController{{Name: "RTX 3060"}}
if CheckVideoControllerChanged(now, []VideoController{{Name: "RTX 3060"}}) {
t.Error("相同显卡不应判定变更")
}
if !CheckVideoControllerChanged(now, []VideoController{{Name: "RTX 4060"}}) {
t.Error("显卡变化应判定变更")
}
}
func TestCheckDiskDriveChanged(t *testing.T) {
now := []DiskDrive{{Caption: "Samsung SSD 980"}}
if CheckDiskDriveChanged(now, []DiskDrive{{Caption: "Samsung SSD 980"}}) {
t.Error("相同硬盘不应判定变更")
}
if !CheckDiskDriveChanged(now, []DiskDrive{{Caption: "Samsung SSD 990"}}) {
t.Error("硬盘变化应判定变更")
}
}
func TestCheckSoundDeviceChanged(t *testing.T) {
now := []SoundDevice{{Name: "Realtek"}}
if CheckSoundDeviceChanged(now, []SoundDevice{{Name: "Realtek"}}) {
t.Error("相同声卡不应判定变更")
}
if !CheckSoundDeviceChanged(now, []SoundDevice{{Name: "Creative"}}) {
t.Error("声卡变化应判定变更")
}
}
func TestCheckNetworkAdapterChanged(t *testing.T) {
now := []NetworkAdapter{{Name: "Intel I219"}}
if CheckNetworkAdapterChanged(now, []NetworkAdapter{{Name: "Intel I219"}}) {
t.Error("相同网卡不应判定变更")
}
if !CheckNetworkAdapterChanged(now, []NetworkAdapter{{Name: "Realtek 8168"}}) {
t.Error("网卡变化应判定变更")
}
}
+94
View File
@@ -0,0 +1,94 @@
// Package hardware 提供硬件信息采集(WMI)与硬件变更检测(Windows)。
package hardware
// BaseBoard 主板信息(WMI Win32_BaseBoard)
type BaseBoard struct {
Manufacturer string `json:"manufacturer"` //制造商
Product string `json:"product"` //产品
}
// Bios BIOS 信息(WMI Win32_BIOS)
type Bios struct {
SMBIOSBIOSVersion string `json:"SMBIOSBIOSVersion"`
Name string `json:"name"`
}
// VideoController 显卡信息(WMI Win32_VideoController)
type VideoController struct {
Name string `json:"name"`
AdapterRAM uint32 `json:"adapterRAM"`
PNPDeviceID string `json:"PNPDeviceID"`
CurrentHorizontalResolution uint32 `json:"currentHorizontalResolution"`
CurrentVerticalResolution uint32 `json:"currentVerticalResolution"`
CurrentRefreshRate uint32 `json:"currentRefreshRate"`
}
// PhysicalMemory 内存条信息(WMI Win32_PhysicalMemory)
type PhysicalMemory struct {
Manufacturer string `json:"manufacturer"` //制造商
Capacity uint64 `json:"capacity"` //容量
Speed uint32 `json:"speed"` //速率
SerialNumber string `json:"serialNumber"` //序列号
}
// Processor CPU 信息(WMI Win32_Processor)
type Processor struct {
Name string `json:"name"`
NumberOfCores uint32 `json:"numberOfCores"`
NumberOfLogicalProcessors uint32 `json:"numberOfLogicalProcessors"`
}
// DiskDrive 硬盘信息(WMI Win32_DiskDrive)
type DiskDrive struct {
Caption string `json:"caption"`
Size uint64 `json:"size"`
InterfaceType string `json:"interfaceType"`
}
// SoundDevice 声卡信息(WMI Win32_SoundDevice)
type SoundDevice struct {
Name string `json:"name"`
}
// NetworkAdapter 网卡信息(WMI Win32_NetworkAdapter)
type NetworkAdapter struct {
Name string `json:"name"`
Speed uint64 `json:"speed"`
MACAddress string `json:"MACAddress"`
}
// DesktopMonitor 显示器信息(WMI Win32_DesktopMonitor)
type DesktopMonitor struct {
Name string `json:"name"`
ScreenWidth uint32 `json:"screenWidth"`
ScreenHeight uint32 `json:"screenHeight"`
PNPDeviceID string `json:"PNPDeviceID"`
ConfigManagerErrorCode uint32 `json:"configManagerErrorCode"`
}
// PointingDevice 指点设备信息(WMI Win32_PointingDevice)
type PointingDevice struct {
Description string `json:"description"`
PNPDeviceID string `json:"PNPDeviceID"`
}
// Keyboard 键盘信息(WMI Win32_Keyboard)
type Keyboard struct {
Description string `json:"description"`
PNPDeviceID string `json:"PNPDeviceID"`
}
// Hardware 硬件信息汇总
type Hardware struct {
BaseBoard []BaseBoard `json:"baseBoard"`
Bios []Bios `json:"bios"`
Processor []Processor `json:"processor"`
PhysicalMemory []PhysicalMemory `json:"physicalMemory"`
VideoController []VideoController `json:"videoController"`
SoundDevice []SoundDevice `json:"soundDevice"`
NetworkAdapter []NetworkAdapter `json:"networkAdapter"`
DiskDrive []DiskDrive `json:"diskDrive"`
DesktopMonitor []DesktopMonitor `json:"desktopMonitor"`
Keyboard []Keyboard `json:"keyboard"`
PointingDevice []PointingDevice `json:"pointingDevice"`
}
+12
View File
@@ -0,0 +1,12 @@
//go:build unix
package hardware
// GetHardwareInfo 采集硬件信息(Linux 未实现,返回空结构)
func GetHardwareInfo() (Hardware, error) {
hardware := Hardware{}
// 没有在 linux上实现,主是因为 都是在windows上使用,所以没有实现
return hardware, nil
}
+138
View File
@@ -0,0 +1,138 @@
//go:build windows
package hardware
import (
"github.com/yusufpapurcu/wmi"
)
// https://learn.microsoft.com/zh-cn/windows/win32/cimwin32prov/computer-system-hardware-classes
// GetBaseBoardInfo 主板信息
func GetBaseBoardInfo() ([]BaseBoard, error) {
info := make([]BaseBoard, 0)
err := wmi.Query("SELECT * FROM Win32_BaseBoard WHERE (Product IS NOT NULL)", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetBiosInfo BIOS信息
func GetBiosInfo() ([]Bios, error) {
info := make([]Bios, 0)
err := wmi.Query("SELECT * FROM Win32_BIOS WHERE (Name IS NOT NULL)", &info) // WHERE (BIOSVersion IS NOT NULL)
if err != nil {
return nil, err
}
return info, nil
}
// GetVideoControllerInfo 显卡信息
func GetVideoControllerInfo() ([]VideoController, error) {
info := make([]VideoController, 0)
err := wmi.Query("SELECT * FROM Win32_VideoController WHERE (Name IS NOT NULL)", &info) // WHERE (BIOSVersion IS NOT NULL)
if err != nil {
return nil, err
}
return info, nil
}
// GetPhysicalMemoryInfo 物理内存
func GetPhysicalMemoryInfo() ([]PhysicalMemory, error) {
info := make([]PhysicalMemory, 0)
err := wmi.Query("SELECT * FROM Win32_PhysicalMemory", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetProcessorInfo 处理器
func GetProcessorInfo() ([]Processor, error) {
info := make([]Processor, 0)
err := wmi.Query("SELECT * FROM Win32_Processor", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetDiskDriveInfo 硬盘
func GetDiskDriveInfo() ([]DiskDrive, error) {
info := make([]DiskDrive, 0)
err := wmi.Query("SELECT * FROM Win32_DiskDrive", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetSoundDeviceInfo 声卡
func GetSoundDeviceInfo() ([]SoundDevice, error) {
info := make([]SoundDevice, 0)
err := wmi.Query("SELECT * FROM Win32_SoundDevice", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetNetworkAdapterInfo 网卡
func GetNetworkAdapterInfo() ([]NetworkAdapter, error) {
info := make([]NetworkAdapter, 0)
err := wmi.Query("SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = TRUE", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetDesktopMonitorInfo 显示器
func GetDesktopMonitorInfo() ([]DesktopMonitor, error) {
info := make([]DesktopMonitor, 0)
err := wmi.Query("SELECT * FROM Win32_DesktopMonitor", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetPointingDeviceInfo 鼠标
func GetPointingDeviceInfo() ([]PointingDevice, error) {
info := make([]PointingDevice, 0)
err := wmi.Query("SELECT * FROM Win32_PointingDevice", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetKeyboardInfo 键盘
func GetKeyboardInfo() ([]Keyboard, error) {
info := make([]Keyboard, 0)
err := wmi.Query("SELECT * FROM Win32_Keyboard", &info)
if err != nil {
return nil, err
}
return info, nil
}
// GetHardwareInfo 通过 WMI 采集整机硬件信息
func GetHardwareInfo() (Hardware, error) {
hardware := Hardware{}
hardware.BaseBoard, _ = GetBaseBoardInfo()
hardware.Bios, _ = GetBiosInfo()
hardware.VideoController, _ = GetVideoControllerInfo()
hardware.PhysicalMemory, _ = GetPhysicalMemoryInfo()
hardware.Processor, _ = GetProcessorInfo()
hardware.DiskDrive, _ = GetDiskDriveInfo()
hardware.SoundDevice, _ = GetSoundDeviceInfo()
hardware.NetworkAdapter, _ = GetNetworkAdapterInfo()
hardware.DesktopMonitor, _ = GetDesktopMonitorInfo()
hardware.PointingDevice, _ = GetPointingDeviceInfo()
hardware.Keyboard, _ = GetKeyboardInfo()
return hardware, nil
}