namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering;
///
/// 业务单号服务:基于原子 provider 取号 + 「DB UNIQUE 兜底 + duplicate-key 有限次数重试」消费。
/// 硬约束:禁 SELECT MAX()+1 / 禁 read-max-then-write / 禁仅靠进程锁 / 禁随机数假唯一。
/// 最终唯一性 = provider 原子性 + 目标表 DB UNIQUE 兜底;本服务负责有界重试,绝不无限循环。
///
public sealed class BusinessNumberService
{
public const int DefaultMaxRetry = 5;
private readonly IBusinessNumberProvider _provider;
public BusinessNumberService(IBusinessNumberProvider provider)
{
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// 取单个业务单号(原子)。
public async Task NextAsync(string ruleCode, string domain, string operatorNo)
{
var batch = await _provider.NextBatchAsync(ruleCode, domain, 1, operatorNo);
return batch[0];
}
/// 取一批业务单号(原子、连续)。
public Task> NextBatchAsync(string ruleCode, string domain, int count, string operatorNo)
=> _provider.NextBatchAsync(ruleCode, domain, count, operatorNo);
///
/// 取号 → 消费(tryConsume 尝试落库;返回 true=成功占用/唯一,false=撞 DB UNIQUE 需换号重试)。
/// 有界重试 maxRetry 次;耗尽仍失败则返回 Success=false(不无限循环)。
/// tryConsume 应捕获 duplicate-key(1062) 返回 false,其它异常向上抛。
///
public async Task NextWithUniqueRetryAsync(
string ruleCode, string domain, string operatorNo,
Func> tryConsume, int maxRetry = DefaultMaxRetry)
{
if (tryConsume == null) throw new ArgumentNullException(nameof(tryConsume));
if (maxRetry < 1) maxRetry = 1;
string last = null;
for (var attempt = 1; attempt <= maxRetry; attempt++)
{
last = await NextAsync(ruleCode, domain, operatorNo);
if (await tryConsume(last))
return new BusinessNumberConsumeResult { Success = true, Number = last, Attempts = attempt };
}
return new BusinessNumberConsumeResult { Success = false, Number = last, Attempts = maxRetry };
}
}