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
|
{
|
/// <summary>
|
/// 库存监控后台服务
|
/// 定期检查库存和货位数据变化并通过SignalR推送到前端
|
/// </summary>
|
public class StockMonitorBackgroundService : BackgroundService
|
{
|
private readonly ILogger<StockMonitorBackgroundService> _logger;
|
private readonly IHubContext<StockHub> _hubContext;
|
private readonly IServiceProvider _serviceProvider;
|
|
// 货位状态快照:key = LocationId
|
private ConcurrentDictionary<int, LocationSnapshot> _lastLocationSnapshots = new();
|
|
// 监控间隔(毫秒)
|
private const int MonitorIntervalMs = 3000;
|
|
public StockMonitorBackgroundService(
|
ILogger<StockMonitorBackgroundService> logger,
|
IHubContext<StockHub> 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("库存监控后台服务已停止");
|
}
|
|
/// <summary>
|
/// 检查货位和库存变化
|
/// </summary>
|
private async Task CheckChangesAsync()
|
{
|
using var scope = _serviceProvider.CreateScope();
|
var stockService = scope.ServiceProvider.GetRequiredService<IStockInfoService>();
|
var locationRepo = scope.ServiceProvider.GetRequiredService<IRepository<Dt_LocationInfo>>();
|
|
// 1. 获取所有货位数据
|
var allLocations = await locationRepo.QueryDataAsync(x => x.LocationStatus != 99); // 排除禁用的货位
|
|
// 2. 获取所有库存数据(包含明细)
|
var allStockData = await stockService.Repository.Db.Queryable<Dt_StockInfo>()
|
.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<int, LocationSnapshot>();
|
|
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;
|
}
|
|
/// <summary>
|
/// 生成明细数据哈希
|
/// </summary>
|
private string GenerateDetailsHash(List<Dt_StockInfoDetail> 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();
|
}
|
|
/// <summary>
|
/// 构建明细DTO列表
|
/// </summary>
|
private List<StockDetailUpdateDTO> BuildDetailDtos(List<Dt_StockInfoDetail> details)
|
{
|
if (details == null || !details.Any()) return new List<StockDetailUpdateDTO>();
|
|
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();
|
}
|
|
/// <summary>
|
/// 货位快照
|
/// </summary>
|
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; }
|
}
|
}
|
}
|