胶料快检添加离线模式
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using YY.Admin.Core;
|
||||
|
||||
namespace YY.Admin.Services.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 胶料快检相关 MES 只读数据(实验标准、密炼计划)专用轮询管理器。
|
||||
/// </summary>
|
||||
public class MesQuickTestDataPollManager : ISingletonDependency
|
||||
{
|
||||
public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(2);
|
||||
|
||||
private readonly List<(string Name, Func<Task> Task)> _tasks = [];
|
||||
private readonly Timer _timer;
|
||||
private readonly ILoggerService _logger;
|
||||
private volatile bool _tickRunning;
|
||||
|
||||
public MesQuickTestDataPollManager(ILoggerService logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_timer = new Timer(OnTick, null, PollInterval, PollInterval);
|
||||
_logger.Information($"[快检数据轮询] 已启动,间隔 {PollInterval.TotalSeconds} 秒");
|
||||
}
|
||||
|
||||
public void Register(string name, Func<Task> pollTask)
|
||||
{
|
||||
lock (_tasks)
|
||||
_tasks.Add((name, pollTask));
|
||||
_logger.Information($"[快检数据轮询] 注册任务: {name}");
|
||||
}
|
||||
|
||||
private void OnTick(object? _)
|
||||
{
|
||||
if (_tickRunning) return;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (_tickRunning) return;
|
||||
_tickRunning = true;
|
||||
try
|
||||
{
|
||||
List<(string Name, Func<Task> Task)> snapshot;
|
||||
lock (_tasks) snapshot = [.. _tasks];
|
||||
|
||||
foreach (var (name, task) in snapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[快检数据轮询] 任务 [{name}] 异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tickRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -53,17 +53,13 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
|
||||
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[密炼计划] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
_ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
|
||||
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
|
||||
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
|
||||
|
||||
public async Task<MixingProductionPlanPageResult> PageAsync(
|
||||
public Task<MixingProductionPlanPageResult> PageAsync(
|
||||
int pageNo, int pageSize,
|
||||
DateTime? planDateFrom = null,
|
||||
DateTime? planDateTo = null,
|
||||
@@ -73,18 +69,7 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
|
||||
string? materialName = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 列表拉取失败,使用本地缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
List<MesXslMixingProductionPlan> source;
|
||||
lock (_cacheLock)
|
||||
source = _localCache.Select(Clone).ToList();
|
||||
@@ -95,25 +80,14 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
|
||||
.Skip(Math.Max(0, (pageNo - 1) * pageSize))
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
return new MixingProductionPlanPageResult(records, total, pageNo, pageSize);
|
||||
return Task.FromResult(new MixingProductionPlanPageResult(records, total, pageNo, pageSize));
|
||||
}
|
||||
|
||||
public async Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default)
|
||||
public Task<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 全量拉取失败,使用本地缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
lock (_cacheLock)
|
||||
return _localCache.Select(Clone).ToList();
|
||||
return Task.FromResult(_localCache.Select(Clone).ToList());
|
||||
}
|
||||
|
||||
public async Task<bool> SyncFromRemoteAsync(CancellationToken ct = default)
|
||||
@@ -190,25 +164,6 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = "reconnect" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 重连同步失败:{ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static bool IsPlanContentEqual(MesXslMixingProductionPlan a, MesXslMixingProductionPlan b) =>
|
||||
string.Equals(GetPlanFingerprint(a), GetPlanFingerprint(b), StringComparison.Ordinal);
|
||||
|
||||
|
||||
@@ -3,33 +3,58 @@ using System.Text.Json;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.Services.Service.MixingProductionPlan;
|
||||
|
||||
public class MixingProductionPlanSyncCoordinator : ISingletonDependency
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMixingProductionPlanService _planService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public MixingProductionPlanSyncCoordinator(
|
||||
IEventAggregator eventAggregator,
|
||||
SyncPollManager pollManager,
|
||||
IMixingProductionPlanService planService,
|
||||
MesQuickTestDataPollManager pollManager,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_planService = planService;
|
||||
_logger = logger;
|
||||
|
||||
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("密炼计划", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("密炼计划", () => PollSyncAsync("poll"));
|
||||
|
||||
_logger.Information("[密炼计划] MixingProductionPlanSyncCoordinator 已启动");
|
||||
_ = PollSyncAsync("startup");
|
||||
}
|
||||
|
||||
private async Task PollSyncAsync(string action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = action });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] 轮询同步失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
if (!payload.IsOnline) return;
|
||||
_ = Task.Run(() => PollSyncAsync("reconnect"));
|
||||
}
|
||||
|
||||
private void OnRemoteCommand(RemoteCommandPayload payload)
|
||||
@@ -53,7 +78,18 @@ public class MixingProductionPlanSyncCoordinator : ISingletonDependency
|
||||
MixingProductionPlanId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
|
||||
};
|
||||
_logger.Information($"[密炼计划] STOMP action={changed.Action}, id={changed.MixingProductionPlanId}");
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] STOMP 同步失败:{ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -54,10 +54,6 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing
|
||||
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[快检记录同步] 初始化完成,本地记录={_localItems.Count}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
_ = Task.Run(() => PushPendingAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
|
||||
@@ -180,7 +176,79 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing
|
||||
};
|
||||
}
|
||||
|
||||
public bool DeleteFailedLocal(string localId)
|
||||
public async Task<RubberQuickTestRecordSaveResult> UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(localId))
|
||||
throw new ArgumentException("无效的记录标识", nameof(localId));
|
||||
|
||||
var record = CloneRecord(entity);
|
||||
RubberQuickTestRecordLocalItem? existing;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
existing = _localItems.FirstOrDefault(x =>
|
||||
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (existing == null)
|
||||
throw new InvalidOperationException("未找到本地快检记录");
|
||||
|
||||
if (string.Equals(existing.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("已同步记录不可编辑");
|
||||
|
||||
record.CreateTime = existing.Record.CreateTime ?? record.CreateTime ?? DateTime.Now;
|
||||
record.InspectTime = record.InspectTime ?? record.CreateTime;
|
||||
if (string.IsNullOrWhiteSpace(record.RecordNo))
|
||||
record.RecordNo = existing.Record.RecordNo;
|
||||
|
||||
var item = CloneLocalItem(existing);
|
||||
item.Record = record;
|
||||
item.SyncStatus = "Pending";
|
||||
item.SyncError = null;
|
||||
|
||||
if (_networkMonitor.IsOnline && HasCompletePlanInfo(record))
|
||||
{
|
||||
try
|
||||
{
|
||||
var recordNo = await RemoteAddAsync(record, ct).ConfigureAwait(false);
|
||||
if (!string.IsNullOrWhiteSpace(recordNo))
|
||||
{
|
||||
item.Record.RecordNo = recordNo;
|
||||
item.SyncStatus = "Synced";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
item.SyncStatus = "Failed";
|
||||
item.SyncError = ex.Message;
|
||||
_logger.Warning($"[快检记录修改] 远端失败,保留本地:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var target = _localItems.FirstOrDefault(x =>
|
||||
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
if (target == null)
|
||||
throw new InvalidOperationException("未找到本地快检记录");
|
||||
|
||||
target.Record = CloneRecord(item.Record);
|
||||
target.SyncStatus = item.SyncStatus;
|
||||
target.SyncError = item.SyncError;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
|
||||
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
|
||||
.Publish(new RubberQuickTestRecordChangedPayload { Action = "update", RecordId = localId });
|
||||
|
||||
return new RubberQuickTestRecordSaveResult
|
||||
{
|
||||
LocalId = localId,
|
||||
SyncStatus = item.SyncStatus,
|
||||
Record = CloneRecord(item.Record)
|
||||
};
|
||||
}
|
||||
|
||||
public bool DeleteUnsyncedLocal(string localId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(localId)) return false;
|
||||
|
||||
@@ -189,7 +257,7 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing
|
||||
{
|
||||
var item = _localItems.FirstOrDefault(x =>
|
||||
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
if (item == null || !string.Equals(item.SyncStatus, "Failed", StringComparison.OrdinalIgnoreCase))
|
||||
if (item == null || string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
removed = _localItems.Remove(item);
|
||||
@@ -201,10 +269,92 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing
|
||||
|
||||
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
|
||||
.Publish(new RubberQuickTestRecordChangedPayload { Action = "delete", RecordId = localId });
|
||||
_logger.Information($"[快检记录删除] 已删除同步失败本地记录 localId={localId}");
|
||||
_logger.Information($"[快检记录删除] 已删除本地未同步记录 localId={localId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<RubberQuickTestRecordSyncResult> SyncLocalAsync(string localId, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(localId))
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = "无效的记录标识" };
|
||||
|
||||
if (!_networkMonitor.IsOnline)
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = "当前离线,无法同步" };
|
||||
|
||||
RubberQuickTestRecordLocalItem? item;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
item = _localItems.FirstOrDefault(x =>
|
||||
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = "未找到本地快检记录" };
|
||||
|
||||
if (string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase))
|
||||
return new RubberQuickTestRecordSyncResult { Success = true, Message = "记录已同步", RecordNo = item.Record.RecordNo };
|
||||
|
||||
if (!HasCompletePlanInfo(item.Record))
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = PlanInfoValidationMessage };
|
||||
|
||||
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false))
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = "同步正在进行中,请稍后再试" };
|
||||
|
||||
try
|
||||
{
|
||||
var recordNo = await RemoteAddAsync(item.Record, ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
|
||||
if (target == null)
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = "记录已被删除" };
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(recordNo))
|
||||
target.Record.RecordNo = recordNo;
|
||||
target.SyncStatus = "Synced";
|
||||
target.SyncError = null;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
|
||||
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
|
||||
.Publish(new RubberQuickTestRecordChangedPayload { Action = "sync", RecordId = item.LocalId });
|
||||
|
||||
return new RubberQuickTestRecordSyncResult
|
||||
{
|
||||
Success = true,
|
||||
Message = "同步成功",
|
||||
RecordNo = recordNo ?? item.Record.RecordNo
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
|
||||
if (target != null)
|
||||
{
|
||||
target.SyncStatus = "Failed";
|
||||
target.SyncError = ex.Message;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Warning($"[快检记录同步] 手动同步失败 localId={localId}: {ex.Message}");
|
||||
return new RubberQuickTestRecordSyncResult { Success = false, Message = ex.Message };
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private const string PlanInfoValidationMessage = "请维护密炼计划信息(密炼计划、密炼日期、密炼机台)";
|
||||
|
||||
private static bool HasCompletePlanInfo(MesXslRubberQuickTestRecord record) =>
|
||||
!string.IsNullOrWhiteSpace(record.ProductionPlanNo)
|
||||
&& record.ProductionDate != null
|
||||
&& !string.IsNullOrWhiteSpace(record.ProdEquipmentName);
|
||||
|
||||
public string GenerateRecordNo(string rubberMaterialName)
|
||||
{
|
||||
var dateStr = DateTime.Now.ToString("yyyyMMdd");
|
||||
@@ -327,61 +477,6 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing
|
||||
return result.GetProperty("records").Deserialize<List<MesXslRubberQuickTestRecord>>(_jsonOpts) ?? new();
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(() => PushPendingAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private async Task PushPendingAsync(CancellationToken ct)
|
||||
{
|
||||
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false)) return;
|
||||
try
|
||||
{
|
||||
List<RubberQuickTestRecordLocalItem> pending;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
pending = _localItems.Where(x => x.SyncStatus != "Synced").Select(CloneLocalItem).ToList();
|
||||
}
|
||||
|
||||
foreach (var item in pending)
|
||||
{
|
||||
if (!_networkMonitor.IsOnline) break;
|
||||
try
|
||||
{
|
||||
var recordNo = await RemoteAddAsync(item.Record, ct).ConfigureAwait(false);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
|
||||
if (target == null) continue;
|
||||
if (!string.IsNullOrWhiteSpace(recordNo))
|
||||
target.Record.RecordNo = recordNo;
|
||||
target.SyncStatus = "Synced";
|
||||
target.SyncError = null;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
|
||||
if (target != null)
|
||||
{
|
||||
target.SyncStatus = "Failed";
|
||||
target.SyncError = ex.Message;
|
||||
SaveCacheToDiskUnsafe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<RubberQuickTestRecordListRow> ApplyFilters(
|
||||
List<RubberQuickTestRecordListRow> source,
|
||||
string? filterRecordNo,
|
||||
|
||||
@@ -54,35 +54,20 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
|
||||
LoadCacheFromDisk();
|
||||
_logger.Information($"[快检实验标准] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}");
|
||||
|
||||
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
|
||||
if (_networkMonitor.IsOnline)
|
||||
_ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
|
||||
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
|
||||
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
|
||||
|
||||
public async Task<RubberQuickTestStdPageResult> PageAsync(
|
||||
public Task<RubberQuickTestStdPageResult> PageAsync(
|
||||
int pageNo, int pageSize,
|
||||
string? stdName = null,
|
||||
string? rubberMaterialName = null,
|
||||
string? enableStatus = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_networkMonitor.IsOnline)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncFromRemoteAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[快检实验标准] 列表拉取失败,使用本地缓存:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
List<MesXslRubberQuickTestStd> source;
|
||||
lock (_cacheLock)
|
||||
source = _localCache.Select(CloneMain).ToList();
|
||||
@@ -93,7 +78,7 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
.Skip(Math.Max(0, (pageNo - 1) * pageSize))
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
return new RubberQuickTestStdPageResult(records, total, pageNo, pageSize);
|
||||
return Task.FromResult(new RubberQuickTestStdPageResult(records, total, pageNo, pageSize));
|
||||
}
|
||||
|
||||
public async Task<MesXslRubberQuickTestStd?> GetByIdAsync(string id, CancellationToken ct = default)
|
||||
@@ -199,6 +184,8 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
lock (_cacheLock)
|
||||
localSnapshot = _localCache.Select(CloneMain).ToList();
|
||||
|
||||
await HydrateStdLinesAsync(records, localSnapshot, ct).ConfigureAwait(false);
|
||||
|
||||
var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge(
|
||||
localSnapshot,
|
||||
records,
|
||||
@@ -233,18 +220,76 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(bool isOnline)
|
||||
private async Task HydrateStdLinesAsync(
|
||||
List<MesXslRubberQuickTestStd> remoteRecords,
|
||||
List<MesXslRubberQuickTestStd> localSnapshot,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = "reconnect" });
|
||||
});
|
||||
var localById = localSnapshot
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Id))
|
||||
.ToDictionary(x => x.Id!, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
using var gate = new SemaphoreSlim(8);
|
||||
var tasks = remoteRecords
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Id))
|
||||
.Select(async record =>
|
||||
{
|
||||
await gate.WaitAsync(ct).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
localById.TryGetValue(record.Id!, out var local);
|
||||
if (local != null
|
||||
&& IsStdListContentEqual(record, local)
|
||||
&& local.LineList is { Count: > 0 })
|
||||
{
|
||||
record.LineList = local.LineList.Select(CloneStdLine).ToList();
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = await FetchLinesByStdIdAsync(record.Id!, ct).ConfigureAwait(false);
|
||||
record.LineList = lines.Count > 0
|
||||
? lines
|
||||
: local?.LineList?.Select(CloneStdLine).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
gate.Release();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<List<MesXslRubberQuickTestStdLine>> FetchLinesByStdIdAsync(string stdId, CancellationToken ct)
|
||||
{
|
||||
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestStd/anon/queryLineListByStdId?id={Uri.EscapeDataString(stdId)}";
|
||||
using var client = CreateClient();
|
||||
using var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("result", out var resultEl))
|
||||
return [];
|
||||
|
||||
return resultEl.Deserialize<List<MesXslRubberQuickTestStdLine>>(_jsonOpts) ?? [];
|
||||
}
|
||||
|
||||
private static MesXslRubberQuickTestStdLine CloneStdLine(MesXslRubberQuickTestStdLine l) => new()
|
||||
{
|
||||
Id = l.Id,
|
||||
StdId = l.StdId,
|
||||
DataPointId = l.DataPointId,
|
||||
PointName = l.PointName,
|
||||
LowerLimit = l.LowerLimit,
|
||||
UpperLimit = l.UpperLimit,
|
||||
LowerWarn = l.LowerWarn,
|
||||
UpperWarn = l.UpperWarn,
|
||||
TargetValue = l.TargetValue,
|
||||
SortNo = l.SortNo
|
||||
};
|
||||
|
||||
private static List<MesXslRubberQuickTestStd> ApplyFilters(
|
||||
List<MesXslRubberQuickTestStd> source,
|
||||
string? stdName,
|
||||
|
||||
@@ -2,33 +2,52 @@ using Prism.Events;
|
||||
using System.Text.Json;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.Services.Service.RubberQuickTestStd;
|
||||
|
||||
public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRubberQuickTestStdService _stdService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public RubberQuickTestStdSyncCoordinator(
|
||||
IEventAggregator eventAggregator,
|
||||
SyncPollManager pollManager,
|
||||
IRubberQuickTestStdService stdService,
|
||||
MesQuickTestDataPollManager pollManager,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_stdService = stdService;
|
||||
_logger = logger;
|
||||
|
||||
_eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("胶料快检实验标准", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("胶料快检实验标准", () => PollSyncAsync("poll"));
|
||||
|
||||
_logger.Information("[快检实验标准] RubberQuickTestStdSyncCoordinator 已启动");
|
||||
_ = PollSyncAsync("startup");
|
||||
}
|
||||
|
||||
private async Task PollSyncAsync(string action)
|
||||
{
|
||||
if (!await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = action });
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
if (!payload.IsOnline) return;
|
||||
_ = Task.Run(() => PollSyncAsync("reconnect"));
|
||||
}
|
||||
|
||||
private void OnRemoteCommand(RemoteCommandPayload payload)
|
||||
@@ -52,7 +71,11 @@ public class RubberQuickTestStdSyncCoordinator : ISingletonDependency
|
||||
StdId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null
|
||||
};
|
||||
_logger.Information($"[快检实验标准] STOMP action={changed.Action}, stdId={changed.StdId}");
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>().Publish(changed);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>().Publish(changed);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user