using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using WIDESEA_Core.BaseRepository; using WIDESEA_IStockService; using WIDESEA_Model.Models; using WIDESEA_WMSServer.Hubs; namespace WIDESEA_WMSServer.BackgroundServices { /// /// 库存监控后台服务 /// 定期检查库存和货位数据变化并通过SignalR推送到前端 /// public class StockMonitorBackgroundService : BackgroundService { private readonly ILogger _logger; private readonly IHubContext _hubContext; private readonly IServiceProvider _serviceProvider; // 货位状态快照:key = LocationId private ConcurrentDictionary _lastLocationSnapshots = new(); // 监控间隔(毫秒) private const int MonitorIntervalMs = 3000; public StockMonitorBackgroundService( ILogger logger, IHubContext hubContext, IServiceProvider serviceProvider) { _logger = logger; _hubContext = hubContext; _serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("库存监控后台服务已启动"); // 等待应用完全启动 await Task.Delay(5000, stoppingToken); while (!stoppingToken.IsCancellationRequested) { try { await CheckChangesAsync(); } catch (Exception ex) { _logger.LogError(ex, "检查数据变化时发生错误"); } await Task.Delay(MonitorIntervalMs, stoppingToken); } _logger.LogInformation("库存监控后台服务已停止"); } /// /// 检查货位和库存变化 /// private async Task CheckChangesAsync() { using var scope = _serviceProvider.CreateScope(); var stockService = scope.ServiceProvider.GetRequiredService(); var locationRepo = scope.ServiceProvider.GetRequiredService>(); // 1. 获取所有货位数据 var allLocations = await locationRepo.QueryDataAsync(x => x.LocationStatus != 99); // 排除禁用的货位 // 2. 获取所有库存数据(包含明细) var allStockData = await stockService.Repository.Db.Queryable() .Includes(x => x.Details) .ToListAsync(); // 构建库存字典:LocationId -> StockInfo var stockDict = allStockData .Where(s => s.LocationId > 0) .ToDictionary(s => s.LocationId, s => s); // 构建当前货位快照字典 var currentSnapshots = new ConcurrentDictionary(); foreach (var location in allLocations) { // 获取该货位的库存信息 stockDict.TryGetValue(location.Id, out var stock); // 计算库存数量 float totalQuantity = 0; string detailsHash = string.Empty; if (stock?.Details != null && stock.Details.Any()) { totalQuantity = stock.Details.Sum(d => d.StockQuantity); detailsHash = GenerateDetailsHash(stock.Details.ToList()); } var snapshot = new LocationSnapshot { LocationId = location.Id, WarehouseId = location.WarehouseId, LocationCode = location.LocationCode, LocationStatus = location.LocationStatus, PalletCode = stock?.PalletCode, StockStatus = stock?.StockStatus ?? 0, StockQuantity = totalQuantity, DetailsHash = detailsHash }; currentSnapshots.TryAdd(location.Id, snapshot); // 检查是否有变化 if (_lastLocationSnapshots.TryGetValue(location.Id, out var lastSnapshot)) { // 检测变化:货位状态、库存状态、数量、明细变化 if (lastSnapshot.LocationStatus != snapshot.LocationStatus || lastSnapshot.StockStatus != snapshot.StockStatus || lastSnapshot.PalletCode != snapshot.PalletCode || Math.Abs(lastSnapshot.StockQuantity - snapshot.StockQuantity) > 0.001f || lastSnapshot.DetailsHash != snapshot.DetailsHash) { // 构建更新DTO var update = new StockUpdateDTO { LocationId = snapshot.LocationId, WarehouseId = snapshot.WarehouseId, PalletCode = snapshot.PalletCode, StockQuantity = snapshot.StockQuantity, StockStatus = snapshot.StockStatus, LocationStatus = snapshot.LocationStatus, Details = BuildDetailDtos(stock?.Details?.ToList()) }; await _hubContext.Clients.All.SendAsync("StockUpdated", update); _logger.LogDebug("数据变化推送: LocationId={LocationId}, LocStatus={LocStatus}, StockStatus={StockStatus}, Quantity={Quantity}", snapshot.LocationId, snapshot.LocationStatus, snapshot.StockStatus, snapshot.StockQuantity); } } } // 更新快照数据 _lastLocationSnapshots = currentSnapshots; } /// /// 生成明细数据哈希 /// private string GenerateDetailsHash(List details) { if (details == null || !details.Any()) return string.Empty; var hashString = string.Join("|", details .OrderBy(d => d.Id) .Select(d => $"{d.Id}:{d.MaterielCode}:{d.BatchNo}:{d.StockQuantity}")); return hashString.GetHashCode().ToString(); } /// /// 构建明细DTO列表 /// private List BuildDetailDtos(List details) { if (details == null || !details.Any()) return new List(); return details.Select(d => new StockDetailUpdateDTO { Id = d.Id, MaterielCode = d.MaterielCode, MaterielName = d.MaterielName, BatchNo = d.BatchNo, StockQuantity = d.StockQuantity, Unit = d.Unit, Status = d.Status }).ToList(); } /// /// 货位快照 /// private class LocationSnapshot { public int LocationId { get; set; } public int WarehouseId { get; set; } public string LocationCode { get; set; } public int LocationStatus { get; set; } public string PalletCode { get; set; } public int StockStatus { get; set; } public float StockQuantity { get; set; } public string DetailsHash { get; set; } } } }