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的基本步驟,從項目介紹到生產者結合示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
RabbitMQ集群實現(xiàn)消息的高可用性和負載均衡
文章解釋了RabbitMQ集群的概念及其構建目的,詳細闡述了其底層原理,包括Erlang/OTP的分布式基因、ErlangCookie認證和分布式進程管理,感興趣的朋友跟隨小編一起看看吧2026-04-04
使用redisTemplate從redis獲取所有數(shù)據(jù)
這篇文章主要介紹了使用redisTemplate從redis獲取所有數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06

