You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为已审核发票分配连续递增的参考编号?

发票审核通过时分配连续编号的实现方案探讨

我在业务逻辑中遇到需求:当审核通过一张发票时,需要为其分配下一个连续编号作为参考。考虑在数据库创建仅含一行数据的InvoicesCounter表(字段:LastNumber、Timestamp),通过Timestamp实现并发控制,目前有两种思路,想咨询这两种方案是否可行,以及是否存在更优实现方式?


方案一:应用层直接处理

MyAplicationLayerMethod()
{
    Invoice myInvoiceToAccept = _unitOfWork.InvoiceRepository.GetInvoice(1); // 修正拼写:Invoce→Invoice,GetInvoid→GetInvoice
    InvoiceCounter myInvoiceCounter = _unitOfWork.InvoiceCounter.GetLastNumber();
    
    myInvoiceToAccept.Accept(myInvoiceCounter.LastNumber); // 修正:原代码myLastNumber未定义,应取计数器的LastNumber
    myInvoiceCounter.Increment();

    _unitOfWork.Commit();
}

方案二:通过领域服务处理

MyAplicationLayerMethod()
{
    Invoice myInvoiceToAccept = _unitOfWork.InvoiceRepository.GetInvoice(1); // 修正拼写错误
    InvoiceCounter myInvoiceCounter = _unitOfWork.InvoiceCounter.GetLastNumber();
    
    InvoiceDomainService.AcceptInvoice(myInvoiceToAccept, myInvoiceCounter); // 修正命名

    _unitOfWork.Commit();        
}

class InvoiceDomainService
{
    public static void AcceptInvoice(Invoice paramInvoice, InvoiceCounter paramInvoiceCounter) // 修正拼写错误
    {
         paramInvoice.Accept(paramInvoiceCounter.LastNumber); // 补充传递编号
         paramInvoiceCounter.Increment();
    }
}

两种方案的可行性分析

  • 方案一:可行但不符合分层架构原则。将发票审核+编号递增的业务逻辑直接写在应用层,会让应用层承担领域层的职责,导致代码耦合度高,后续如果有其他场景需要生成发票编号,容易出现重复代码,维护成本上升。另外原代码存在拼写错误和未定义变量的问题,实际运行会报错。
  • 方案二:更符合DDD(领域驱动设计)规范,把核心业务逻辑封装到领域服务中,应用层仅负责协调资源调用,职责划分更清晰,代码内聚性更强。但同样需要修正原代码中的拼写错误,以及补充发票审核时传递编号的逻辑。

更优实现方式推荐

1. 数据库层面强化并发控制

依赖Timestamp做乐观锁是可行的,但可以利用数据库原子操作或行锁进一步避免并发冲突:

  • 原子递增语句:直接在数据库层面完成编号递增并返回新值,比如SQL Server:
    UPDATE InvoicesCounter 
    SET LastNumber = LastNumber + 1, Timestamp = GETUTCDATE()
    OUTPUT INSERTED.LastNumber;
    
    这种方式能保证编号生成的原子性,避免应用层先读再写导致的并发重复问题。
  • 行级锁:对于支持行锁的数据库(如MySQL InnoDB、PostgreSQL),读取计数器时加锁:
    SELECT LastNumber FROM InvoicesCounter FOR UPDATE;
    
    确保同一时间只有一个事务能修改计数器。

2. 领域对象封装优化

让InvoiceCounter自身负责生成下一个编号,而不是外部调用Increment(),强化领域对象的封装性:

public class InvoiceCounter
{
    public int LastNumber { get; private set; }
    public DateTime Timestamp { get; private set; }

    // 生成下一个编号并更新自身状态
    public int GetNextNumber()
    {
        LastNumber++;
        Timestamp = DateTime.UtcNow;
        return LastNumber;
    }
}

然后领域服务简化为:

public class InvoiceDomainService
{
    public void AcceptInvoice(Invoice invoice, InvoiceCounter counter)
    {
        var nextInvoiceNumber = counter.GetNextNumber();
        invoice.Accept(nextInvoiceNumber);
    }
}

3. 拆分计数器避免单点瓶颈

如果发票量较大,单一行的InvoicesCounter可能成为性能瓶颈,可以按业务维度拆分计数器,比如按年月(InvoicesCounter_202409、InvoicesCounter_202410)、按客户类型等,不同分片的编号独立生成,提升并发处理能力。


内容的提问来源于stack exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 23:35:39