目录
- 1. 实现思路
- 2. 代码实现
- 3. 总结
- 参考
做项目的时候,经常会有自动生成业务编码的需求,比如插入数据的时候需要生成如下产品编号:P-(年份日期和三位序列号),比如P-20180727001
1. 实现思路
- 使用
业务编号+当前日期
获得redis的key值; - 使用redis的
incr
来原子性地获得其对应的自增数; - 避免redis的数据冗余,需要在第一次
incr
的时候使用expireAt
设置其数据当天24点过期。
这样即可在并发情况下获得不重复的相应编码。
2. 代码实现
public interface CodeGenerateService {
/**
* 根据编号类型生成相应的编号
*
* @param bizCode 编号类型
* @param inrNumberLength 自增编号的长度
*
* @return 编号
*/
String generateCode(String bizCode, int inrNumberLength);
}
@Service
public class CodeGenerateServiceImpl implements CodeGenerateService {
private static final String SERIAL_GENERATE_PREX = "com.demo:serial_number";
@Resource
private CacheClient cacheClient;
private static String getRedisKey(String bizType) {
//获得当前日期
String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
return SERIAL_GENERATE_PREX + ":" + bizType + "-" + date;
}
/**
* 获得自动补0的相应位数的值
*
* @param seq 数值
* @param length 长度
*
* @return 一定位数的序列号
*/
private static String getSequence(long seq, int length) {
String str = String.valueOf(seq);
int len = str.length();
if (len > length) {
return str;
}
int rest = length - len;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < rest; i++) {
sb.append('0');
}
sb.append(str);
return sb.toString();
}
/**
* 获取某一天的0时0分0秒0毫秒
*
* @param amount 日增减量
*
* @return 时间
*/
private Date getDay(int amount) {
// 获取当前日期
Calendar calendar = Calendar.getInstance();
// 加一天
calendar.add(Calendar.DAY_OF_MONTH, amount);
// 清空时/分/秒/毫秒
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
@Override
public String generateCode(String bizCode, int inrNumberLength) {
String key = getRedisKey(bizCode);
long number = cacheClient.incr(key);
if (String.valueOf(number).length() > inrNumberLength) {
//抛出自定义异常
throw new SerialRangException("编码超出范围!");
}
if (number == 1) {
cacheClient.expireAt(key, getDay(1).getTime() / 1000);
}
return key + getSequence(number, inrNumberLength);
}
@Override
public String generateCode(String bizCode) {
return this.generateCode(bizCode, 3);
}
}
调用
codeGenerateService.generateCode("P")
后打印出来的编号为:P-20180917001
查看redis的存储值如图:
图中已有该key存储的值,且已设置了超时时间为当天的24点,可以满足项目的需求
3. 总结
利用redis的incr
原子操作来获得唯一编号,此方法可保证在并发情况下编号的唯一性,但不能保证连续性,比如需要一次就生成连续的多个编号,需要使用锁
来实现。
参考
- 流水号的生成(日期+业务码+自增序列)