介绍

本次采用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. linux平台依赖性

    每个电脑平台有其自己的特点, 内核设计者可以自由使用所有的特性来获得更好的性能. in the target object file ??? 不象应用程序开发者, 他们必须和预编译的库一起连接他们的代 ...

  2. Vant-UI移动端时间选择框

    使用Vant input框时有时需要调用时间选择,时间选择框要结合弹出层使用 <div class="van-cell van-field"> <span cla ...

  3. Linux 内核 struct device 设备

    在最低层, Linux 系统中的每个设备由一个 struct device 代表: struct device { struct device *parent; struct kobject kobj ...

  4. Linux 内核

    在接口总线领域的最新的一项是外部总线的整个类. 这包括 USB, 固件, 和 IEEE1284(基 于并口的外部总线). 这些接口有些类似于老的非外部的技术, 例如 PCMCIA/CardBus 和 ...

  5. Educational Codeforces Round 61

    Educational Codeforces Round 61 今早刚刚说我适合打pikmike出的EDU 然后我就挂了 A 不管 B 不管 C 这道题到快结束了才调出来 大概就是\(n^2\)枚举不 ...

  6. CUP计算资源争抢通过IIS启用处理器关联解决

    由于业务的复杂性,我们在客户环境部署的时候,采用的是预装好在一台机器然后再把机器安装到客户环境,所以为了简单方便,我们把所有的服务都安装到一台机器上面了. 在正常的使用过程中是没有任何问题的.但是当有 ...

  7. QString 转换为 char *

    一.QString 转换为 char * 将 QString 转 char *,需要用到 QByteArray 类,QByteArray 类的说明详见 Qt 帮助文档. 因为 char * 最后都有一 ...

  8. MFC入门

    目录 001.MFC_应用程序类型    002.MFC_对话框_静态文本_编辑框  003.MFC_对话框_访问控件_7种方法_A   004.MFC_对话框_访问控件_7种方法_B   005.M ...

  9. sed & awk & grep 专题

    转载自:http://www.cnblogs.com/moveofgod/p/3540575.html grep, sed 与 awk 相当有用 ! gerp 查找, sed 编辑, awk 根据内容 ...

  10. Visio文本相关操作

    三种方式:双击形状输入文本,插入文本框, 文本工具 文本块工具 选择后可以对文本进行移动旋转 如果要给文本加入边框 直接显示线条就可以了 因为都是文本框 添加特殊文本: 插入符号 插入域 比如当前时间 ...