springBoot3 生成訂單號的示例代碼
1 業(yè)務(wù)背景
財(cái)務(wù)軟件 發(fā)票號碼要求連續(xù)且唯一
2 序號生成服務(wù)
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) {
// 生成無序隨機(jī)序號
return generateSequenceRandom(safeCount,null);
}
// 2 序號前綴
String sequencePrefix = StringUtils.defaultIfEmpty(sequence.getSequencePrefix(), "");
if (Objects.equals(sequence.getSequenceRule(), GenerateSequenceConstants.ORDER_SEQUENCE_RULES_RANDOM))
{
// 生成無序隨機(jī)序號
return generateSequenceRandom(safeCount, sequencePrefix);
}
// 3 根據(jù)日期類型獲取日期字符串
String dateStr = StringUtils.isNull(sequence.getDateType()) ? "" : DateUtils.dateTimeNow(sequence.getDateType());
// 4 當(dāng)前年份
Integer currentYear = LocalDate.now().getYear();
// 5 當(dāng)前月份(保持2位)
String currentMonth = String.format("%02d", LocalDate.now().getMonthValue());
// 為了自動(dòng)更新憑證起始編號:精度添加 年 + 月
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 當(dāng)前年份
Integer currentYear = LocalDate.now().getYear();
// 5 當(dāng)前月份(保持2位)
String currentMonth = String.format("%02d", LocalDate.now().getMonthValue());
// 為了自動(dòng)更新憑證起始編號:精度添加 年 + 月
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;
}
// 構(gòu)建redisKey
private String buildRedisKey(Long tenantId, String orderType, Integer currentYear, String currentMonth) {
return GenerateSequenceConstants.SEQUENCE_KEY + tenantId + ":" + orderType + ":" + currentYear + ":" + currentMonth;
}
/**
* 批量生成隨機(jī)無序序號
* @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 當(dāng)前年份
* @param currentMonth 當(dāng)前月份
* @param incrementBy 自增步長
* @return 返回開始序號 = 當(dāng)前值 + 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;
}
}
/**
* 初始化序列明細(xì)
* @param sequence 序列對象
* @param currentYear 當(dāng)前年度
* @param currentMonth 當(dāng)前月份
* @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("初始化序列明細(xì)失敗,sequenceId={},currentYear={},currentMonth={}", sequence.getSequenceId(), currentYear, currentMonth);
throw new ServiceException("初始化序列明細(xì)失?。?);
}
return 1L;
}
return sequenceDetail.getCurrentNumber() + 1L;
}
/**
* 持久化序列明細(xì)到數(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("更新序列明細(xì)失敗,sequenceId={},currentYear={},currentMonth={},currentNumber={}", sequence.getSequenceId(), currentYear, currentMonth, currentNumber);
throw new ServiceException("更新序列明細(xì)失敗!");
}
}
/**
* 獲取序號配置
*/
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);
}
/**
* 生成一個(gè)序列號
* @param tenantId
* @param orderType
* @return
*/
public String generateSequenceUno(Long tenantId, String orderType){
return generateSequenceBatch(tenantId, orderType, 1L).get(0);
}
/**
* 生成一個(gè)發(fā)票序號
* @param tenantId
* @param orderType
* @return
*/
public InvoiceNo generateInvoiceSequenceUno(Long tenantId, String orderType){
return batchGenerateInvoiceSequence(tenantId, orderType, 1L).get(0);
}
/**
* 預(yù)獲取下一個(gè)序列號
* @param tenantId
* @param orderType
* @return
*/
public String preGetNextSequenceUno(Long tenantId, String orderType){
// 1 獲取序號配置
Sequence sequence = getSequenceConfig(tenantId, orderType);
if (sequence == null) {
throw new ServiceException("獲取序號配置失敗,請?jiān)O(shè)置對應(yīng)單據(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 獲取當(dāng)前序號 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í)間 + 數(shù)值;
2 優(yōu)先redis中獲取;
3 如果redis中沒有,從數(shù)據(jù)庫獲取,然后同步redis;
4 redis更新,同時(shí)同步數(shù)據(jù)庫,實(shí)現(xiàn)持久化!
4 業(yè)務(wù)中使用
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());
}
}
到此這篇關(guān)于springBoot3 生成訂單號的示例代碼的文章就介紹到這了,更多相關(guān)springBoot3 生成訂單號內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC整合Shiro權(quán)限控制的方法
這篇文章主要介紹了Spring MVC整合Shiro權(quán)限控制,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
JAVA設(shè)計(jì)模式之訪問者模式原理與用法詳解
這篇文章主要介紹了JAVA設(shè)計(jì)模式之訪問者模式,簡單說明了訪問者模式的原理,并結(jié)合實(shí)例分析了java訪問者模式的定義與用法,需要的朋友可以參考下2017-08-08
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之紅黑樹
紅黑樹的應(yīng)用比較廣泛,主要是用它來存儲(chǔ)有序的數(shù)據(jù),它的時(shí)間復(fù)雜度是O(lgn),效率非常之高。例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虛擬內(nèi)存的管理,都是通過紅黑樹去實(shí)現(xiàn)的2022-02-02
詳解Java 包掃描實(shí)現(xiàn)和應(yīng)用(Jar篇)
這篇文章主要介紹了詳解Java 包掃描實(shí)現(xiàn)和應(yīng)用(Jar篇),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
maven的pom.xml中repositories和distributionManagement使用
這篇文章主要介紹了maven的pom.xml中repositories和distributionManagement使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
淺析Java中關(guān)鍵詞volatile底層的實(shí)現(xiàn)原理
在 Java 并發(fā)編程中,有 3 個(gè)最常用的關(guān)鍵字:synchronized、ReentrantLock 和 volatile,這篇文章主要來和大家聊聊volatile底層的實(shí)現(xiàn)原理,感興趣的可以了解下2024-02-02

