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

324 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AmtScanner.Api.Data;
using AmtScanner.Api.Models;
using AmtScanner.Api.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
namespace AmtScanner.Api.Controllers;
[ApiController]
[Route("api/os-devices")]
public class OsDevicesController : ControllerBase
{
private readonly AppDbContext _context;
private readonly IWindowsScannerService _scannerService;
private readonly ILogger<OsDevicesController> _logger;
private static readonly ConcurrentDictionary<string, OsScanProgress> _scanProgress = new();
public OsDevicesController(
AppDbContext context,
IWindowsScannerService scannerService,
ILogger<OsDevicesController> logger)
{
_context = context;
_scannerService = scannerService;
_logger = logger;
}
/// <summary>
/// 获取所有操作系统设备
/// </summary>
[HttpGet]
public async Task<ActionResult<ApiResponse<List<OsDeviceDto>>>> GetAll()
{
var devices = await _context.OsDevices
.Include(o => o.AmtDevice)
.OrderByDescending(o => o.LastUpdatedAt)
.Select(o => new OsDeviceDto
{
Id = o.Id,
IpAddress = o.IpAddress,
SystemUuid = o.SystemUuid,
Hostname = o.Hostname,
OsType = o.OsType.ToString(),
OsVersion = o.OsVersion,
Architecture = o.Architecture,
LoggedInUser = o.LoggedInUser,
LastBootTime = o.LastBootTime,
MacAddress = o.MacAddress,
IsOnline = o.IsOnline,
LastOnlineAt = o.LastOnlineAt,
DiscoveredAt = o.DiscoveredAt,
LastUpdatedAt = o.LastUpdatedAt,
Description = o.Description,
AmtDeviceId = o.AmtDeviceId,
AmtDeviceIp = o.AmtDevice != null ? o.AmtDevice.IpAddress : null
})
.ToListAsync();
return Ok(ApiResponse<List<OsDeviceDto>>.Success(devices));
}
/// <summary>
/// 获取单个操作系统设备
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<OsDeviceDto>>> GetById(long id)
{
var device = await _context.OsDevices
.Include(o => o.AmtDevice)
.FirstOrDefaultAsync(o => o.Id == id);
if (device == null)
return Ok(ApiResponse<OsDeviceDto>.Fail(404, "设备不存在"));
return Ok(ApiResponse<OsDeviceDto>.Success(new OsDeviceDto
{
Id = device.Id,
IpAddress = device.IpAddress,
SystemUuid = device.SystemUuid,
Hostname = device.Hostname,
OsType = device.OsType.ToString(),
OsVersion = device.OsVersion,
Architecture = device.Architecture,
LoggedInUser = device.LoggedInUser,
LastBootTime = device.LastBootTime,
MacAddress = device.MacAddress,
IsOnline = device.IsOnline,
LastOnlineAt = device.LastOnlineAt,
DiscoveredAt = device.DiscoveredAt,
LastUpdatedAt = device.LastUpdatedAt,
Description = device.Description,
AmtDeviceId = device.AmtDeviceId,
AmtDeviceIp = device.AmtDevice?.IpAddress
}));
}
/// <summary>
/// 启动操作系统扫描
/// </summary>
[HttpPost("scan/start")]
public async Task<ActionResult<ApiResponse<OsScanStartResponse>>> StartScan(
[FromBody] OsScanRequest request)
{
var taskId = Guid.NewGuid().ToString("N");
var progress = new Progress<OsScanProgress>(p =>
{
_scanProgress[taskId] = p;
});
_ = Task.Run(async () =>
{
try
{
await _scannerService.ScanNetworkAsync(taskId, request.NetworkSegment, request.SubnetMask, progress);
}
catch (Exception ex)
{
_logger.LogError(ex, "OS scan failed for task {TaskId}", taskId);
}
});
return Ok(ApiResponse<OsScanStartResponse>.Success(new OsScanStartResponse
{
TaskId = taskId,
Message = "操作系统扫描已启动"
}));
}
/// <summary>
/// 获取扫描进度
/// </summary>
[HttpGet("scan/status/{taskId}")]
public ActionResult<ApiResponse<OsScanProgress>> GetScanStatus(string taskId)
{
if (_scanProgress.TryGetValue(taskId, out var progress))
{
return Ok(ApiResponse<OsScanProgress>.Success(progress));
}
return Ok(ApiResponse<OsScanProgress>.Fail(404, "扫描任务不存在"));
}
/// <summary>
/// 取消扫描
/// </summary>
[HttpPost("scan/cancel/{taskId}")]
public ActionResult<ApiResponse<object>> CancelScan(string taskId)
{
_scannerService.CancelScan(taskId);
return Ok(ApiResponse<object>.Success(null, "扫描已取消"));
}
/// <summary>
/// 获取设备详细信息(通过 WMI
/// </summary>
[HttpPost("{id}/fetch-info")]
public async Task<ActionResult<ApiResponse<OsDeviceDto>>> FetchDeviceInfo(
long id,
[FromBody] WmiCredentials credentials)
{
var device = await _context.OsDevices.FindAsync(id);
if (device == null)
return Ok(ApiResponse<OsDeviceDto>.Fail(404, "设备不存在"));
try
{
var osInfo = await _scannerService.GetOsInfoAsync(
device.IpAddress,
credentials.Username,
credentials.Password);
if (osInfo == null)
return Ok(ApiResponse<OsDeviceDto>.Fail(500, "无法获取系统信息。可能原因1) 目标机器WMI服务未启动 2) 防火墙阻止连接 3) 凭据不正确 4) 目标机器不允许远程WMI连接"));
// 更新设备信息
device.SystemUuid = osInfo.SystemUuid;
device.Hostname = osInfo.Hostname;
device.OsVersion = osInfo.OsVersion;
device.Architecture = osInfo.Architecture;
device.LoggedInUser = osInfo.LoggedInUser;
device.LastBootTime = osInfo.LastBootTime;
device.MacAddress = osInfo.MacAddress;
device.LastUpdatedAt = DateTime.UtcNow;
device.Description = "通过 WMI 获取详细信息";
await _context.SaveChangesAsync();
// 尝试绑定 AMT 设备
await _scannerService.BindAmtDevicesAsync();
// 重新加载以获取关联的 AMT 设备
await _context.Entry(device).Reference(d => d.AmtDevice).LoadAsync();
return Ok(ApiResponse<OsDeviceDto>.Success(new OsDeviceDto
{
Id = device.Id,
IpAddress = device.IpAddress,
SystemUuid = device.SystemUuid,
Hostname = device.Hostname,
OsType = device.OsType.ToString(),
OsVersion = device.OsVersion,
Architecture = device.Architecture,
LoggedInUser = device.LoggedInUser,
LastBootTime = device.LastBootTime,
MacAddress = device.MacAddress,
IsOnline = device.IsOnline,
LastOnlineAt = device.LastOnlineAt,
DiscoveredAt = device.DiscoveredAt,
LastUpdatedAt = device.LastUpdatedAt,
Description = device.Description,
AmtDeviceId = device.AmtDeviceId,
AmtDeviceIp = device.AmtDevice?.IpAddress
}, "系统信息已更新"));
}
catch (Exception ex)
{
_logger.LogError(ex, "获取设备 {Id} 系统信息失败", id);
return Ok(ApiResponse<OsDeviceDto>.Fail(500, $"获取系统信息失败: {ex.Message}"));
}
}
/// <summary>
/// 手动绑定 AMT 设备
/// </summary>
[HttpPost("{id}/bind-amt/{amtDeviceId}")]
public async Task<ActionResult<ApiResponse<object>>> BindAmtDevice(long id, long amtDeviceId)
{
var osDevice = await _context.OsDevices.FindAsync(id);
if (osDevice == null)
return Ok(ApiResponse<object>.Fail(404, "操作系统设备不存在"));
var amtDevice = await _context.AmtDevices.FindAsync(amtDeviceId);
if (amtDevice == null)
return Ok(ApiResponse<object>.Fail(404, "AMT 设备不存在"));
osDevice.AmtDeviceId = amtDeviceId;
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "绑定成功"));
}
/// <summary>
/// 解除 AMT 绑定
/// </summary>
[HttpPost("{id}/unbind-amt")]
public async Task<ActionResult<ApiResponse<object>>> UnbindAmtDevice(long id)
{
var osDevice = await _context.OsDevices.FindAsync(id);
if (osDevice == null)
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
osDevice.AmtDeviceId = null;
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "已解除绑定"));
}
/// <summary>
/// 自动绑定所有设备
/// </summary>
[HttpPost("auto-bind")]
public async Task<ActionResult<ApiResponse<object>>> AutoBindAll()
{
await _scannerService.BindAmtDevicesAsync();
return Ok(ApiResponse<object>.Success(null, "自动绑定完成"));
}
/// <summary>
/// 删除设备
/// </summary>
[HttpDelete("{id}")]
public async Task<ActionResult<ApiResponse<object>>> Delete(long id)
{
var device = await _context.OsDevices.FindAsync(id);
if (device == null)
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
_context.OsDevices.Remove(device);
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "删除成功"));
}
}
public class OsDeviceDto
{
public long Id { get; set; }
public string IpAddress { get; set; } = string.Empty;
public string? SystemUuid { get; set; }
public string? Hostname { get; set; }
public string? OsType { get; set; }
public string? OsVersion { get; set; }
public string? Architecture { get; set; }
public string? LoggedInUser { get; set; }
public DateTime? LastBootTime { get; set; }
public string? MacAddress { get; set; }
public bool IsOnline { get; set; }
public DateTime? LastOnlineAt { get; set; }
public DateTime DiscoveredAt { get; set; }
public DateTime LastUpdatedAt { get; set; }
public string? Description { get; set; }
public long? AmtDeviceId { get; set; }
public string? AmtDeviceIp { get; set; }
}
public class WmiCredentials
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
public class OsScanStartResponse
{
public string TaskId { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
}
public class OsScanRequest
{
public string NetworkSegment { get; set; } = string.Empty;
public string SubnetMask { get; set; } = string.Empty;
}