java
复制代码
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);
}
}
}