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; }
}
}
}