基於mysql的單據號生成(前綴+日期+自增id+后綴)


介紹

本次采用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"521"小郭測試單據號");



    /**
     * 業務唯一標識(只是作為唯一標識,並不參與單據號的生成)
     */

    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();
        }
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM