Java中流水编号的生成

2021年9月28日 4点热度 0条评论

在开发中,遇到这样一个需求,在介质资料新增时,需要生成一个介质编号,格式为"JZ+yyyyMMDD+4位递增数字"
先是使用百度找寻解决方法。解决方法

里面的查询缓存的方法在我这项目里没有,我也不会写。就自己想了个折中的方法,再请求这个接口的时候,先去数据库查询MAX(id),如果有,就在此基础上+1
如果没有,就初始化一个值1进行传参。相关代码如下:

点击查看代码



public class NumberUtils {

    /**
    * 生成14位唯一流水号,"JZ"+yyyyMMdd+4位数字
    * 4位数字,如:0001
    * @return
    */
    @NotNull
    public static String generateSerialNum(String str, Integer nowNum){
        // 定义需要返回的流水号
        String serialNum;
        // 先查询到今天的日期,格式:"yyyyMMdd"
        String todayDate = new SimpleDateFormat("yyyyMMdd")
                .format(new Date());
        // 固定字母前缀 拼接 今天日期,组成新的完整的前缀
        String prefixCode = str + todayDate;

        serialNum = getCode(prefixCode, nowNum);

        return serialNum;
    }

    /**
    * 将数值拼接成对应的位数
    * @param prefix  前缀:"JZ"+yyyyMMdd
    * @param nowNum  当前要生成的数字
    * @return 拼接好的流水号
    */
    private static String getCode(String prefix,int nowNum ) {
        // 需要返回的code
        StringBuilder code = new StringBuilder();
        // 需要拼接的数字
        StringBuilder num = new StringBuilder();
        // 封装的数字对象,里面 value 加了 volatile关键字,保证了线程安全
        AtomicInteger count = new AtomicInteger(nowNum);

        // 将数值补足为4位字符串
        if (count.get() < 10) {
            num.append("000").append(count.get());
        } else if(count.get() < 100){
            num.append("00").append(count.get());
        }else if(count.get() < 1000){
            num.append("0").append(count.get());
        } else if (count.get() >= 1000) {
            num.append(count.get());
        }

        // 先拼接前缀
        code.append(prefix);
        // 再拼接数字
        code.append(num);
        return code.toString();
    }
}

后面被说不行,并发下不行。而且跟数据库连接效率也低。就想到了使用redis的incr方法。相关代码如下:

点击查看代码

public class NumberUtils {

    /**
    * 编号自动生成
    * @param redisTemplate redis模板
    * @param code 前缀 首字母大写
    * @param dateFormat 日期格式
    * @param nums 几位数字
    * @return 编号
    */
    @NotNull
    public static String generateSerialNum(@NotNull RedisTemplate<String,Integer> redisTemplate,
                                           String code, String dateFormat, Integer nums) {
        // 使用StringBuilder拼接字符串
        StringBuilder sb = new StringBuilder();
        // 获取当前时间并定义日期格式
        String localDateFormat = DateUtils.localDateFormat(LocalDate.now(), dateFormat);
        sb.append(code);
        sb.append(localDateFormat);

        // 将拼接好的字符串做key,获取redis中的数据
        Integer i = redisTemplate.opsForValue().get(sb.toString());
        int increment;
        if (i != null) {
            // 如果redis中有数据,则使用increment()将数据+1
            increment = Objects.requireNonNull(redisTemplate.opsForValue()
                                     .increment(sb.toString())).intValue();
        } else {
            // 如果redis中没有数据,则使用setIfAbsent()将数据设置为1
            increment = 1;
            redisTemplate.opsForValue().setIfAbsent(sb.toString(),increment);
        }

        // 第一次format格式化nums位0,之后再格式化increment
        sb.append(String.format(String.format("%%0%dd", nums), increment));
        return sb.toString();
    }
}

然后说代码逻辑没问题,就是代码可以再优化,一通解释加我一通操作,最后代码如下:

点击查看代码

@Slf4j
public class NumberUtils {
    private NumberUtils() {}

    /**
    * 编号自动生成
    *
    * @param redisTemplate redis模板
    * @param code          前缀 首字母大写
    * @param dateFormat    日期格式
    * @param nums          几位数字
    * @return 编号
    */
    @NotNull
    public static String generateSerialNum(RedisTemplate<String,Integer> redisTemplate,
                                           String code, String dateFormat, Integer nums) {
        // nums位数字,不够的前面补0
        val format = String.format("%%0%dd", nums);
        // 返回拼接好的编号
        return code + DateUtils.localDateFormat(LocalDate.now(), dateFormat)
                        + String.format(format, getIncrement(redisTemplate, code + dateFormat));
    }

    private static Integer getIncrement(@NotNull RedisTemplate<String,Integer> redisTemplate, String redisKey) {
        return Optional.ofNullable(redisTemplate.opsForValue().get(redisKey))
                // 返回redis里的值 + 1
                .map(num -> Objects.requireNonNull(redisTemplate.opsForValue().increment(redisKey)).intValue())
                // 如果redis里没有,则插入1并通过判断插入是否成功来返回初始化值1,如果插入失败,则返回拼接null
                .orElse(Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(redisKey, 1)) ? 1 : null);
    }
}