业务系统中通常存在生成具有业务含义的流水号功能,该流水号要求唯一,通常由以下三部分组成:具备业务含义的字符串(可选)+年月日(年或年月或年月日时等,可选)+若干位流水号(从1开始),如HT201910080003,表示的是2019年10月8日拟定的第3份合同。
流水号的设置由IT管理员进行维护和管理。
流水号跟常规实体还是有一定差别,一定程度上从属于实体,怎么来管理,主要有两种方案,具体如下:
方案1:独立管理,配置流水号规则,然后实体模型配置时选择流水号实体记录进行关联
方案2:实体配置时进行配置,从属于实体模型
这两种方案各有优缺点。方案1需要是在实体配置前,先创建该实体对应的流水号规则,先后次序上略别扭。方案2是按照常规次序进行,但是完全变成了实体模型的从属,不再有独立的查看页面。
从业务功能而言,流水号的配置需要设置前缀、时间段和流水号,在实体模型配置过程中进行流水号的配置,有一点重,单独配置,作为前置工作更合理一些,实体模型配置时直接使用即可。
此外,流水号的记录不仅仅记录了生成规则,同时还记录了当前流水号,并且会按照重置规则去重置,当系统异常排查问题时,通过独立的查看页面去查看,如果是从属,打开实体配置去看流水号当前规则及值,反而操作上显得诡异。
特别重要的一点是,流水号规则会随着业务变化调整,变动频率要远高于实体自身配置,单独维护更好一些。
还有一种业务场景,就是多个单据公用一个流水号,这时候,如果使用方案2从属业务实体模式,需求上无法满足,功能上无法实现。
综上,采用方案1,还是独立管理,在实体配置前预配置而已,不算问题。这种情况下,流水号就变成了普通的实体了,有独立的菜单,进行列表展示,增删改查。
新增:创建单据流水号数据记录,配置流水号生成规则及重置策略
编辑:对单据流水号进行调整,只对将来需要生成的流水号起作用,不追溯调整已经生成的历史单据流水号
删除:逻辑删除
查询:根据单据类型和单据名称查询流水号生成规则
最多三段,分别为前缀(可选)+日期时间格式字符串(,可选)+若干位流水号,可选择连接字符串,默认为空字符串
前缀:具备业务含义的字符串,通常为业务单据的英文单词或汉语拼音首字母缩写
日期时间格式字符串:用户可以灵活输入符合yyyyMMddHHmmss格式的字符串或符合部分起始相同的字符串(如仅输入yyyyMMdd),由程序根据当前时间进行转换
流水号:从1开始编号,位数不足前面补零
获取单个流水号:后端服务,传入单据类型,获取单个流水号
批量获取流水号:后端服务,传入单据类型和数量,获取指定数量的流水号
重置流水号:根据重置策略,自动将流水号号重置为1
按年、按月、按日,使用任务调度功能实现
注:不考虑按小时等更细的时间维度,业务上通常到不了这么细
package tech.abc.platform.support.scheduler;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import tech.abc.platform.common.annotation.SystemLog;
import tech.abc.platform.common.constant.CommonConstant;
import tech.abc.platform.common.enums.LogTypeEnum;
import tech.abc.platform.support.service.SerialNoService;
/**
* 重置流水号
*
* @author wqliu
* @date 2023-05-30
*/
@Component
@Slf4j
@DisallowConcurrentExecution
public class ResetSerialNo extends QuartzJobBean {
@Autowired
private SerialNoService serialNoService;
@Override
@SystemLog(value = "重置流水号", logType = LogTypeEnum.SCHEDULER, logRequestParam = false, executeResult =
CommonConstant.NO)
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
serialNoService.resetSerialNo();
} catch (Exception e) {
log.error("重置流水号失败", e);
throw new JobExecutionException(e);
}
}
}
作为业务单据的唯一性编号,流水号需要保证不重复,否则会严重影响业务和系统正常运行。这里技术方案上并没有使用传统加锁方式,主要是基于性能方面的考虑。使用乐观锁+自动重试机制,来进行流水号的生成。乐观锁是平台通过深度集成MybatisPlus的实现的,不需要额外处理。自动重试使用了Spring的Retry功能组件。
添加组件依赖
<!--重试组件-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
在需要重试的类上加注解@Retryable
/**
* 批量获取流水号
* @param code 编码
* @param count 数量
* @return {@link List}<{@link String}>
*/
@Override
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 100L, multiplier = 2))
public List<String> generateBatch(String code, int count) {
// 先根据编码获取当前流水号
SerialNo entity = this.lambdaQuery().eq(SerialNo::getCode, code).one();
if (entity == null) {
throw new CustomException(SerialNoExceptionEnum.CODE_NOT_EXIST);
}
int currentSerialNo = entity.getCurrentValue();
// 计算更新后的流水号
int updateSerialNo = currentSerialNo + count;
// 更新数据
entity.setCurrentValue(updateSerialNo);
boolean updateFlag = modify(entity);
if (!updateFlag) {
throw new CustomException(CommonException.UPDATE_ERROR);
}
// 组织返回
String template = generateNoTemplate(entity);
List<String> result = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
result.add(String.format(template, StringUtils.leftPad(String.valueOf(currentSerialNo), entity.getLength(),
'0')));
currentSerialNo++;
}
return result;
}
注解参数及含义如下:
- value:抛出指定异常才会重试
- include:和value一样,默认为空,当exclude也为空时,默认所有异常
- exclude:指定不处理的异常
- maxAttempts:最大重试次数,默认3次
- backoff:重试等待策略,
- 默认使用@Backoff,@Backoff的value默认为1000L,我们设置为100; 以毫秒为单位的延迟(默认 1000)
- multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
在同一个类中,需要增加一个@Recover注解的方法,用于达到最大重试次数后处理。
/**
* 达到最大重试次数
*
* @param e 异常
*/
@Recover
public List<String> recoverBatch(Exception e) {
log.error("生成单据流水号达到最大重试次数", e);
throw new CustomException(CommonException.TRY_MAX_COUNT);
}
最后,记得需要在SpringBoot启动类上加上注解@EnableRetry。
平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
欢迎收藏、点赞、评论,你的支持是我前行的动力。