springBoot3 生成订单号的示例代码
目录
- 1 业务背景
- 2 序号生成服务
- 3 原理
- 4 业务中使用
- 4.1 插入数据成功后,更新序号
- 4.2 更新失败,回滚序号、清除缓存
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; /** javascript * 批量生成序列号 * * @param tenantId 租户ID * @param orderType 单据类型 * @param count 需要生成的序列号数量 * @return 序列号列表 */ @Transactional(rollbackFor = Exception.cl编程客栈ass) 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; bJHdxwsD } 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); javascriptif (rows <= 0){ throw new ServiceException("更新发票序号异常!"); } } catch (Exception e){ // 清除序号缓存 generateSequenceService.clearCache(invoice.getTenantId(), invoice.getInvoiceTipo()); throw new ServiceException("更新发票序号异常!异常原因:" + e.getMessage()); } }
到此这篇关于springBoot3 生成订单号的示例代码的文章就介绍到这了,更多相关springBoot3 生成订单号内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论