| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 |
- using System.Collections.Concurrent;
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Entity;
- namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Idempotency;
- /// <summary>
- /// 线程安全内存幂等存储——用于并发/单元测试。以 ConcurrentDictionary.TryAdd 模拟
- /// (tenant_id, domain_code, fbillno) 的 DB UNIQUE 原子占位语义(TryAdd 原子,多并发仅一路成功)。
- /// **不代表真实 MySQL 唯一约束**(那属 Phase 4B)。
- /// </summary>
- public sealed class InMemoryIqcPostingStore : IIqcPostingStore
- {
- private readonly ConcurrentDictionary<string, AdoIqcInventoryPosting> _store =
- new(StringComparer.Ordinal);
- private static string Key(long tenantId, string domainCode, string fbillNo)
- => $"{tenantId}|{domainCode ?? string.Empty}|{fbillNo ?? string.Empty}";
- public Task<PostingInsertResult> TryInsertAsync(AdoIqcInventoryPosting posting)
- {
- if (posting == null) throw new ArgumentNullException(nameof(posting));
- var key = Key(posting.TenantId, posting.DomainCode, posting.FbillNo);
- var inserted = _store.TryAdd(key, posting);
- return Task.FromResult(new PostingInsertResult
- {
- Inserted = inserted,
- Existing = inserted ? null : _store.TryGetValue(key, out var e) ? e : null,
- });
- }
- public Task<AdoIqcInventoryPosting> GetAsync(long tenantId, string domainCode, string fbillNo)
- {
- _store.TryGetValue(Key(tenantId, domainCode, fbillNo), out var e);
- return Task.FromResult(e);
- }
- public Task UpdateStatusAsync(AdoIqcInventoryPosting posting, string status)
- {
- if (posting == null) throw new ArgumentNullException(nameof(posting));
- if (_store.TryGetValue(Key(posting.TenantId, posting.DomainCode, posting.FbillNo), out var e))
- {
- lock (e) { e.PostingStatus = status; e.UpdateTime = DateTime.Now; }
- }
- posting.PostingStatus = status;
- return Task.CompletedTask;
- }
- /// <summary>原子抢占:对既有记录取锁,校验 PROCESSING + stale 后刷新心跳。lock(e) 保证并发仅一路成功。</summary>
- public Task<bool> TryRecoverStaleAsync(long tenantId, string domainCode, string fbillNo, DateTime staleBefore, DateTime now)
- {
- if (!_store.TryGetValue(Key(tenantId, domainCode, fbillNo), out var e))
- return Task.FromResult(false);
- lock (e)
- {
- var status = (e.PostingStatus ?? string.Empty).Trim().ToUpperInvariant();
- var heartbeat = e.UpdateTime ?? e.CreateTime;
- if (status == "PROCESSING" && heartbeat < staleBefore)
- {
- e.UpdateTime = now; // 续租=抢占
- return Task.FromResult(true);
- }
- return Task.FromResult(false);
- }
- }
- }
|