lvfengfree eebbacafde feat: 实现OS设备扫描和UUID绑定功能
- 添加OsDevice模型和OsDevicesController
- 实现WindowsScannerService用于网络扫描和WMI查询
- 添加AMT设备UUID查询功能(从CIM_ComputerSystemPackage获取PlatformGUID)
- 实现PlatformGUID到标准UUID格式的转换(字节序转换)
- 修复HardwareInfoRepository保存UUID的问题
- 前端添加OS设备管理页面和UUID获取/刷新按钮
- 添加数据库迁移脚本
2026-01-21 16:16:48 +08:00

466 lines
16 KiB
C#

using AmtScanner.Api.Data;
using AmtScanner.Api.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
using System.Management;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace AmtScanner.Api.Services;
public interface IWindowsScannerService
{
Task<List<OsDevice>> ScanNetworkAsync(string taskId, string networkSegment, string subnetMask,
IProgress<OsScanProgress> progress, CancellationToken cancellationToken = default);
Task<OsDevice?> GetOsInfoAsync(string ipAddress, string username, string password);
Task<string?> GetSystemUuidAsync(string ipAddress, string username, string password);
Task BindAmtDevicesAsync();
void CancelScan(string taskId);
}
public class WindowsScannerService : IWindowsScannerService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<WindowsScannerService> _logger;
private readonly IConfiguration _configuration;
private readonly ConcurrentDictionary<string, CancellationTokenSource> _cancellationTokens = new();
public WindowsScannerService(
IServiceScopeFactory scopeFactory,
ILogger<WindowsScannerService> logger,
IConfiguration configuration)
{
_scopeFactory = scopeFactory;
_logger = logger;
_configuration = configuration;
}
public async Task<List<OsDevice>> ScanNetworkAsync(
string taskId,
string networkSegment,
string subnetMask,
IProgress<OsScanProgress> progress,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Starting OS scan for task: {TaskId}", taskId);
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_cancellationTokens[taskId] = cts;
try
{
var ipList = CalculateIpRange(networkSegment, subnetMask);
var foundDevices = new ConcurrentBag<OsDevice>();
int scannedCount = 0;
int foundCount = 0;
var threadPoolSize = _configuration.GetValue<int>("Scanner:ThreadPoolSize", 50);
var parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = threadPoolSize,
CancellationToken = cts.Token
};
await Parallel.ForEachAsync(ipList, parallelOptions, async (ip, ct) =>
{
try
{
var device = await ScanSingleHostAsync(ip, ct);
var scanned = Interlocked.Increment(ref scannedCount);
if (device != null)
{
foundDevices.Add(device);
var found = Interlocked.Increment(ref foundCount);
await SaveOsDeviceAsync(device);
progress.Report(new OsScanProgress
{
TaskId = taskId,
ScannedCount = scanned,
TotalCount = ipList.Count,
FoundDevices = found,
ProgressPercentage = (double)scanned / ipList.Count * 100,
CurrentIp = ip,
LatestDevice = device
});
}
else
{
progress.Report(new OsScanProgress
{
TaskId = taskId,
ScannedCount = scanned,
TotalCount = ipList.Count,
FoundDevices = foundCount,
ProgressPercentage = (double)scanned / ipList.Count * 100,
CurrentIp = ip
});
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Error scanning {Ip}", ip);
}
});
// 扫描完成后尝试绑定 AMT 设备
await BindAmtDevicesAsync();
return foundDevices.ToList();
}
finally
{
_cancellationTokens.TryRemove(taskId, out _);
cts.Dispose();
}
}
public void CancelScan(string taskId)
{
if (_cancellationTokens.TryGetValue(taskId, out var cts))
{
cts.Cancel();
_logger.LogInformation("OS scan task {TaskId} cancelled", taskId);
}
}
private async Task<OsDevice?> ScanSingleHostAsync(string ip, CancellationToken ct)
{
// 先 Ping 检测是否在线
if (!await IsHostOnlineAsync(ip, ct))
return null;
// 检测 Windows 端口
var isWindows = await IsWindowsHostAsync(ip, ct);
if (isWindows)
{
return new OsDevice
{
IpAddress = ip,
OsType = OsType.Windows,
IsOnline = true,
LastOnlineAt = DateTime.UtcNow,
DiscoveredAt = DateTime.UtcNow,
LastUpdatedAt = DateTime.UtcNow,
Description = "通过端口扫描发现"
};
}
// 检测 Linux (SSH 端口)
var isLinux = await IsPortOpenAsync(ip, 22, 2000, ct);
if (isLinux)
{
return new OsDevice
{
IpAddress = ip,
OsType = OsType.Linux,
IsOnline = true,
LastOnlineAt = DateTime.UtcNow,
DiscoveredAt = DateTime.UtcNow,
LastUpdatedAt = DateTime.UtcNow,
Description = "通过 SSH 端口发现"
};
}
return null;
}
private async Task<bool> IsHostOnlineAsync(string ip, CancellationToken ct)
{
try
{
using var ping = new Ping();
var reply = await ping.SendPingAsync(ip, 1000);
return reply.Status == IPStatus.Success;
}
catch
{
return false;
}
}
private async Task<bool> IsWindowsHostAsync(string ip, CancellationToken ct)
{
// 检测 Windows 常用端口: 135(RPC), 445(SMB), 3389(RDP), 5985(WinRM)
var windowsPorts = new[] { 135, 445, 3389, 5985 };
foreach (var port in windowsPorts)
{
if (await IsPortOpenAsync(ip, port, 1000, ct))
return true;
}
return false;
}
private async Task<bool> IsPortOpenAsync(string ip, int port, int timeoutMs, CancellationToken ct)
{
try
{
using var client = new TcpClient();
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(timeoutMs);
await client.ConnectAsync(ip, port, cts.Token);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 通过 WMI 获取远程 Windows 系统信息
/// </summary>
public async Task<OsDevice?> GetOsInfoAsync(string ipAddress, string username, string password)
{
return await Task.Run(() =>
{
try
{
var options = new ConnectionOptions
{
Username = username,
Password = password,
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy
};
var scope = new ManagementScope($"\\\\{ipAddress}\\root\\cimv2", options);
scope.Connect();
var device = new OsDevice
{
IpAddress = ipAddress,
OsType = OsType.Windows,
IsOnline = true,
LastOnlineAt = DateTime.UtcNow,
LastUpdatedAt = DateTime.UtcNow
};
// 获取 UUID
var uuidQuery = new ObjectQuery("SELECT UUID FROM Win32_ComputerSystemProduct");
using (var uuidSearcher = new ManagementObjectSearcher(scope, uuidQuery))
{
foreach (var obj in uuidSearcher.Get())
{
device.SystemUuid = obj["UUID"]?.ToString();
break;
}
}
// 获取操作系统信息
var osQuery = new ObjectQuery("SELECT Caption, Version, OSArchitecture, LastBootUpTime FROM Win32_OperatingSystem");
using (var osSearcher = new ManagementObjectSearcher(scope, osQuery))
{
foreach (var obj in osSearcher.Get())
{
device.OsVersion = $"{obj["Caption"]} ({obj["Version"]})";
device.Architecture = obj["OSArchitecture"]?.ToString();
var lastBootStr = obj["LastBootUpTime"]?.ToString();
if (!string.IsNullOrEmpty(lastBootStr))
{
device.LastBootTime = ManagementDateTimeConverter.ToDateTime(lastBootStr);
}
break;
}
}
// 获取计算机名
var csQuery = new ObjectQuery("SELECT Name, UserName FROM Win32_ComputerSystem");
using (var csSearcher = new ManagementObjectSearcher(scope, csQuery))
{
foreach (var obj in csSearcher.Get())
{
device.Hostname = obj["Name"]?.ToString();
device.LoggedInUser = obj["UserName"]?.ToString();
break;
}
}
// 获取 MAC 地址
var netQuery = new ObjectQuery("SELECT MACAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True");
using (var netSearcher = new ManagementObjectSearcher(scope, netQuery))
{
foreach (var obj in netSearcher.Get())
{
var mac = obj["MACAddress"]?.ToString();
if (!string.IsNullOrEmpty(mac))
{
device.MacAddress = mac;
break;
}
}
}
device.Description = "通过 WMI 获取详细信息";
return device;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get OS info for {Ip} via WMI", ipAddress);
return null;
}
});
}
/// <summary>
/// 获取远程 Windows 系统的 UUID
/// </summary>
public async Task<string?> GetSystemUuidAsync(string ipAddress, string username, string password)
{
return await Task.Run(() =>
{
try
{
var options = new ConnectionOptions
{
Username = username,
Password = password,
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy
};
var scope = new ManagementScope($"\\\\{ipAddress}\\root\\cimv2", options);
scope.Connect();
var query = new ObjectQuery("SELECT UUID FROM Win32_ComputerSystemProduct");
using var searcher = new ManagementObjectSearcher(scope, query);
foreach (var obj in searcher.Get())
{
return obj["UUID"]?.ToString();
}
return null;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get UUID for {Ip}", ipAddress);
return null;
}
});
}
/// <summary>
/// 根据 UUID 自动绑定 AMT 设备和操作系统设备
/// </summary>
public async Task BindAmtDevicesAsync()
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 获取所有有 UUID 的操作系统设备
var osDevices = await context.OsDevices
.Where(o => o.SystemUuid != null && o.AmtDeviceId == null)
.ToListAsync();
// 获取所有有 UUID 的 AMT 设备
var amtDevices = await context.AmtDevices
.Where(a => a.SystemUuid != null)
.ToListAsync();
var amtUuidMap = amtDevices.ToDictionary(a => a.SystemUuid!, a => a);
foreach (var osDevice in osDevices)
{
if (osDevice.SystemUuid != null && amtUuidMap.TryGetValue(osDevice.SystemUuid, out var amtDevice))
{
osDevice.AmtDeviceId = amtDevice.Id;
_logger.LogInformation("Bound OS device {OsIp} to AMT device {AmtIp} via UUID {Uuid}",
osDevice.IpAddress, amtDevice.IpAddress, osDevice.SystemUuid);
}
}
await context.SaveChangesAsync();
}
private async Task SaveOsDeviceAsync(OsDevice device)
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var existing = await context.OsDevices
.FirstOrDefaultAsync(d => d.IpAddress == device.IpAddress);
if (existing != null)
{
existing.OsType = device.OsType;
existing.IsOnline = device.IsOnline;
existing.LastOnlineAt = device.LastOnlineAt;
existing.LastUpdatedAt = DateTime.UtcNow;
if (!string.IsNullOrEmpty(device.SystemUuid))
existing.SystemUuid = device.SystemUuid;
if (!string.IsNullOrEmpty(device.Hostname))
existing.Hostname = device.Hostname;
if (!string.IsNullOrEmpty(device.OsVersion))
existing.OsVersion = device.OsVersion;
}
else
{
context.OsDevices.Add(device);
}
await context.SaveChangesAsync();
}
private List<string> CalculateIpRange(string networkSegment, string subnetMask)
{
var ipList = new List<string>();
try
{
var networkLong = IpToLong(networkSegment);
var cidr = SubnetMaskToCidr(subnetMask);
var hostBits = 32 - cidr;
var totalHosts = (int)Math.Pow(2, hostBits);
for (int i = 1; i < totalHosts - 1; i++)
{
var ipLong = networkLong + i;
ipList.Add(LongToIp(ipLong));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calculating IP range");
}
return ipList;
}
private long IpToLong(string ipAddress)
{
var parts = ipAddress.Split('.');
long result = 0;
for (int i = 0; i < 4; i++)
result = result << 8 | long.Parse(parts[i]);
return result;
}
private string LongToIp(long ip) =>
$"{(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}";
private int SubnetMaskToCidr(string subnetMask)
{
if (subnetMask.StartsWith("/"))
return int.Parse(subnetMask.Substring(1));
var parts = subnetMask.Split('.');
int cidr = 0;
foreach (var part in parts)
cidr += Convert.ToString(int.Parse(part), 2).Count(c => c == '1');
return cidr;
}
}
public class OsScanProgress
{
public string TaskId { get; set; } = string.Empty;
public int ScannedCount { get; set; }
public int TotalCount { get; set; }
public int FoundDevices { get; set; }
public double ProgressPercentage { get; set; }
public string? CurrentIp { get; set; }
public OsDevice? LatestDevice { get; set; }
}