- 添加OsDevice模型和OsDevicesController - 实现WindowsScannerService用于网络扫描和WMI查询 - 添加AMT设备UUID查询功能(从CIM_ComputerSystemPackage获取PlatformGUID) - 实现PlatformGUID到标准UUID格式的转换(字节序转换) - 修复HardwareInfoRepository保存UUID的问题 - 前端添加OS设备管理页面和UUID获取/刷新按钮 - 添加数据库迁移脚本
324 lines
11 KiB
C#
324 lines
11 KiB
C#
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;
|
||
}
|