最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

springBoot3 生成訂單號的示例代碼

 更新時間:2025年10月22日 16:29:00   作者:xiguolangzi  
本文主要介紹了springBoot3 生成訂單號的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

1 業(yè)務背景

財務軟件 發(fā)票號碼要求連續(xù)且唯一

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 單據(jù)類型
     * @param count     需要生成的序列號數(shù)量
     * @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 根據(jù)日期類型獲取日期字符串
        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);
        // 如果是 年 規(guī)則模式,則月份只能是"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;

        // 持久化更新數(shù)據(jù)庫
        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;
    }


    /**
     * 批量生成 發(fā)票序列號
     *
     * @param tenantId  租戶ID
     * @param orderType 單據(jù)類型
     * @param count     需要生成的序列號數(shù)量
     * @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("請檢查發(fā)票序號配置!");
        }

        // 2 序號前綴
        String sequencePrefix = StringUtils.defaultIfEmpty(sequence.getSequencePrefix(), "");
        if (!Objects.equals(sequence.getSequenceRule(), GenerateSequenceConstants.ORDER_SEQUENCE_RULES_YEAR_CONTINUOUS))
        {
            throw new ServiceException("請檢查發(fā)票序號配置規(guī)則!發(fā)票序號規(guī)則必須是年連續(xù)!");
        }

        // 3 根據(jù)日期類型獲取日期字符串
        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);
        // 如果是 年 規(guī)則模式,則月份只能是"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;

        // 持久化更新數(shù)據(jù)庫
        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;
    }

    /**
     * 持久化序列明細到數(shù)據(jù)庫
     */
    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)){
            // 更正發(fā)票類型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);
    }

    /**
     * 生成一個發(fā)票序號
     * @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("獲取序號配置失敗,請設置對應單據(jù)的序號配置!");
        }
        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 單據(jù)類型
     */
    public void clearCache(long tenantId, String orderType) {
        log.info("清除單據(jù)類型:{}, 的緩存序號", orderType);
        String redisPrefix = GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":*";
        Set<String> keys = stringRedisTemplate.keys(redisPrefix);
        if (keys != null && !keys.isEmpty()) {
            stringRedisTemplate.delete(keys);
        }
    }

}

3 原理

1 根據(jù)客戶配置獲取序號的 前綴 + 時間 + 數(shù)值;
2 優(yōu)先redis中獲取;
3 如果redis中沒有,從數(shù)據(jù)庫獲取,然后同步redis;
4 redis更新,同時同步數(shù)據(jù)庫,實現(xiàn)持久化!

4 業(yè)務中使用

4.1 插入數(shù)據(jù)成功后,更新序號

// 3 插入數(shù)據(jù)
int rows = verifacInvoiceMapper.insertVerifacInvoice(verifacInvoice);
insertVerifacInvoiceDetail(verifacInvoice);
if (rows > 0){
    // 4 更新發(fā)票編號
    updateInvoiceNo(verifacInvoice);
}

4.2 更新失敗,回滾序號、清除緩存

// 更新發(fā)票號
private void updateInvoiceNo(VerifacInvoice invoice) {
    try {
        log.info( "訂單號:{},生成發(fā)票,開始更新發(fā)票號...", invoice.getOrderInitNo());
        if (invoice.getTenantId() == null || invoice.getInvoiceTipo() == null){
            throw new ServiceException("獲取發(fā)票序號,發(fā)票信息不完整!獲取失敗!");
        }
        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( "訂單號:{},生成發(fā)票,更新發(fā)票號成功:{}", invoice.getOrderInitNo(), invoice.getInvoiceSerie() + invoice.getInvoiceNumero());
        }
        // 檢查發(fā)票號的唯一性
        checkInvoiceNoUnique(invoice);
        int rows = verifacInvoiceMapper.updateVerifacInvoiceNo(invoice);
        if (rows <= 0){
            throw new ServiceException("更新發(fā)票序號異常!");
        }
    } catch (Exception e){
        // 清除序號緩存
        generateSequenceService.clearCache(invoice.getTenantId(), invoice.getInvoiceTipo());
        throw new ServiceException("更新發(fā)票序號異常!異常原因:" + e.getMessage());
    }
}

到此這篇關于springBoot3 生成訂單號的示例代碼的文章就介紹到這了,更多相關springBoot3 生成訂單號內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringCloud?Stream?整合RabbitMQ的基本步驟

    SpringCloud?Stream?整合RabbitMQ的基本步驟

    這篇文章主要介紹了SpringCloud?Stream?整合RabbitMQ的基本步驟,從項目介紹到生產者結合示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • java操作hdfs的方法示例代碼

    java操作hdfs的方法示例代碼

    這篇文章主要介紹了java操作hdfs的相關資料,在本地配置Hadoop和Maven的環(huán)境變量,首先需從官網(wǎng)下載與服務器相同版本的Hadoop安裝包,配置環(huán)境變量后,引入Maven的配置文件,以便管理項目依賴,最后,編寫代碼實現(xiàn)對HDFS的連接和操作,完成數(shù)據(jù)的讀寫,需要的朋友可以參考下
    2022-02-02
  • RabbitMQ集群實現(xiàn)消息的高可用性和負載均衡

    RabbitMQ集群實現(xiàn)消息的高可用性和負載均衡

    文章解釋了RabbitMQ集群的概念及其構建目的,詳細闡述了其底層原理,包括Erlang/OTP的分布式基因、ErlangCookie認證和分布式進程管理,感興趣的朋友跟隨小編一起看看吧
    2026-04-04
  • 動態(tài)代理模擬實現(xiàn)aop的示例

    動態(tài)代理模擬實現(xiàn)aop的示例

    下面小編就為大家?guī)硪黄獎討B(tài)代理模擬實現(xiàn)aop的示例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望對大家有所幫助
    2017-11-11
  • Struts2學習筆記(4)-通配符的使用

    Struts2學習筆記(4)-通配符的使用

    本文主要介紹Struts2中通配符的使用,簡單實用,希望能給大家做一個參考。
    2016-06-06
  • 使用Java編寫一個文件批量重命名工具

    使用Java編寫一個文件批量重命名工具

    這篇文章主要為大家詳細介紹了如何使用Java編寫一個文件批量重命名工具,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-12-12
  • 淺析java貪心算法

    淺析java貪心算法

    這篇文章簡單主要介紹了java貪心算法,包含貪心算法的基本思路,性質,以及實現(xiàn)示例,有需要的小伙伴參考下
    2015-02-02
  • 詳解HDFS多文件Join操作的實例

    詳解HDFS多文件Join操作的實例

    這篇文章主要介紹了詳解HDFS多文件Join操作的實例的相關資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內容,需要的朋友可以參考下
    2017-10-10
  • Springboot 全局時間格式化三種方式示例詳解

    Springboot 全局時間格式化三種方式示例詳解

    時間格式化在項目中使用頻率是非常高的,當我們的 API? 接口返回結果,需要對其中某一個 date? 字段屬性進行特殊的格式化處理,通常會用到 SimpleDateFormat? 工具處理,這篇文章主要介紹了3 種 Springboot 全局時間格式化方式,需要的朋友可以參考下
    2024-01-01
  • 使用redisTemplate從redis獲取所有數(shù)據(jù)

    使用redisTemplate從redis獲取所有數(shù)據(jù)

    這篇文章主要介紹了使用redisTemplate從redis獲取所有數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06

最新評論

绥中县| 富川| 织金县| 阳信县| 宜兴市| 武功县| 金湖县| 乌兰察布市| 黄石市| 离岛区| 东山县| 济源市| 南昌市| 深州市| 离岛区| 南岸区| 富顺县| 阳新县| 周宁县| 疏附县| 平顶山市| 虹口区| 长阳| 仪陇县| 兴隆县| 徐闻县| 辽宁省| 汝南县| 棋牌| 沙河市| 广昌县| 桂平市| 灵台县| 扬中市| 永川市| 高雄县| 罗江县| 什邡市| 全椒县| 长汀县| 辽阳市|