springBoot3 生成订单号的示例代码

 更新时间:2025年10月22日 16:29:00   作者:xiguolangzi  
本文主要介绍了springBoot3 生成订单号的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1 业务背景

财务软件 发票号码要求连续且唯一

2 序号生成服务

package com.okyun.sequence.service.impl;


@Service
@Slf4j
@RequiredArgsConstructor
public class GenerateSequenceService {

    private final StringRedisTemplate stringRedisTemplate;
    private final SequenceMapper sequenceMapper;
    private final SnowflakeIdGenerator snowflakeIdGenerator;
    private final SequenceCacheManager  sequenceCacheManager;

    /**
     * 批量生成序列号
     *
     * @param tenantId  租户ID
     * @param orderType 单据类型
     * @param count     需要生成的序列号数量
     * @return 序列号列表
     */
    @Transactional(rollbackFor = Exception.class)
    public List<String> generateSequenceBatch(Long tenantId, String orderType, Long count) {
        long safeCount = (count == null || count <= 0) ? 1 : count;
        // 1 获取序号配置
        Sequence sequence = getSequenceConfig(tenantId, orderType);
        if (sequence == null || sequence.getSequenceRule() == null) {
            // 生成无序随机序号
            return  generateSequenceRandom(safeCount,null);
        }

        // 2 序号前缀
        String sequencePrefix = StringUtils.defaultIfEmpty(sequence.getSequencePrefix(), "");
        if (Objects.equals(sequence.getSequenceRule(), GenerateSequenceConstants.ORDER_SEQUENCE_RULES_RANDOM))
        {
            // 生成无序随机序号
            return  generateSequenceRandom(safeCount, sequencePrefix);
        }

        // 3 根据日期类型获取日期字符串
        String dateStr = StringUtils.isNull(sequence.getDateType()) ? "" : DateUtils.dateTimeNow(sequence.getDateType());

        // 4 当前年份
        Integer currentYear = LocalDate.now().getYear();
        // 5 当前月份(保持2位)
        String currentMonth = String.format("%02d", LocalDate.now().getMonthValue());
        // 为了自动更新凭证起始编号:精度添加 年 + 月
        String redisKey = buildRedisKey(tenantId, orderType, currentYear, currentMonth);
        // 如果是 年 规则模式,则月份只能是"00"
        if (Objects.equals( GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS, sequence.getSequenceRule()))
        {
            redisKey = buildRedisKey(tenantId, orderType, currentYear, GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
        }

        // 获取或初始化 Redis 的序列值
        long startNumber = initializeOrFetchRedisKey(redisKey, sequence, currentYear, currentMonth, safeCount);
        long endNumber = startNumber + safeCount - 1;

        // 持久化更新数据库
        persistSequenceDetail(sequence, currentYear, currentMonth, endNumber);

        // 生成序列号
        List<String> sequenceList = new ArrayList<>();
        for (long i = startNumber; i <= endNumber; i++) {
            String formattedNumber = String.format("%06d", i);
            sequenceList.add(sequencePrefix + dateStr + formattedNumber);
        }

        return sequenceList;
    }


    /**
     * 批量生成 发票序列号
     *
     * @param tenantId  租户ID
     * @param orderType 单据类型
     * @param count     需要生成的序列号数量
     * @return 序列号列表
     */
    @Transactional(rollbackFor = Exception.class)
    public List<InvoiceNo> batchGenerateInvoiceSequence(Long tenantId, String orderType, Long count) {
        long safeCount = (count == null || count <= 0) ? 1 : count;
        // 1 获取序号配置
        Sequence sequence = getSequenceConfig(tenantId, orderType);
        if (sequence == null || sequence.getSequenceRule() == null) {
            throw new ServiceException("请检查发票序号配置!");
        }

        // 2 序号前缀
        String sequencePrefix = StringUtils.defaultIfEmpty(sequence.getSequencePrefix(), "");
        if (!Objects.equals(sequence.getSequenceRule(), GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS))
        {
            throw new ServiceException("请检查发票序号配置规则!发票序号规则必须是年连续!");
        }

        // 3 根据日期类型获取日期字符串
        String dateStr = StringUtils.isNull(sequence.getDateType()) ? "" : DateUtils.dateTimeNow(sequence.getDateType());

        // 4 当前年份
        Integer currentYear = LocalDate.now().getYear();
        // 5 当前月份(保持2位)
        String currentMonth = String.format("%02d", LocalDate.now().getMonthValue());
        // 为了自动更新凭证起始编号:精度添加 年 + 月
        String redisKey = buildRedisKey(tenantId, orderType, currentYear, currentMonth);
        // 如果是 年 规则模式,则月份只能是"00"
        if (Objects.equals( GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS, sequence.getSequenceRule()))
        {
            redisKey = buildRedisKey(tenantId, orderType, currentYear, GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
        }

        // 获取或初始化 Redis 的序列值
        long startNumber = initializeOrFetchRedisKey(redisKey, sequence, currentYear, currentMonth, safeCount);
        long endNumber = startNumber + safeCount - 1;

        // 持久化更新数据库
        persistSequenceDetail(sequence, currentYear, currentMonth, endNumber);

        // 生成序列号
        List<InvoiceNo> sequenceList = new ArrayList<>();
        for (long i = startNumber; i <= endNumber; i++) {
            InvoiceNo invoiceNo = new InvoiceNo();
            invoiceNo.setInvoiceSerie(sequencePrefix + dateStr);
            invoiceNo.setInvoiceNumero(i);
            sequenceList.add(invoiceNo);
        }
        return sequenceList;
    }

    // 构建redisKey
    private String buildRedisKey(Long tenantId, String orderType, Integer currentYear, String currentMonth) {
        return GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":" + currentYear + ":" + currentMonth;
    }

    /**
     * 批量生成随机无序序号
     * @param count
     */
    private List<String> generateSequenceRandom(long count, String sequencePrefix) {
        List<String> sequenceNoList = new ArrayList<>();
        for (int i = 0; i < count; i++)
        {
            String sequenceNo = snowflakeIdGenerator.generateStringId();
            sequenceNoList.add(sequencePrefix + sequenceNo);
        }
        return sequenceNoList;
    }

    /**
     * 初始化或获取 Redis 键值
     * @param redisKey Redis 键
     * @param sequence  序号对象
     * @param currentYear 当前年份
     * @param currentMonth 当前月份
     * @param incrementBy 自增步长
     * @return 返回开始序号 = 当前值 + 1 或 初始值 1
     */
    private Long initializeOrFetchRedisKey(String redisKey, Sequence sequence, Integer currentYear, String currentMonth, long incrementBy) {
        Long currentNumber;
        if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(redisKey))) {
            // 如果 Redis 中没有此 Key,初始化值
            currentNumber = initializeSequenceDetail(sequence, currentYear, currentMonth,  incrementBy);
            long redisValue = currentNumber - 1L + incrementBy;
            stringRedisTemplate.opsForValue().set(redisKey, String.valueOf(redisValue));
            stringRedisTemplate.expire(redisKey, 365, TimeUnit.DAYS);
            return currentNumber;
        } else {
            // 如果 Key 存在,自增
            currentNumber = stringRedisTemplate.opsForValue().increment(redisKey, incrementBy);
            if (currentNumber == null) {
                throw new ServiceException("Redis 自增操作失败!");
            }
            return currentNumber - incrementBy + 1;
        }
    }

    /**
     * 初始化序列明细
     * @param sequence  序列对象
     * @param currentYear 当前年度
     * @param currentMonth 当前月份
     * @return 开始值 或 初始1
     */
    private Long initializeSequenceDetail(Sequence sequence, Integer currentYear, String currentMonth, long incrementBy) {
        SequenceDetail sequenceDetail = null;
        if (Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_MONTH_CONTINUOUS, sequence.getSequenceRule()) ) {
            sequenceDetail = sequenceMapper.selectSequenceDetailByIdAndCurrentYearAndCurrentMonth(sequence.getSequenceId(), currentYear, currentMonth);
        } else if (Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS, sequence.getSequenceRule()) ) {
            sequenceDetail = sequenceMapper.selectSequenceDetailByIdAndCurrentYearAndCurrentMonth(sequence.getSequenceId(), currentYear, GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
        }

        if (sequenceDetail == null)
        {
            sequenceDetail = new SequenceDetail();
            sequenceDetail.setSequenceId(sequence.getSequenceId());
            sequenceDetail.setPeriodYear(currentYear);
            sequenceDetail.setPeriodMonth( Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_MONTH_CONTINUOUS, sequence.getSequenceRule())  ? currentMonth : GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
            sequenceDetail.setCurrentNumber(incrementBy);

            int res = sequenceMapper.insertSequenceDetail(sequenceDetail);
            if (res <= 0)
            {
                log.error("初始化序列明细失败,sequenceId={},currentYear={},currentMonth={}", sequence.getSequenceId(), currentYear, currentMonth);
                throw new ServiceException("初始化序列明细失败!");
            }

            return 1L;
        }
        return sequenceDetail.getCurrentNumber() + 1L;
    }

    /**
     * 持久化序列明细到数据库
     */
    private void persistSequenceDetail(Sequence sequence, Integer currentYear, String currentMonth, Long currentNumber) {
        SequenceDetail sequenceDetail = new SequenceDetail();
        sequenceDetail.setSequenceId(sequence.getSequenceId());
        sequenceDetail.setPeriodYear(currentYear);
        sequenceDetail.setPeriodMonth(Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_MONTH_CONTINUOUS, sequence.getSequenceRule())  ? currentMonth : GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
        sequenceDetail.setCurrentNumber(currentNumber);

        int res = sequenceMapper.updateSequenceDetail(sequenceDetail);
        if (res <= 0) {
            log.error("更新序列明细失败,sequenceId={},currentYear={},currentMonth={},currentNumber={}", sequence.getSequenceId(), currentYear, currentMonth, currentNumber);
            throw new ServiceException("更新序列明细失败!");
        }
    }

    /**
     * 获取序号配置
     */
    private Sequence getSequenceConfig(Long tenantId, String orderType) {
        if (GenerateSequenceConstants.isInvoiceR1Type(orderType)){
            // 更正发票类型R1-R5 都使用 R1 的配置
            orderType = GenerateSequenceConstants.ORDER_SEQUENCE_TYPE_INVOICE_R1;
        }
        return sequenceCacheManager.getSequenceCache(tenantId, orderType);
    }

    /**
     * 生成一个序列号
     * @param tenantId
     * @param orderType
     * @return
     */
    public String generateSequenceUno(Long tenantId, String orderType){
        return generateSequenceBatch(tenantId, orderType, 1L).get(0);
    }

    /**
     * 生成一个发票序号
     * @param tenantId
     * @param orderType
     * @return
     */
    public InvoiceNo generateInvoiceSequenceUno(Long tenantId, String orderType){
        return batchGenerateInvoiceSequence(tenantId, orderType, 1L).get(0);
    }

    /**
     * 预获取下一个序列号
     * @param tenantId
     * @param orderType
     * @return
     */
    public String preGetNextSequenceUno(Long tenantId, String orderType){
        // 1 获取序号配置
        Sequence sequence = getSequenceConfig(tenantId, orderType);
        if (sequence == null) {
            throw new ServiceException("获取序号配置失败,请设置对应单据的序号配置!");
        }
        Integer sequenceRule = sequence.getSequenceRule();
        Integer currentYear = LocalDate.now().getYear();
        String currentMonth = String.format("%02d", LocalDate.now().getMonthValue());
        String dateType = sequence.getDateType();
        String sequencePrefix = StringUtils.defaultIfEmpty(sequence.getSequencePrefix(), "");
        String date = StringUtils.isNull(dateType) ? "" : DateUtils.dateTimeNow(dateType);

        Long nextSequenceNo = 1l;
        // 2 获取当前序号 redis 键
        String redisKey = GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":" + currentYear + ":" + currentMonth;
        if (Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS, sequenceRule) )
        {
            redisKey = GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":" + currentYear + ":" + GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH;
        }


        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(redisKey)))
        {
            String currentValue = stringRedisTemplate.opsForValue().get(redisKey);
            if (currentValue != null)
            {
                nextSequenceNo = Long.parseLong(currentValue) + 1;
            }
        }
        else
        {
            SequenceDetail sequenceDetail = null;
            if (Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_MONTH_CONTINUOUS, sequenceRule)) {
                sequenceDetail = sequenceMapper.selectSequenceDetailByIdAndCurrentYearAndCurrentMonth(sequence.getSequenceId(), currentYear, currentMonth);
            } else if (Objects.equals(GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS, sequenceRule) ) {
                sequenceDetail = sequenceMapper.selectSequenceDetailByIdAndCurrentYearAndCurrentMonth(sequence.getSequenceId(), currentYear, GenerateSequenceConstants.SEQUENCE_ORDER_YEAR_MONTH);
            }
            if (sequenceDetail != null)
            {
                nextSequenceNo = sequenceDetail.getCurrentNumber() + 1;
            }
        }

        // 返回拼接后的序列号码
        return sequencePrefix + date + String.format("%06d", nextSequenceNo);

    }

    /**
     * 更新序号失败 - 清理redis 中缓存的序列号
     * @param tenantId 租户ID
     * @param orderType 单据类型
     */
    public void clearCache(long tenantId, String orderType) {
        log.info("清除单据类型:{}, 的缓存序号", orderType);
        String redisPrefix = GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":*";
        Set<String> keys = stringRedisTemplate.keys(redisPrefix);
        if (keys != null && !keys.isEmpty()) {
            stringRedisTemplate.delete(keys);
        }
    }

}

3 原理

1 根据客户配置获取序号的 前缀 + 时间 + 数值;
2 优先redis中获取;
3 如果redis中没有,从数据库获取,然后同步redis;
4 redis更新,同时同步数据库,实现持久化!

4 业务中使用

4.1 插入数据成功后,更新序号

// 3 插入数据
int rows = verifacInvoiceMapper.insertVerifacInvoice(verifacInvoice);
insertVerifacInvoiceDetail(verifacInvoice);
if (rows > 0){
    // 4 更新发票编号
    updateInvoiceNo(verifacInvoice);
}

4.2 更新失败,回滚序号、清除缓存

// 更新发票号
private void updateInvoiceNo(VerifacInvoice invoice) {
    try {
        log.info( "订单号:{},生成发票,开始更新发票号...", invoice.getOrderInitNo());
        if (invoice.getTenantId() == null || invoice.getInvoiceTipo() == null){
            throw new ServiceException("获取发票序号,发票信息不完整!获取失败!");
        }
        if (invoice.getInvoiceNumero() == null || invoice.getInvoiceSerie() == null){
            InvoiceNo invoiceNo = generateSequenceService.generateInvoiceSequenceUno(invoice.getTenantId(), invoice.getInvoiceTipo());
            invoice.setInvoiceSerie(invoiceNo.getInvoiceSerie());
            invoice.setInvoiceNumero(invoiceNo.getInvoiceNumero());
            log.info( "订单号:{},生成发票,更新发票号成功:{}", invoice.getOrderInitNo(), invoice.getInvoiceSerie() + invoice.getInvoiceNumero());
        }
        // 检查发票号的唯一性
        checkInvoiceNoUnique(invoice);
        int rows = verifacInvoiceMapper.updateVerifacInvoiceNo(invoice);
        if (rows <= 0){
            throw new ServiceException("更新发票序号异常!");
        }
    } catch (Exception e){
        // 清除序号缓存
        generateSequenceService.clearCache(invoice.getTenantId(), invoice.getInvoiceTipo());
        throw new ServiceException("更新发票序号异常!异常原因:" + e.getMessage());
    }
}

到此这篇关于springBoot3 生成订单号的示例代码的文章就介绍到这了,更多相关springBoot3 生成订单号内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring MVC整合Shiro权限控制的方法

    Spring MVC整合Shiro权限控制的方法

    这篇文章主要介绍了Spring MVC整合Shiro权限控制,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java设计模式之建造者模式浅析示例

    Java设计模式之建造者模式浅析示例

    建造者模式,是一种对象构建模式 它可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同表现的对象。本文将通过示例讲解建造者模式,需要的可以参考一下
    2022-11-11
  • JAVA设计模式之访问者模式原理与用法详解

    JAVA设计模式之访问者模式原理与用法详解

    这篇文章主要介绍了JAVA设计模式之访问者模式,简单说明了访问者模式的原理,并结合实例分析了java访问者模式的定义与用法,需要的朋友可以参考下
    2017-08-08
  • Java 数据结构与算法系列精讲之红黑树

    Java 数据结构与算法系列精讲之红黑树

    红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的
    2022-02-02
  • 详解Java 包扫描实现和应用(Jar篇)

    详解Java 包扫描实现和应用(Jar篇)

    这篇文章主要介绍了详解Java 包扫描实现和应用(Jar篇),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java日期时间使用方法汇总

    Java日期时间使用方法汇总

    这篇文章主要针对Java日期时间使用方法进行汇总,感兴趣的朋友可以参考一下
    2016-03-03
  • maven的pom.xml中repositories和distributionManagement使用

    maven的pom.xml中repositories和distributionManagement使用

    这篇文章主要介绍了maven的pom.xml中repositories和distributionManagement使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Maven配置多仓库无效的解决

    Maven配置多仓库无效的解决

    在项目中使用Maven管理jar包依赖往往会出现很多问题,所以这时候就需要配置Maven多仓库,本文介绍了如何配置以及问题的解决
    2021-05-05
  • Java实现手写线程池的示例代码

    Java实现手写线程池的示例代码

    在我们的日常的编程当中,并发是始终离不开的主题,而在并发多线程当中,线程池又是一个不可规避的问题。本文就来分享一下如何自己手写一个线程池,需要的可以参考一下
    2022-08-08
  • 浅析Java中关键词volatile底层的实现原理

    浅析Java中关键词volatile底层的实现原理

    在 Java 并发编程中,有 3 个最常用的关键字:synchronized、ReentrantLock 和 volatile,这篇文章主要来和大家聊聊volatile底层的实现原理,感兴趣的可以了解下
    2024-02-02

最新评论