570 lines
19 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.Net.Sockets;
namespace AmtScanner.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DevicesController : ControllerBase
{
private readonly AppDbContext _context;
private readonly ILogger<DevicesController> _logger;
private readonly IAmtPowerService _powerService;
private readonly ICredentialService _credentialService;
public DevicesController(
AppDbContext context,
ILogger<DevicesController> logger,
IAmtPowerService powerService,
ICredentialService credentialService)
{
_context = context;
_logger = logger;
_powerService = powerService;
_credentialService = credentialService;
}
[HttpGet]
public async Task<ActionResult<ApiResponse<List<object>>>> GetAllDevices()
{
var devices = await _context.AmtDevices.ToListAsync();
// 解密 AMT 密码返回给前端
var result = devices.Select(d => new {
d.Id,
d.IpAddress,
d.Hostname,
d.SystemUuid,
d.MajorVersion,
d.MinorVersion,
d.ProvisioningState,
d.Description,
d.AmtOnline,
d.OsOnline,
d.WindowsUsername,
d.WindowsPassword,
d.AmtUsername,
AmtPasswordDecrypted = string.IsNullOrEmpty(d.AmtPassword) ? null :
System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(d.AmtPassword)),
d.DiscoveredAt,
d.LastSeenAt
}).ToList();
return Ok(ApiResponse<List<object>>.Success(result.Cast<object>().ToList()));
}
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<AmtDevice>>> GetDevice(long id)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<AmtDevice>.Fail(404, "设备不存在"));
}
return Ok(ApiResponse<AmtDevice>.Success(device));
}
[HttpDelete("{id}")]
public async Task<ActionResult<ApiResponse<object>>> DeleteDevice(long id)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
}
_context.AmtDevices.Remove(device);
await _context.SaveChangesAsync();
return Ok(ApiResponse<object>.Success(null, "删除成功"));
}
/// <summary>
/// 手动添加设备
/// </summary>
[HttpPost]
public async Task<ActionResult<ApiResponse<AmtDevice>>> AddDevice([FromBody] AddDeviceRequest request)
{
// 验证 IP 地址格式
if (string.IsNullOrWhiteSpace(request.IpAddress))
{
return Ok(ApiResponse<AmtDevice>.Fail(400, "IP 地址不能为空"));
}
// 检查设备是否已存在
var existingDevice = await _context.AmtDevices.FirstOrDefaultAsync(d => d.IpAddress == request.IpAddress);
if (existingDevice != null)
{
return Ok(ApiResponse<AmtDevice>.Fail(400, $"设备 {request.IpAddress} 已存在"));
}
var device = new AmtDevice
{
IpAddress = request.IpAddress,
Hostname = request.Hostname,
Description = request.Description,
MajorVersion = 0,
MinorVersion = 0,
ProvisioningState = ProvisioningState.UNKNOWN,
AmtOnline = false,
OsOnline = false,
DiscoveredAt = DateTime.UtcNow,
LastSeenAt = DateTime.UtcNow
};
// 如果提供了 Windows 凭据,一并保存
if (!string.IsNullOrEmpty(request.WindowsUsername))
{
device.WindowsUsername = request.WindowsUsername;
if (!string.IsNullOrEmpty(request.WindowsPassword))
{
device.WindowsPassword = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(request.WindowsPassword));
}
}
_context.AmtDevices.Add(device);
await _context.SaveChangesAsync();
_logger.LogInformation("Manually added device {Ip}", request.IpAddress);
return Ok(ApiResponse<AmtDevice>.Success(device, "设备添加成功"));
}
/// <summary>
/// 更新设备信息
/// </summary>
[HttpPut("{id}")]
public async Task<ActionResult<ApiResponse<AmtDevice>>> UpdateDevice(long id, [FromBody] UpdateDeviceRequest request)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<AmtDevice>.Fail(404, "设备不存在"));
}
if (!string.IsNullOrEmpty(request.Hostname))
device.Hostname = request.Hostname;
if (!string.IsNullOrEmpty(request.Description))
device.Description = request.Description;
await _context.SaveChangesAsync();
return Ok(ApiResponse<AmtDevice>.Success(device, "更新成功"));
}
/// <summary>
/// 设置设备的 Windows 登录凭据
/// </summary>
[HttpPut("{id}/credentials")]
public async Task<ActionResult<ApiResponse<object>>> SetDeviceCredentials(long id, [FromBody] SetDeviceCredentialsRequest request)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
}
device.WindowsUsername = request.Username;
// 简单加密存储密码(生产环境应使用更安全的加密方式)
device.WindowsPassword = string.IsNullOrEmpty(request.Password) ? null :
Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(request.Password));
await _context.SaveChangesAsync();
_logger.LogInformation("Updated Windows credentials for device {Id} ({Ip})", id, device.IpAddress);
return Ok(ApiResponse<object>.Success(null, "凭据设置成功"));
}
/// <summary>
/// 获取设备的 Windows 凭据(仅返回用户名,不返回密码)
/// </summary>
[HttpGet("{id}/credentials")]
public async Task<ActionResult<ApiResponse<DeviceCredentialsDto>>> GetDeviceCredentials(long id)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<DeviceCredentialsDto>.Fail(404, "设备不存在"));
}
return Ok(ApiResponse<DeviceCredentialsDto>.Success(new DeviceCredentialsDto
{
DeviceId = device.Id,
Username = device.WindowsUsername,
HasPassword = !string.IsNullOrEmpty(device.WindowsPassword)
}));
}
/// <summary>
/// 设置设备的 AMT 登录凭据
/// </summary>
[HttpPut("{id}/amt-credentials")]
public async Task<ActionResult<ApiResponse<object>>> SetAmtCredentials(long id, [FromBody] SetAmtCredentialsRequest request)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<object>.Fail(404, "设备不存在"));
}
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
{
return Ok(ApiResponse<object>.Fail(400, "用户名和密码不能为空"));
}
device.AmtUsername = request.Username;
// 简单加密存储密码(生产环境应使用更安全的加密方式)
device.AmtPassword = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(request.Password));
await _context.SaveChangesAsync();
_logger.LogInformation("Updated AMT credentials for device {Id} ({Ip})", id, device.IpAddress);
return Ok(ApiResponse<object>.Success(null, "AMT凭据设置成功"));
}
/// <summary>
/// 批量获取/更新设备 UUID
/// </summary>
[HttpPost("batch-fetch-uuid")]
public async Task<ActionResult<ApiResponse<BatchFetchUuidResponse>>> BatchFetchUuid([FromBody] BatchFetchUuidRequest request)
{
if (request.DeviceIds == null || request.DeviceIds.Count == 0)
{
return Ok(ApiResponse<BatchFetchUuidResponse>.Fail(400, "请选择要获取 UUID 的设备"));
}
var results = new List<FetchUuidResult>();
var hardwareService = HttpContext.RequestServices.GetRequiredService<IHardwareInfoService>();
foreach (var deviceId in request.DeviceIds)
{
var result = new FetchUuidResult { DeviceId = deviceId };
try
{
var device = await _context.AmtDevices.FindAsync(deviceId);
if (device == null)
{
result.Success = false;
result.Error = "设备不存在";
results.Add(result);
continue;
}
result.IpAddress = device.IpAddress;
// 获取硬件信息(强制刷新)
var hardwareInfo = await hardwareService.GetHardwareInfoAsync(deviceId, true);
if (hardwareInfo?.SystemInfo?.Uuid != null)
{
device.SystemUuid = hardwareInfo.SystemInfo.Uuid;
await _context.SaveChangesAsync();
result.Success = true;
result.Uuid = hardwareInfo.SystemInfo.Uuid;
_logger.LogInformation("Successfully fetched UUID for device {Ip}: {Uuid}", device.IpAddress, device.SystemUuid);
}
else
{
result.Success = false;
result.Error = "未能从设备获取 UUID";
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
_logger.LogWarning(ex, "Failed to fetch UUID for device {DeviceId}", deviceId);
}
results.Add(result);
}
var successCount = results.Count(r => r.Success);
var failCount = results.Count(r => !r.Success);
return Ok(ApiResponse<BatchFetchUuidResponse>.Success(new BatchFetchUuidResponse
{
Results = results,
SuccessCount = successCount,
FailCount = failCount
}, $"成功获取 {successCount} 台设备的 UUID失败 {failCount} 台"));
}
/// <summary>
/// 检测所有设备的在线状态
/// </summary>
[HttpGet("status")]
public async Task<ActionResult<ApiResponse<List<DeviceStatusDto>>>> CheckAllDevicesStatus()
{
var devices = await _context.AmtDevices.ToListAsync();
var credentials = await _context.AmtCredentials.ToListAsync();
var statusList = new List<DeviceStatusDto>();
// 并行检测所有设备
var tasks = devices.Select(async device =>
{
var (amtOnline, openPorts) = await CheckAmtOnlineAsync(device.IpAddress);
var osOnline = false;
// 如果 AMT 在线,尝试查询电源状态来判断 OS 是否在线
if (amtOnline && openPorts.Count > 0)
{
osOnline = await CheckOsOnlineAsync(device.IpAddress, openPorts, credentials);
}
// 更新数据库中的在线状态
device.AmtOnline = amtOnline;
device.OsOnline = osOnline;
if (amtOnline)
{
device.LastSeenAt = DateTime.UtcNow;
}
return new DeviceStatusDto
{
Id = device.Id,
IpAddress = device.IpAddress,
AmtOnline = amtOnline,
OsOnline = osOnline
};
});
statusList = (await Task.WhenAll(tasks)).ToList();
// 保存更新
await _context.SaveChangesAsync();
return Ok(ApiResponse<List<DeviceStatusDto>>.Success(statusList));
}
/// <summary>
/// 检测单个设备的在线状态
/// </summary>
[HttpGet("{id}/status")]
public async Task<ActionResult<ApiResponse<DeviceStatusDto>>> CheckDeviceStatus(long id)
{
var device = await _context.AmtDevices.FindAsync(id);
if (device == null)
{
return Ok(ApiResponse<DeviceStatusDto>.Fail(404, "设备不存在"));
}
var credentials = await _context.AmtCredentials.ToListAsync();
var (amtOnline, openPorts) = await CheckAmtOnlineAsync(device.IpAddress);
var osOnline = false;
// 如果 AMT 在线,尝试查询电源状态来判断 OS 是否在线
if (amtOnline && openPorts.Count > 0)
{
osOnline = await CheckOsOnlineAsync(device.IpAddress, openPorts, credentials);
}
// 更新数据库
device.AmtOnline = amtOnline;
device.OsOnline = osOnline;
if (amtOnline)
{
device.LastSeenAt = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
return Ok(ApiResponse<DeviceStatusDto>.Success(new DeviceStatusDto
{
Id = device.Id,
IpAddress = device.IpAddress,
AmtOnline = amtOnline,
OsOnline = osOnline
}));
}
/// <summary>
/// 检测 AMT 是否在线通过尝试连接AMT端口
/// </summary>
private async Task<(bool isOnline, List<int> openPorts)> CheckAmtOnlineAsync(string ipAddress)
{
int[] amtPorts = { 16992, 16993 };
var openPorts = new List<int>();
foreach (var port in amtPorts)
{
try
{
using var client = new TcpClient();
var connectTask = client.ConnectAsync(ipAddress, port);
// 设置超时时间为2秒增加超时以应对网络延迟
if (await Task.WhenAny(connectTask, Task.Delay(2000)) == connectTask)
{
if (client.Connected)
{
openPorts.Add(port);
_logger.LogInformation("Device {Ip} AMT port {Port} is open", ipAddress, port);
}
}
else
{
_logger.LogInformation("Device {Ip} AMT port {Port} connection timeout", ipAddress, port);
}
}
catch (Exception ex)
{
_logger.LogInformation("Failed to connect to {Ip}:{Port}: {Error}", ipAddress, port, ex.Message);
}
}
var isOnline = openPorts.Count > 0;
_logger.LogInformation("Device {Ip} AMT online: {Online}, open ports: [{Ports}]",
ipAddress, isOnline, string.Join(", ", openPorts));
return (isOnline, openPorts);
}
/// <summary>
/// 检测操作系统是否在线(通过查询电源状态)
/// </summary>
private async Task<bool> CheckOsOnlineAsync(string ipAddress, List<int> openPorts, List<AmtCredential> credentials)
{
// 尝试使用所有凭据查询电源状态
foreach (var credential in credentials)
{
try
{
var decryptedPassword = _credentialService.DecryptPassword(credential.Password);
var powerState = await _powerService.GetPowerStateAsync(
ipAddress,
credential.Username,
decryptedPassword,
openPorts);
if (powerState.Success)
{
// PowerState = 2 表示开机(操作系统在运行)
var osOnline = powerState.PowerState == 2;
_logger.LogInformation("Device {Ip} OS online: {Online} (PowerState: {State} - {StateText})",
ipAddress, osOnline, powerState.PowerState, powerState.PowerStateText);
return osOnline;
}
else
{
_logger.LogWarning("Device {Ip} failed to get power state: {Error}",
ipAddress, powerState.Error);
}
}
catch (Exception ex)
{
_logger.LogWarning("Failed to get power state for {Ip} with credential {User}: {Error}",
ipAddress, credential.Username, ex.Message);
}
}
// 如果无法查询电源状态,返回 false
_logger.LogWarning("Device {Ip} OS online status unknown (no valid credentials or query failed)", ipAddress);
return false;
}
}
/// <summary>
/// 设备状态DTO
/// </summary>
public class DeviceStatusDto
{
public long Id { get; set; }
public string IpAddress { get; set; } = string.Empty;
public bool AmtOnline { get; set; }
public bool OsOnline { get; set; }
}
/// <summary>
/// 更新设备请求
/// </summary>
public class UpdateDeviceRequest
{
public string? Hostname { get; set; }
public string? Description { get; set; }
}
/// <summary>
/// 设置设备 Windows 凭据请求
/// </summary>
public class SetDeviceCredentialsRequest
{
public string? Username { get; set; }
public string? Password { get; set; }
}
/// <summary>
/// 设备凭据DTO
/// </summary>
public class DeviceCredentialsDto
{
public long DeviceId { get; set; }
public string? Username { get; set; }
public bool HasPassword { get; set; }
}
/// <summary>
/// 添加设备请求
/// </summary>
public class AddDeviceRequest
{
public string IpAddress { get; set; } = string.Empty;
public string? Hostname { get; set; }
public string? Description { get; set; }
public string? WindowsUsername { get; set; }
public string? WindowsPassword { get; set; }
}
/// <summary>
/// 设置设备 AMT 凭据请求
/// </summary>
public class SetAmtCredentialsRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
/// <summary>
/// 批量获取 UUID 请求
/// </summary>
public class BatchFetchUuidRequest
{
public List<long> DeviceIds { get; set; } = new();
}
/// <summary>
/// 批量获取 UUID 响应
/// </summary>
public class BatchFetchUuidResponse
{
public List<FetchUuidResult> Results { get; set; } = new();
public int SuccessCount { get; set; }
public int FailCount { get; set; }
}
/// <summary>
/// 单个设备获取 UUID 结果
/// </summary>
public class FetchUuidResult
{
public long DeviceId { get; set; }
public string? IpAddress { get; set; }
public bool Success { get; set; }
public string? Uuid { get; set; }
public string? Error { get; set; }
}