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 _logger; private static readonly ConcurrentDictionary _scanProgress = new(); public OsDevicesController( AppDbContext context, IWindowsScannerService scannerService, ILogger logger) { _context = context; _scannerService = scannerService; _logger = logger; } /// /// 获取所有操作系统设备 /// [HttpGet] public async Task>>> 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, WindowsUsername = o.WindowsUsername, WindowsPassword = o.WindowsPassword, AmtDeviceId = o.AmtDeviceId, AmtDeviceIp = o.AmtDevice != null ? o.AmtDevice.IpAddress : null }) .ToListAsync(); return Ok(ApiResponse>.Success(devices)); } /// /// 获取单个操作系统设备 /// [HttpGet("{id}")] public async Task>> GetById(long id) { var device = await _context.OsDevices .Include(o => o.AmtDevice) .FirstOrDefaultAsync(o => o.Id == id); if (device == null) return Ok(ApiResponse.Fail(404, "设备不存在")); return Ok(ApiResponse.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 })); } /// /// 启动操作系统扫描 /// [HttpPost("scan/start")] public async Task>> StartScan( [FromBody] OsScanRequest request) { var taskId = Guid.NewGuid().ToString("N"); // 初始化进度为 0% _scanProgress[taskId] = new OsScanProgress { TaskId = taskId, ScannedCount = 0, TotalCount = 1, // 避免除以0 FoundDevices = 0, ProgressPercentage = 0, CurrentIp = "初始化中..." }; // 使用 Action 回调直接更新进度 Action progressCallback = p => { _scanProgress[taskId] = p; }; _ = Task.Run(async () => { try { await _scannerService.ScanNetworkAsync(taskId, request.NetworkSegment, request.SubnetMask, progressCallback); } catch (Exception ex) { _logger.LogError(ex, "OS scan failed for task {TaskId}", taskId); } }); return Ok(ApiResponse.Success(new OsScanStartResponse { TaskId = taskId, Message = "操作系统扫描已启动" })); } /// /// 获取扫描进度 /// [HttpGet("scan/status/{taskId}")] public ActionResult> GetScanStatus(string taskId) { if (_scanProgress.TryGetValue(taskId, out var progress)) { return Ok(ApiResponse.Success(progress)); } // 任务不存在时返回 -1 表示任务不存在,前端应该停止轮询 return Ok(ApiResponse.Success(new OsScanProgress { TaskId = taskId, ScannedCount = 0, TotalCount = 0, FoundDevices = 0, ProgressPercentage = -1, // -1 表示任务不存在 CurrentIp = null })); } /// /// 取消扫描 /// [HttpPost("scan/cancel/{taskId}")] public ActionResult> CancelScan(string taskId) { _scannerService.CancelScan(taskId); return Ok(ApiResponse.Success(null, "扫描已取消")); } /// /// 获取扫描发现的设备列表(未保存到数据库) /// [HttpGet("scan/results/{taskId}")] public ActionResult>> GetScanResults(string taskId) { _logger.LogInformation("Getting scan results for task: {TaskId}", taskId); var devices = _scannerService.GetScanResults(taskId); _logger.LogInformation("Found {Count} devices in scan results for task: {TaskId}", devices.Count, taskId); var results = devices.Select(d => new ScanResultDto { IpAddress = d.IpAddress, OsType = d.OsType.ToString(), Hostname = d.Hostname, IsOnline = d.IsOnline, DiscoveredAt = d.DiscoveredAt }).ToList(); return Ok(ApiResponse>.Success(results)); } /// /// 保存选中的设备到数据库 /// [HttpPost("scan/save")] public async Task>> SaveSelectedDevices( [FromBody] SaveDevicesRequest request) { if (string.IsNullOrEmpty(request.TaskId) || request.SelectedIps == null || request.SelectedIps.Count == 0) { return Ok(ApiResponse.Fail(400, "请选择要添加的设备")); } var savedCount = await _scannerService.SaveSelectedDevicesAsync(request.TaskId, request.SelectedIps); // 清除扫描结果 _scannerService.ClearScanResults(request.TaskId); return Ok(ApiResponse.Success(new SaveDevicesResponse { SavedCount = savedCount, Message = $"成功添加 {savedCount} 台设备" })); } /// /// 获取设备详细信息(通过 WMI) /// [HttpPost("{id}/fetch-info")] public async Task>> FetchDeviceInfo( long id, [FromBody] WmiCredentials credentials) { var device = await _context.OsDevices.FindAsync(id); if (device == null) return Ok(ApiResponse.Fail(404, "设备不存在")); try { var osInfo = await _scannerService.GetOsInfoAsync( device.IpAddress, credentials.Username, credentials.Password); if (osInfo == null) return Ok(ApiResponse.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.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.Fail(500, $"获取系统信息失败: {ex.Message}")); } } /// /// 手动绑定 AMT 设备 /// [HttpPost("{id}/bind-amt/{amtDeviceId}")] public async Task>> BindAmtDevice(long id, long amtDeviceId) { var osDevice = await _context.OsDevices.FindAsync(id); if (osDevice == null) return Ok(ApiResponse.Fail(404, "操作系统设备不存在")); var amtDevice = await _context.AmtDevices.FindAsync(amtDeviceId); if (amtDevice == null) return Ok(ApiResponse.Fail(404, "AMT 设备不存在")); osDevice.AmtDeviceId = amtDeviceId; await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "绑定成功")); } /// /// 解除 AMT 绑定 /// [HttpPost("{id}/unbind-amt")] public async Task>> UnbindAmtDevice(long id) { var osDevice = await _context.OsDevices.FindAsync(id); if (osDevice == null) return Ok(ApiResponse.Fail(404, "设备不存在")); osDevice.AmtDeviceId = null; await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "已解除绑定")); } /// /// 自动绑定所有设备 /// [HttpPost("auto-bind")] public async Task>> AutoBindAll() { await _scannerService.BindAmtDevicesAsync(); return Ok(ApiResponse.Success(null, "自动绑定完成")); } /// /// 设置 Windows 登录凭据 /// [HttpPut("{id}/credentials")] public async Task>> SetCredentials(long id, [FromBody] WindowsCredentialsRequest request) { var device = await _context.OsDevices.FindAsync(id); if (device == null) return Ok(ApiResponse.Fail(404, "设备不存在")); device.WindowsUsername = request.Username; device.WindowsPassword = request.Password; // TODO: 加密存储 device.LastUpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); return Ok(ApiResponse.Success(null, "凭据已保存")); } /// /// 删除设备 /// [HttpDelete("{id}")] public async Task>> Delete(long id) { var device = await _context.OsDevices.FindAsync(id); if (device == null) return Ok(ApiResponse.Fail(404, "设备不存在")); _context.OsDevices.Remove(device); await _context.SaveChangesAsync(); return Ok(ApiResponse.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 string? WindowsUsername { get; set; } public string? WindowsPassword { 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; } public class ScanResultDto { public string IpAddress { get; set; } = string.Empty; public string OsType { get; set; } = string.Empty; public string? Hostname { get; set; } public bool IsOnline { get; set; } public DateTime DiscoveredAt { get; set; } } public class SaveDevicesRequest { public string TaskId { get; set; } = string.Empty; public List SelectedIps { get; set; } = new(); } public class SaveDevicesResponse { public int SavedCount { get; set; } public string Message { get; set; } = string.Empty; } public class WindowsCredentialsRequest { public string Username { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; }