RedisSequenceFactory是一个简单封装类,用于使用redisTemplate生成自增ID值。代码如下:
@Slf4j
@Service
public class IdGenerateServiceImpl extends ServiceImpl<IdGenerateMapper, IdGenerate> implements IdGenerateService {
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedissonLocker redissonLocker;
private final Lock lock = new ReentrantLock();
/**
* 获取链接工厂
*/
public RedisConnectionFactory getConnectionFactory() {
return Objects.requireNonNull(redisTemplate.getConnectionFactory());
}
/**
* 自增数
*
* @param key key
* @return 自增数
*/
public long increment(String key) {
RedisAtomicLong redisAtomicLong;
// 加Redis分布式锁 双重保险,防止初始化的时候并发
// 【不存在会有两种情况,一种是没有这个key的自增,一种是数据库中有,redis过期了或重启了】
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
try {
lock.lock();
//查询是否在数据库中存在
IdGenerate idGenerate = this.getOne(Wrappers.lambdaQuery(IdGenerate.class).eq(IdGenerate::getGenerateKey, key));
redisAtomicLong = new RedisAtomicLong(key, this.getConnectionFactory());
if (idGenerate != null) {
// 从数据库赋值后,redis初始化不自增
redisAtomicLong.set(idGenerate.getCurrentNo());
}
// -1 为不过期
//log.warn(String.valueOf(redisAtomicLong.getExpire()));
} finally {
// 解锁
lock.unlock();
}
} else {
// 如果存在key,increment 是原子性的,不会重复计数
redisAtomicLong = new RedisAtomicLong(key, this.getConnectionFactory());
// 如果存在key,根据key的创建月份时间,超过本月的编号,将会重新生成
String[] keys = key.split(StringConstant.Symbol.COLON);
// 转换时间类型
TemporalAccessor yyyyMM = DateTimeFormatter.ofPattern("yyyyMM").parse(keys[1]);
LocalDate of = LocalDate.of(yyyyMM.get(ChronoField.YEAR), yyyyMM.get(ChronoField.MONTH_OF_YEAR), 1);
// 获取本月第一天
LocalDate now = LocalDate.now();
LocalDate firstDay = now.with(TemporalAdjusters.firstDayOfMonth());
// 历史的编号key的月份小于当前月份,则重新生成key
if (of.getMonthValue() < firstDay.getMonthValue()) {
try {
redissonLocker.lock(key);
// 删除key
redisTemplate.delete(key);
} finally {
// 解锁
redissonLocker.unlock(key);
}
}
}
return redisAtomicLong.incrementAndGet();
}
@Override
public String generateId(int codeLength, String prefix, String rule) {
SerialNumber serialNumber = SerialNumber.getInstance();
// 格式:xxx:202204
String key = prefix + StringConstant.Symbol.COLON + serialNumber.generateKey();
// 从 redis 中获取一个自增的计数器
long increment = increment(key);
// 完整的编码 例如:xxx_202204_001477
String finalCode = serialNumber.generateForDate(prefix, increment, codeLength);
// 保存到数据库中
IdGenerate idGenerate = new IdGenerate();
idGenerate.setRule(rule);
idGenerate.setGenerateKey(key);
idGenerate.setPrefix(prefix);
idGenerate.setCurrentNo(increment);
idGenerate.setCodeLength(codeLength);
this.saveToTable(idGenerate);
return finalCode;
}
/**
* 保存id生成器
*
* @param entity 新增或修改对象
* @return 保存成功或失败
*/
@Transactional
public boolean saveToTable(IdGenerate entity) {
boolean submit;
IdGenerate idGenerateValue = this.getOne(Wrappers.lambdaQuery(IdGenerate.class).eq(IdGenerate::getGenerateKey, entity.getGenerateKey()));
IdGenerate idGenerate = new IdGenerate();
if (idGenerateValue == null) {
idGenerate.setRule(entity.getRule());
idGenerate.setGenerateKey(entity.getGenerateKey());
idGenerate.setPrefix(entity.getPrefix());
idGenerate.setCurrentNo(entity.getCurrentNo());
idGenerate.setCodeLength(entity.getCodeLength());
idGenerate.setCreateTime(LocalDateTime.now());
// 不存在,新增
submit = this.save(idGenerate);
} else {
idGenerate.setId(idGenerateValue.getId());
idGenerate.setCurrentNo(entity.getCurrentNo());
idGenerate.setUpdateTime(LocalDateTime.now());
submit = this.updateById(idGenerate);
}
return submit;
}
}
@Mapper
public interface IdGenerateMapper extends BaseMapper<IdGenerate> {
}
@Data
@TableName("id_generate")
public class IdGenerate implements Serializable {
/**
* 主键
*/
@NotNull(message = "[主键]不能为空")
@ApiModelProperty("主键")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 编号规则
*/
@Size(max = 255, message = "编码长度不能超过255")
@ApiModelProperty("编号规则")
@Length(max = 255, message = "编码长度不能超过255")
private String rule;
/**
* redis的key
*/
@Size(max = 120, message = "编码长度不能超过120")
@ApiModelProperty("redis的key")
@Length(max = 120, message = "编码长度不能超过120")
private String generateKey;
/**
* 编号前缀
*/
@Size(max = 50, message = "编码长度不能超过50")
@ApiModelProperty("编号前缀")
@Length(max = 50, message = "编码长度不能超过50")
private String prefix;
/**
* 当前编号
*/
@ApiModelProperty("当前编号")
private Long currentNo;
/**
* 序号长度 默认0填充
*/
@ApiModelProperty("序号长度 默认0填充")
private Integer codeLength;
/**
* 创建时间
*/
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
/**
* 修改时间
*/
@ApiModelProperty("修改时间")
private LocalDateTime updateTime;
}
编写一个编号生成的规则类
public class SerialNumber {
private volatile static SerialNumber serialNumber;
public SerialNumber() {
}
public static SerialNumber getInstance() {
if (serialNumber != null) {
return serialNumber;
}
synchronized (SerialNumber.class) {
if (serialNumber != null) {
return serialNumber;
}
serialNumber = new SerialNumber();
}
return serialNumber;
}
/**
* @param prefix (前缀)
* @return id
*/
public String generate(String prefix, Integer increment, Integer length) {
//** 自增 *//*
String finalCode;
String formatCounter = generateId(length, increment);
// 生成后的计数器
if (StringUtils.isNotEmpty(prefix)) {
finalCode = prefix + formatCounter;
} else {
finalCode = String.valueOf(increment);
}
return finalCode;
}
/**
* 完整格式 xxx_202204_001477
* @param prefix 前缀 xxx_202204
* @param increment 自增数
* @param length 0的补位长度
* @return 编号
*/
public String generateForDate(String prefix, long increment, Integer length) {
//** 自增 *//*
String finalCode;
String formatCounter = generateForDateId(length, increment);
// 生成后的计数器
if (StringUtils.isNotEmpty(prefix)) {
finalCode = prefix + formatCounter;
} else {
finalCode = String.valueOf(increment);
}
return finalCode;
}
public String generateId(Integer length, Integer increment) {
return String.format("%0" + length + "d", increment);
}
/**
* 生成id(每日重置自增序列)
* 格式:日期 + 6位自增数
* 如:202108_000001
*
* @param length 几位自增数
* @return id
*/
public String generateForDateId(Integer length, long increment) {
return this.generateKey() + StringConstant.Symbol.UDLINE + String.format("%0" + length + "d", increment);
}
public String generateKey() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime utc = DateUtil.toUtc(now);
return utc.format(DateTimeFormatter.ofPattern("yyyyMM"));
}
}