diff --git a/examples/hardware/main.go b/examples/hardware/main.go new file mode 100644 index 0000000..a4a8d56 --- /dev/null +++ b/examples/hardware/main.go @@ -0,0 +1,38 @@ +// hardware 包示例:读取硬件信息(WMI)与硬件变更检测(Windows) +package main + +import ( + "fmt" + + "git.zeroonesoft.cn/golib/zogo/hardware" +) + +func main() { + hw, err := hardware.GetHardwareInfo() + if err != nil { + fmt.Println("获取失败:", err) + return + } + + for _, p := range hw.Processor { + fmt.Printf("CPU: %s (物理核心 %d, 逻辑处理器 %d)\n", p.Name, p.NumberOfCores, p.NumberOfLogicalProcessors) + } + for _, m := range hw.PhysicalMemory { + fmt.Printf("内存: %s 容量 %d MB 速率 %d MHz 序列号 %s\n", m.Manufacturer, m.Capacity/1024/1024, m.Speed, m.SerialNumber) + } + for _, d := range hw.DiskDrive { + fmt.Printf("硬盘: %s 容量 %.0f GB 接口 %s\n", d.Caption, float64(d.Size)/1024/1024/1024, d.InterfaceType) + } + for _, v := range hw.VideoController { + fmt.Printf("显卡: %s 分辨率 %dx%d@%dHz 显存 %.0f MB\n", + v.Name, v.CurrentHorizontalResolution, v.CurrentVerticalResolution, v.CurrentRefreshRate, float64(v.AdapterRAM)/1024/1024) + } + for _, b := range hw.BaseBoard { + fmt.Printf("主板: %s %s\n", b.Manufacturer, b.Product) + } + + // 硬件变更检测:与上次采集结果对比(返回 true 表示硬件发生变化) + fmt.Println("内存是否变更:", hardware.CheckPhysicalMemoryChanged(hw.PhysicalMemory, hw.PhysicalMemory)) + fmt.Println("CPU 是否变更:", hardware.CheckProcessorChanged(hw.Processor, hw.Processor)) + fmt.Println("硬盘是否变更:", hardware.CheckDiskDriveChanged(hw.DiskDrive, hw.DiskDrive)) +} diff --git a/hardware/README.md b/hardware/README.md new file mode 100644 index 0000000..65088eb --- /dev/null +++ b/hardware/README.md @@ -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` 按字段值比较,不比对顺序。 diff --git a/hardware/change.go b/hardware/change.go new file mode 100644 index 0000000..6d0ea00 --- /dev/null +++ b/hardware/change.go @@ -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 +} diff --git a/hardware/change_test.go b/hardware/change_test.go new file mode 100644 index 0000000..f17d257 --- /dev/null +++ b/hardware/change_test.go @@ -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("网卡变化应判定变更") + } +} diff --git a/hardware/types.go b/hardware/types.go new file mode 100644 index 0000000..0394385 --- /dev/null +++ b/hardware/types.go @@ -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"` +} diff --git a/hardware/unix.go b/hardware/unix.go new file mode 100644 index 0000000..7819270 --- /dev/null +++ b/hardware/unix.go @@ -0,0 +1,12 @@ +//go:build unix + +package hardware + +// GetHardwareInfo 采集硬件信息(Linux 未实现,返回空结构) +func GetHardwareInfo() (Hardware, error) { + hardware := Hardware{} + + // 没有在 linux上实现,主是因为 都是在windows上使用,所以没有实现 + + return hardware, nil +} diff --git a/hardware/windows.go b/hardware/windows.go new file mode 100644 index 0000000..e8fe2c7 --- /dev/null +++ b/hardware/windows.go @@ -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 +}