介绍

本次采用mysql处理,性能不是很好,对于高并发有要求的建议不要采用
公司一个小项目,需要生成一个单据号,格式为: 日期 + 每日重新自增号,自己考虑了一下每日自增需要考虑并发和持久问题,两种数据库redis和mysql由于项目较小,所以没有redis因为这个增加一个redis好像有点不值得,所以采用mysql作为持久化处理,一下思路也是借鉴了网上的许多想法

源码

源码查看规则
源码位置: blog-study:module-utils:work-no

欢迎大家随时批评指正

思路

  1. 首先根据需求,需要这样一个格式: 20200101 + 00001 的单据号
  2. 分析日期只需要获取当前时间格式化即可简单,
    自增号:
    1)需要考虑每日重新开始
    2)宕机数据可以持久化(存库即可)
    3)并发获取单据号唯一
    以上四个问题只需要考虑 每日重新开始计数和并发问题
  3. 每日重新开始,在数据库中加一个时间标志,每次获取都与当前时间做对比然后做相应处理, 并发问题我能想到的就是加锁(编程锁或者数据库锁,我这里采用lock锁)
  4. 以上基本可以解决项目问题了,然后思考是否可以用于其它业务号的生成
    1)于是想可以增加业务标识、前缀和后缀来满足不同业务,其中业务标识不参与单据号组成只是标识某一类单据号,这样可以保证不同业务可以使用相同的单据号,前缀是根据业务需要标识业务单据号的参与单据号生成, 后缀的作用是干扰随机数,可以尽可能防止他人直接看出来业务单据每日的生成数量
    格式: GX + 20200101 + 00001 + 23
    2) 每日重新开始计数,是否可以改为可选择的,每月重新计数,每年重新计数,或者一直不需要重新计数
  5. 思考是否可以封装为组件,引入依赖直接就可以使用,想了一下自己目前还不具备这种能力吧,以后还要多学多了解,也希望大家可以多多指点,小子将不胜感激。

功能介绍

  1. 数据库结构(使用实体类代替)
/**
     * 业务唯一标识(只是作为唯一标识,并不参与单据号的生成)
     */
    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;
  1. 业务号格式使用枚举规定好,每次启动项目自动识别处理
/**
     * 为了保证每次生成的单号一致性,所以初始化作为第一次初始化成功后不再修改
     */
    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;

项目启动初始化:

@Configuration
public 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);
            }
        }
    }
}
  1. 获取单据号:
@Component
public 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();
        }
    } }

基于mysql的单据号生成(前缀+日期+自增id+后缀)的更多相关文章

  1. 基于redis的订单号生成方案

    目前,比较火的nosql数据库,如MongoDB,Redis,Riak都提供了类似incr原子行操作. 下面是PHP版的一种实现方式: <?php /** * 基于Redis的全局订单号id * ...

  2. mysql 插入数据后返回当前的自增ID方法

    存储过程的写法: mysql>create procedure test( ->in username varchar(50), ->in password varchar(50), ...

  3. mysql 多主多从配置,自增id解决方案

    MySQL两主(多主)多从架构配置 一.角色划分 1.MySQL数据库规划 我现在的环境是:zhdy04和zhdy05已经做好了主主架构配置,现在需要的是把两台或者多台从服务器与主一一同步. 主机名 ...

  4. MySQL获得指定数据表中auto_increment自增id值的方法及实例

    http://kb.cnblogs.com/a/2357592/很多情况下,我们要提前用到当前某个表的auto_increment自增列id,可以通过执行sql语句来查询到这个id值. show ta ...

  5. .NET 6 在小并发下如何生成唯一单据号

    一.场景介绍 小并发下要解决生成单据号的问题,会碰到哪些问题呢?,接下来让我们一探究竟[这是小并发的解决方案,大家有更好的做好可以一起讨论分享]. 之所以叫小并发:是因为确实是小并发场景的应用模式,一 ...

  6. MySQL 使用自增ID主键和UUID 作为主键的优劣比较详细过程(从百万到千万表记录测试)

    测试缘由 一个开发同事做了一个框架,里面主键是uuid,我跟他建议说mysql不要用uuid用自增主键,自增主键效率高,他说不一定高,我说innodb的索引特性导致了自增id做主键是效率最好的,为了拿 ...

  7. 45 MySQL自增id

    45 MySQL自增id 表定义自增id 说到自增id,前面提到mysql的自增id不连续,当表定义的自增值达到上限后的逻辑是:再申请下一个id时,得到的值保持不变 ; insert into t v ...

  8. oracle生成单据号

    --创建单据号存放表 CREATE TABLE BU_TAB( DOC_NUM NUMBER --生成的单据号 ); --单据号 create table cux_doc_num( tab ), -- ...

  9. 用SQL存储过程生成唯一单据号

    用SQL存储过程生成唯一单据号     在一些系统中,经理要生成单据号,为了不使多台客户端生成的单据号重复,一般要在服务端生成这种流水号,本文是在数据库中生成流水号,并且可以生成多种类型的单据号(比如 ...

随机推荐

  1. java Set接口(元素不可以重复)

    Set是Collection子接口: Set和Collection基本上一样,一点除外: Set无法记住添加的顺序,不允许包含重复的元素. 当试图添加两个相同元素进Set集合,添加操作失败,add() ...

  2. js基础——变量、作用域、内存

    1.new关键字创建的是引用类型: eg. var box = new Object();      box.name = "Linda";//引用类型添加属性没问题     al ...

  3. UVa 1152 -4 Values whose Sum is 0—[哈希表实现]

    The SUM problem can be formulated as follows: given four lists A, B, C, D of integer values, compute ...

  4. HDU 3746 Cyclic Nacklace(kmp next数组运用)

    Cyclic Nacklace Problem Description CC always becomes very depressed at the end of this month, he ha ...

  5. Jmeter压力测试教程(入门篇)

    2.4 添加HTTP镜像服务器 在运行测试之前,如何验证我们发送的信息是否有误?如何可以理直气壮的告诉别人自己发送的测试请求没有问题?这里推荐添加HTTP镜像服务器进行校验测试. HTTP镜像服务器并 ...

  6. linux llseek 实现

    llseek 方法实现了 lseek 和 llseek 系统调用. 我们已经说了如果 llseek 方法从设备 的操作中缺失, 内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是 ...

  7. linux 操作 I/O 端口

      在驱动硬件请求了在它的活动中需要使用的 I/O 端口范围之后, 它必须读且/或写到这些 端口. 为此, 大部分硬件区别 8-位, 16-位, 和 32-位端口. 常常你无法混合它们, 象你 正常使 ...

  8. 【b503】篝火晚会

    Time Limit: 1 second Memory Limit: 50 MB [问题描述] 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官" ...

  9. vue 父组件中调用子组件函数

    2019/06/06 在父组件中调用子组件的方法:  1.给子组件定义一个ref属性.eg:ref="childItem"  2.在子组件的methods中声明一个函数.eg: u ...

  10. apply call 用法

    1.对象转数组  tips 先看怎么用  再研究方法原理 var obj = {     0: 'qian',     1: 'long',     2: 'chu',     3: 'tian', ...