249 lines
7.9 KiB
C#
249 lines
7.9 KiB
C#
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<List<AmtDevice>>> GetAllDevices()
|
||
{
|
||
return await _context.AmtDevices.ToListAsync();
|
||
}
|
||
|
||
[HttpGet("{id}")]
|
||
public async Task<ActionResult<AmtDevice>> GetDevice(long id)
|
||
{
|
||
var device = await _context.AmtDevices.FindAsync(id);
|
||
|
||
if (device == null)
|
||
{
|
||
return NotFound();
|
||
}
|
||
|
||
return device;
|
||
}
|
||
|
||
[HttpDelete("{id}")]
|
||
public async Task<IActionResult> DeleteDevice(long id)
|
||
{
|
||
var device = await _context.AmtDevices.FindAsync(id);
|
||
|
||
if (device == null)
|
||
{
|
||
return NotFound();
|
||
}
|
||
|
||
_context.AmtDevices.Remove(device);
|
||
await _context.SaveChangesAsync();
|
||
|
||
return NoContent();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测所有设备的在线状态
|
||
/// </summary>
|
||
[HttpGet("status")]
|
||
public async Task<ActionResult<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 statusList;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测单个设备的在线状态
|
||
/// </summary>
|
||
[HttpGet("{id}/status")]
|
||
public async Task<ActionResult<DeviceStatusDto>> CheckDeviceStatus(long id)
|
||
{
|
||
var device = await _context.AmtDevices.FindAsync(id);
|
||
|
||
if (device == null)
|
||
{
|
||
return NotFound();
|
||
}
|
||
|
||
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 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; }
|
||
}
|