序号生成算法

算法介绍

需求:

  1. 支持前缀+日期时间+序号+后缀规则
  2. 支持前缀+序号+日期时间+后缀规则
  3. 支持前缀+序号+后缀规则
  4. 支持根据设置的日期格式自动重置序号,即如果日期格式是YYYYMM,那么当MM值变化了,自动重置序号为初始值。
  5. 支持自定义日期时间格式
  6. 支持设置序号初始值
  7. 支持设置序号的固定长度
  8. 支持设置序号的最大值和最小值
  9. 支持设置序号自增步长
  10. 支持步长可以为负数
  11. 支持序号超过最大值或最小值后自动重置为初始值。
  12. 支持前缀后缀可以为空。

算法简介:

  1. 序号类型
    1. 基础序号:前缀+序号值+后缀
    2. 日期+序号:前缀+日期+序号值+后缀
    3. 序号+日期:前缀+序号值+日期+后缀
  2. 固定长度序号算法,即无论是1还是10001,如果指定的序号固定长度是6位,那么得到的序号值应该都是6位长的字符,序号值1得到的序号字符串是000001、而序号值10001得到的序号字符串是010001,核心代码如下: String.format("%0" + fixedLength + "d", value)
  3. 动态前缀和后缀算法
    sb.append(StringUtils.isBlank(seqno.getPrefix())?"":seqno.getPrefix())
           .append(String.format("%0" + fixedLength + "d", value))
           .append(StringUtils.isBlank(seqno.getSuffix())?"":seqno.getSuffix());
    
  4. 序号按照指定步长自增,并且达到上限自动重置算法
    int nextValue = value + seqno.getStep();
    // 判断序号是否超过上下限
    if (nextValue > seqno.getMax() || nextValue < seqno.getMin()) {
     nextValue = seqno.getInitValue();
    }
    seqno.setValue(nextValue);
    
  5. 指定的日期格式中最小单位变化自动重置序号为初始值算法,核心算法为通过两次日期的格式化和解析操作,完整丢弃掉指定的日期格式以外的日期数据,从而保证和当前日期运算的结果的单位是指定的格式的日期的最小单位。
LocalDateTime now = LocalDateTime.now(SystemConstant.UTC_8);
LocalDateTime currentDate = seqno.getCurrentDate();

//日期格式化
String dateFormat = seqno.getDateFormat();
String nowString = "";
if(!StringUtils.isBlank(dateFormat)) {
    //系统当前时间
    nowString = now.format(DateTimeFormatter.ofPattern(dateFormat));
    seqno.setCurrentDate(now);
    //需要重置序号
    if(seqno.getResetValueByDate()) {
        //设置一个带默认值的日期转换器
        DateTimeFormatter format = new DateTimeFormatterBuilder().appendPattern(dateFormat)
        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
        .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
        .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)  
        .parseDefaulting(ChronoField.MICRO_OF_SECOND, 0)  
        .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
        .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
        .toFormatter(); 
        //序号表当前时间
        String currentDateString =  Optional.ofNullable(currentDate).orElse(now).atZone(SystemConstant.UTC_8).format(format);
        //再转换为日期(丢弃掉日期数据中没有指定格式化样式的数据)
        LocalDateTime firstDate = LocalDateTime.parse(currentDateString, format);
        LocalDateTime secondDate = LocalDateTime.parse(nowString, format);
        Duration duration = Duration.between(firstDate, secondDate);
        if(!duration.isZero()) {  //如果差值不等于0,重置当前序号值
            value = seqno.getInitValue();
        }
    }

源码

    public synchronized String nextSequence(Integer id) {
        SystemSequenceNumber seqno = getCurrentSequence(id);
        StringBuilder sb = new StringBuilder();
        LocalDateTime now = LocalDateTime.now(SystemConstant.UTC_8);
        if (seqno != null) {
            int fixedLength = seqno.getLength();
            int value = seqno.getValue();
            LocalDateTime currentDate = seqno.getCurrentDate();

            //日期格式化
            String dateFormat = seqno.getDateFormat();
            String nowString = "";
            if(!StringUtils.isBlank(dateFormat)) {
                //系统当前时间
                nowString = now.format(DateTimeFormatter.ofPattern(dateFormat));
                seqno.setCurrentDate(now);
                //需要重置序号
                if(seqno.getResetValueByDate()) {
                    //设置一个带默认值的日期转换器
                    DateTimeFormatter format = new DateTimeFormatterBuilder().appendPattern(dateFormat)
                    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                    .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)  
                    .parseDefaulting(ChronoField.MICRO_OF_SECOND, 0)  
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                    .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                    .toFormatter(); 
                    //序号表当前时间
                    String currentDateString =  Optional.ofNullable(currentDate).orElse(now).atZone(SystemConstant.UTC_8).format(format);
                    //再转换为日期(丢弃掉日期数据中没有指定格式化样式的数据)
                    LocalDateTime firstDate = LocalDateTime.parse(currentDateString, format);
                    LocalDateTime secondDate = LocalDateTime.parse(nowString, format);
                    Duration duration = Duration.between(firstDate, secondDate);
                    if(!duration.isZero()) {  //如果差值大于0,重置当前序号值
                        value = seqno.getInitValue();
                    }
                }
            }
            
            //获取序号类型
            SystemSequenceType type = SystemSequenceType.valueOf(seqno.getType());
            switch (type) {
                case BASE_VALUE: //基础序号
                    sb.append(StringUtils.isBlank(seqno.getPrefix())?"":seqno.getPrefix())
                    .append(String.format("%0" + fixedLength + "d", value))
                    .append(StringUtils.isBlank(seqno.getSuffix())?"":seqno.getSuffix());
                    break;
                case DATE_FORMAT_VALUE: //日期+序号
                    sb.append(StringUtils.isBlank(seqno.getPrefix())?"":seqno.getPrefix())
                    .append(nowString)
                    .append(String.format("%0" + fixedLength + "d", value))
                    .append(StringUtils.isBlank(seqno.getSuffix())?"":seqno.getSuffix());
                    break;
                case VALUE_DATE_FORMAT: //序号+日期
                    sb.append(StringUtils.isBlank(seqno.getPrefix())?"":seqno.getPrefix())
                    .append(String.format("%0" + fixedLength + "d", value))
                    .append(StringUtils.isBlank(seqno.getSuffix())?"":seqno.getSuffix())
                    .append(nowString);
                    break;
                    
                default:
                    break;
            }
            
            int nextValue = value + seqno.getStep();
            // 判断序号是否超过上下限
            if (nextValue > seqno.getMax() || nextValue < seqno.getMin()) {
                nextValue = seqno.getInitValue();
            }
            
            seqno.setValue(nextValue);
            systemSequenceNumberMapper.updateByPrimaryKey(seqno);
        }
        return sb.toString();
    }

测试

    /**
     * 测试基础序号获取
     * @throws Exception
     */
    @Test
    @Transactional
    public void testNextSequenceBaseValue() throws Exception {
        String sequence = iSequenceService.nextSequence(BASE_VALUE_ID);
        assertThat(sequence).isEqualTo("BASE_VALUE_ID00000000SUFFIX");
        SystemSequenceNumber expected = new SystemSequenceNumber();
        expected.setCurrentDate(null);
        expected.setDateFormat(null);
        expected.setDescription("测试-基础序号");
        expected.setId(BASE_VALUE_ID);
        expected.setLength(8);
        expected.setMax(99999999);
        expected.setMin(0);
        expected.setPrefix("BASE_VALUE_ID");
        expected.setResetValueByDate(true);
        expected.setStep(1);
        expected.setSuffix("SUFFIX");
        expected.setType(SystemSequenceType.BASE_VALUE.value());
        expected.setInitValue(0);
        expected.setValue(1);
        
        SystemSequenceNumber actual = systemSequenceNumberMapper.selectByPrimaryKey(BASE_VALUE_ID);
        assertThat(actual).isEqualToComparingFieldByField(expected);
        
    }

    /**
     * 测试日期+序号获取
     * @throws Exception
     */
    @Test
    @Transactional
    public void testNextSequenceDateFormatValue() throws Exception {
        String sequence = iSequenceService.nextSequence(DATE_FORMAT_VALUE);
        assertThat(sequence).isEqualTo("DATE_FORMAT_VALUE2018-06-05-140000SUFFIX");
        
        SystemSequenceNumber expected = new SystemSequenceNumber();
        expected.setCurrentDate(null);
        expected.setDateFormat("yyyy-MM-dd-HH");
        expected.setDescription("测试-日期+序号");
        expected.setId(DATE_FORMAT_VALUE);
        expected.setLength(4);
        expected.setMax(9999);
        expected.setMin(0);
        expected.setPrefix("DATE_FORMAT_VALUE");
        expected.setResetValueByDate(true);
        expected.setStep(1);
        expected.setSuffix("SUFFIX");
        expected.setType(SystemSequenceType.DATE_FORMAT_VALUE.value());
        expected.setInitValue(0);
        expected.setValue(1);
        
        SystemSequenceNumber actual = systemSequenceNumberMapper.selectByPrimaryKey(DATE_FORMAT_VALUE);
        assertThat(actual).isEqualToIgnoringGivenFields(expected, "currentDate");

    }
    
    
    /**
     * 测试序号+日期获取
     * @throws Exception
     */
    @Test
    @Transactional
    public void testNextSequenceValueDateFormat() throws Exception {
        String sequence = iSequenceService.nextSequence(VALUE_DATE_FORMAT);
        assertThat(sequence).isEqualTo("VALUE_DATE_FORMAT18040000SUFFIX");
        
        SystemSequenceNumber expected = new SystemSequenceNumber();
        expected.setCurrentDate(null);
        expected.setDateFormat("yyMM");
        expected.setDescription("测试-序号+日期");
        expected.setId(VALUE_DATE_FORMAT);
        expected.setLength(4);
        expected.setMax(9999);
        expected.setMin(0);
        expected.setPrefix("VALUE_DATE_FORMAT");
        expected.setResetValueByDate(true);
        expected.setStep(1);
        expected.setSuffix("SUFFIX");
        expected.setType(SystemSequenceType.VALUE_DATE_FORMAT.value());
        expected.setInitValue(0);
        expected.setValue(1);
        
        SystemSequenceNumber actual = systemSequenceNumberMapper.selectByPrimaryKey(VALUE_DATE_FORMAT);
        assertThat(actual).isEqualToIgnoringGivenFields(expected, "currentDate");

    }