BusinessNumberService.cs 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering;
  2. /// <summary>
  3. /// 业务单号服务:基于原子 provider 取号 + 「DB UNIQUE 兜底 + duplicate-key 有限次数重试」消费。
  4. /// 硬约束:禁 SELECT MAX()+1 / 禁 read-max-then-write / 禁仅靠进程锁 / 禁随机数假唯一。
  5. /// 最终唯一性 = provider 原子性 + 目标表 DB UNIQUE 兜底;本服务负责有界重试,绝不无限循环。
  6. /// </summary>
  7. public sealed class BusinessNumberService
  8. {
  9. public const int DefaultMaxRetry = 5;
  10. private readonly IBusinessNumberProvider _provider;
  11. public BusinessNumberService(IBusinessNumberProvider provider)
  12. {
  13. _provider = provider ?? throw new ArgumentNullException(nameof(provider));
  14. }
  15. /// <summary>取单个业务单号(原子)。</summary>
  16. public async Task<string> NextAsync(string ruleCode, string domain, string operatorNo)
  17. {
  18. var batch = await _provider.NextBatchAsync(ruleCode, domain, 1, operatorNo);
  19. return batch[0];
  20. }
  21. /// <summary>取一批业务单号(原子、连续)。</summary>
  22. public Task<IReadOnlyList<string>> NextBatchAsync(string ruleCode, string domain, int count, string operatorNo)
  23. => _provider.NextBatchAsync(ruleCode, domain, count, operatorNo);
  24. /// <summary>
  25. /// 取号 → 消费(tryConsume 尝试落库;返回 true=成功占用/唯一,false=撞 DB UNIQUE 需换号重试)。
  26. /// 有界重试 maxRetry 次;耗尽仍失败则返回 Success=false(不无限循环)。
  27. /// tryConsume 应捕获 duplicate-key(1062) 返回 false,其它异常向上抛。
  28. /// </summary>
  29. public async Task<BusinessNumberConsumeResult> NextWithUniqueRetryAsync(
  30. string ruleCode, string domain, string operatorNo,
  31. Func<string, Task<bool>> tryConsume, int maxRetry = DefaultMaxRetry)
  32. {
  33. if (tryConsume == null) throw new ArgumentNullException(nameof(tryConsume));
  34. if (maxRetry < 1) maxRetry = 1;
  35. string last = null;
  36. for (var attempt = 1; attempt <= maxRetry; attempt++)
  37. {
  38. last = await NextAsync(ruleCode, domain, operatorNo);
  39. if (await tryConsume(last))
  40. return new BusinessNumberConsumeResult { Success = true, Number = last, Attempts = attempt };
  41. }
  42. return new BusinessNumberConsumeResult { Success = false, Number = last, Attempts = maxRetry };
  43. }
  44. }