介绍
本次采用mysql处理,性能不是很好,对于高并发有要求的建议不要采用
公司一个小项目,需要生成一个单据号,格式为: 日期 + 每日重新自增号,自己考虑了一下每日自增需要考虑并发和持久问题,两种数据库redis和mysql由于项目较小,所以没有redis因为这个增加一个redis好像有点不值得,所以采用mysql作为持久化处理,一下思路也是借鉴了网上的许多想法
源码
源码查看规则
源码位置: blog-study:module-utils:work-no
欢迎大家随时批评指正
思路
- 首先根据需求,需要这样一个格式: 20200101 + 00001 的单据号
- 分析日期只需要获取当前时间格式化即可简单,
自增号:
1)需要考虑每日重新开始
2)宕机数据可以持久化(存库即可)
3)并发获取单据号唯一
以上四个问题只需要考虑 每日重新开始计数和并发问题 - 每日重新开始,在数据库中加一个时间标志,每次获取都与当前时间做对比然后做相应处理, 并发问题我能想到的就是加锁(编程锁或者数据库锁,我这里采用lock锁)
- 以上基本可以解决项目问题了,然后思考是否可以用于其它业务号的生成
1)于是想可以增加业务标识、前缀和后缀来满足不同业务,其中业务标识不参与单据号组成只是标识某一类单据号,这样可以保证不同业务可以使用相同的单据号,前缀是根据业务需要标识业务单据号的参与单据号生成, 后缀的作用是干扰随机数,可以尽可能防止他人直接看出来业务单据每日的生成数量
格式: GX + 20200101 + 00001 + 23
2) 每日重新开始计数,是否可以改为可选择的,每月重新计数,每年重新计数,或者一直不需要重新计数 - 思考是否可以封装为组件,引入依赖直接就可以使用,想了一下自己目前还不具备这种能力吧,以后还要多学多了解,也希望大家可以多多指点,小子将不胜感激。
功能介绍
- 数据库结构(使用实体类代替)
/** * 业务唯一标识(只是作为唯一标识,并不参与单据号的生成) */ private String workNo; /** * 业务码前缀 */ private String prefix; /** * 序号 */ private Integer serialNum; /** * 序号长度 */ private Integer serialLength; /** * 序号之后的随机数长度(干扰串) */ private Integer randomLength; /** * 重置模式(0:无需重置,1: 按天重置, 2: 按月重置, 3: 按年重置) */ private Integer restType; /** * 重置时间(检测是否需要重置使用) */ private LocalDate restTime; /** * 创建时间 */ private LocalDateTime createTime; /** * 修改时间 */ private LocalDateTime updateTime; /** * 备注 */ private String remark;
- 业务号格式使用枚举规定好,每次启动项目自动识别处理
/** * 为了保证每次生成的单号一致性,所以初始化作为第一次初始化成功后不再修改 */ ORDER_NO("XG", "XG", 5, 2, 1, "小郭测试单据号");
/** * 业务唯一标识(只是作为唯一标识,并不参与单据号的生成) */ private String workNo; /** * 业务码前缀 */ private String prefix; /** * 序号长度 */ private Integer serialLength; /** * 序号之后的随机数长度(干扰串)0代表没有干扰 */ private Integer randomLength; /** * 重置模式(0:无需重置,1: 按天重置, 2: 按月重置, 3: 按年重置) */ private Integer restType; /** * 备注 */ private String remark;
项目启动初始化:
@Configurationpublic class WorkNoInit implements ApplicationRunner { @Autowired private WorkNoDao workNoDao;
/** * 单据号初始化 * * @param args * @throws Exception */ @Override public void run(ApplicationArguments args) throws Exception { WorkNoInfo workNoInfo = null; for (WorkInfoEnum workInfoEnum : WorkInfoEnum.values()) { workNoInfo = new WorkNoInfo(workInfoEnum); //查询是否已有该业务单据号(没有初始化) if (workNoDao.isHaveWorkNo(workNoInfo.getWorkNo()) == 0) { workNoDao.addOrderNumInfo(workNoInfo); } } }}
- 获取单据号:
@Componentpublic class WorkNoService {
@Autowired private WorkNoDao workNoDao;
private static Lock lock = new ReentrantLock();
/** * 获取业务序列码 * * 流程: * 1. 加锁保证线程安全 * 2. 查询数据库中的业务信息 * 3. 判断重置模式修改数据库中相应的数据 * 4. 返回业务码 * * @param workInfoEnum 枚举 * @return 业务序列码 */ public String getOrderNo(WorkInfoEnum workInfoEnum) { lock.lock(); LocalDateTime localDateTime = LocalDateTime.now(); try { WorkNoInfo workNoInfo = workNoDao.queryByWorkNo(workInfoEnum.getWorkNo()); if (workNoInfo == null) { throw new ErrorCodeException(500, "查询生成业务码基础数据失败!"); } //序号 Integer serialNum = workNoInfo.getSerialNum(); //判断是否需要重置序号 if (workNoInfo.getRestType() == 1) { String dayTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")); String dataBaseTime = workNoInfo.getRestTime().format(DateTimeFormatter.ofPattern("yyyyMMdd")); if (!dataBaseTime.equals(dayTime)) { serialNum = 0; } } else if (workNoInfo.getRestType() == 2) { String monthTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMM")); String dataBaseTime = workNoInfo.getRestTime().format(DateTimeFormatter.ofPattern("yyyyMM")); if (!dataBaseTime.equals(monthTime)) { serialNum = 0; } } else if (workNoInfo.getRestType() == 3) { String yearTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyy")); String dataBaseTime = workNoInfo.getRestTime().format(DateTimeFormatter.ofPattern("yyyy")); if (!dataBaseTime.equals(yearTime)) { serialNum = 0; } } workNoInfo.setSerialNum(serialNum + 1); workNoInfo.setRestTime(localDateTime.toLocalDate()); //更新数据库 workNoDao.updateOrderNo(workNoInfo); return workNoInfo.getNo(); } catch (Exception e) { throw new ErrorCodeException(500, "生成业务码失败" + e.getMessage()); } finally { lock.unlock(); } }
}
原文地址:https://www.cnblogs.com/chunyun/p/12128430.html
时间: 2024-10-11 17:57:40