一、前言- S% g1 L* {6 Q/ Q8 R
生成流水号,在企业中可以说是比较常见的需求,尤其是订单类业务。一般来说,需要保证流水号的唯一性。
+ p# }& q- |3 S$ y* C) Q2 b 如果没有长度和字符的限制,那么直接使用UUID生成一个唯一字符串即可,也可以直接使用数据库表中的主键,主键就是唯一的。
* t8 y' C! X- U0 K3 v/ s 那么,如果限制了流水号必须多少位,这种怎么生成呢?可以采用"前缀+日期+数字"的方式(ps:此方式是需要用到缓存的)
% M2 y& _$ ?# k0 P" T; Q( P- 前缀:为了更好的标识这个流水号是属于哪种类型;
- 日期:为了防止重复;
- 数字:为了表示当前的流水所处序号。
- 需求:生成一个17位数的唯一流水号,“LSH”+yyyyMMdd+6位数字5 k. {5 H8 w3 N% L* M; f: J
二、代码实现# B1 |% h: {2 v2 Z( K. ^4 {
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.GregorianCalendar;
- import java.util.concurrent.atomic.AtomicInteger;
-
- public class SerialNoTest {
-
- public static void main(String[] args) {
- String serialNo = generateSerialNo();
- System.out.println("生成的流水号:"+serialNo);
- }
-
- /**
- * 生成17位唯一流水号,"LSH"+yyyyMMdd+6位数字
- * 6位数字,如:000001
- * @return
- */
- private static String generateSerialNo(){
- //定义需要返回的流水号
- String serialNo = null;
- //先查询到今天的日期,格式:"yyyyMMdd"
- String todayDate = new SimpleDateFormat("yyyyMMdd")
- .format(new Date());
- //固定字母前缀 拼接 今天日期,组成新的完整的前缀,也就是缓存的key
- String cacheKey = "LSH"+todayDate;
- //再通过key查询缓存有没有num数据,缓存操作根据自身项目封装工具类
- Long codeNum = cacheService.getCache(cacheKey, Long.class);
- //如果缓存查询有值,数值+1,再赋值给下一个流水号
- if (null != codeNum) {
- codeNum = codeNum + 1L;
- } else {
- //如果缓存查询没值,直接赋值为1
- codeNum = 1L;
- }
- //流水号 = 缓存key + 拼接的数值 = 前缀 + 日期 + 拼接的数值
- serialNo = getCodeOfSix(cacheKey, codeNum.intValue());
- //设置缓存,调用此方法,会自动将key所对应的value+1,保存时长:今天剩余的时间
- cacheService.incr(cacheKey, getSeconds());
- return serialNo;
- }
-
-
- /**
- * 将数值拼接成对应的位数
- * @param prefix 前缀:"LSH"+yyyyMMdd
- * @param nowNum 当前要生成的数字
- * @return 拼接好的流水号
- */
- public static String getCodeOfSix(String prefix,int nowNum ) {
- //需要返回的code
- StringBuilder codeSb = new StringBuilder();
- //需要拼接的数字
- StringBuilder numSb = new StringBuilder();
- //封装的数字对象,里面 value 加了 volatile关键字,保证了线程安全
- AtomicInteger count = new AtomicInteger(nowNum);
-
- //将数值补足为6位字符串
- if (count.get() < 10) {
- numSb.append("00000").append(count.get());
- } else if(count.get() < 100){
- numSb.append("0000").append(count.get());
- }else if(count.get() < 1000){
- numSb.append("000").append(count.get());
- }else if(count.get() < 10000){
- numSb.append("00").append(count.get());
- }else if(count.get() < 100000){
- numSb.append("0").append(count.get());
- } else if (count.get() >= 100000) {
- numSb.append(count.get());
- }
-
- //先拼接前缀
- codeSb.append(prefix);
- //再拼接数字
- codeSb.append(numSb);
- return codeSb.toString();
- }
-
-
- /**
- * 获取当天结束还剩余多少秒
- * @return
- */
- public static int getSeconds(){
- //获取今天当前时间
- Calendar curDate = Calendar.getInstance();
- //获取明天凌晨0点的日期
- Calendar tommorowDate = new GregorianCalendar(
- curDate.get(Calendar.YEAR),
- curDate.get(Calendar.MONTH),
- curDate.get(Calendar.DATE) + 1,
- 0, 0, 0);
- //返回 明天凌晨0点 和 今天当前时间 的差值(秒数)
- return (int)(tommorowDate.getTimeInMillis() - curDate .getTimeInMillis()) / 1000;
- }
- }
假如今天是2021年4月22日,运行项目,生成的第1个流水号则为:LSH20210422000001
, N! w4 J) ]/ @1 E 第2个流水号则为:LSH20210422000002,依次类推。
6 {, ]$ k% K) y( U需要注意的是:
5 c7 A z* w- C& s" K& u- 如果限制了位数,6位数字每天最多能生成10w个流水号,所以,这个数字位数根据具体业务量进行调整。
- 如果每天的生成数量量不到1w,那么使用4位数字即可。
/ F2 k2 w7 y w( ]7 I
|