胶料快检添加离线模式

This commit is contained in:
2026-06-30 11:28:04 +08:00
parent efcd73a565
commit 840e68a450
19 changed files with 1053 additions and 343 deletions

View File

@@ -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,