配置表
配置类
package com.nuzar.fcms.framework.idgen.config;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.nuzar.cloud.components.lock.RedisCacheAutoConfiguration;
import com.nuzar.fcms.framework.idgen.constant.IdGenRuleEnum;
import com.nuzar.fcms.framework.idgen.service.BizSequenceGenConfigService;
import com.nuzar.fcms.framework.idgen.service.FcmsIdentifierGenerator;
import com.nuzar.fcms.framework.idgen.service.impl.YearMonthDayNumberSequence;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@ConditionalOnProperty(name="fcms.idgen.enabled", havingValue = "true")
@ConditionalOnBean({ RedisCacheAutoConfiguration.class })
@Configuration
@EnableConfigurationProperties(FcmsIdGeneratorProperties.class)
@MapperScan({"com.nuzar.fcms.framework.idgen.mapper"})
@ComponentScan({"com.nuzar.fcms.framework.idgen.service"})
public class FcmsIdGeneratorAutoConfiguration {
@Bean
public BizSequenceGenConfigService bizSequenceGenConfigService(){
return new BizSequenceGenConfigService();
}
@Bean(IdGenRuleEnum.YearMonthDayNumberSequenceBeanId)
public YearMonthDayNumberSequence yearMonthDayNumberSequence(){
return new YearMonthDayNumberSequence();
}
@ConditionalOnClass(InetUtils.class)
public static class IdentifierGeneratorConfiguration {
@Bean
@Primary
public IdentifierGenerator fcmsIdentifierGenerator(InetUtils inetUtils) {
return new FcmsIdentifierGenerator(inetUtils.findFirstNonLoopbackAddress());
}
}
}
package com.nuzar.fcms.framework.idgen.service.impl;
import com.nuzar.cloud.utils.Assert;
import com.nuzar.fcms.framework.idgen.service.BizSequenceGenConfigService;
import com.nuzar.fcms.framework.idgen.service.FcmsIdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* 生成一个以日为范围的序列递增数字ID,这是一个初版设计, 对并发不高且能预估每天的业务量的场景序列生成适用
* 当前配置 序列最大值为 99999,依次递增,当天超出则抛出异常,次日重新从BEGIN_SEQ开始
*
*/
@Slf4j
public class YearMonthDayNumberSequence implements FcmsIdGenerator {
@Resource BizSequenceGenConfigService bizSequenceGenConfigService;
@Override public Number nextId(String bizName) {
Date now = bizSequenceGenConfigService.getDatabaseTime();
Date beginDate = DateUtils.truncate(now, Calendar.DAY_OF_MONTH);
Date endDate = DateUtils.addDays(beginDate, 1);
// 在指定时间范围内获取自增序列号, 如果获取失败,则随机延迟后再尝试2次获取,否则异常
Long seqValue = bizSequenceGenConfigService.tryIncrSeqAndGet(bizName, beginDate, endDate);
int retryTimes = 2;
while( retryTimes >0 && Objects.isNull(seqValue)){
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(RandomUtils.nextLong(32L, 128L)));
now = bizSequenceGenConfigService.getDatabaseTime();
beginDate = DateUtils.truncate(now, Calendar.DAY_OF_MONTH);
endDate = DateUtils.addDays(beginDate, 1);
seqValue = bizSequenceGenConfigService.tryIncrSeqAndGet(bizName, beginDate, endDate);
retryTimes--;
log.warn(">>>>>YearMonthDayNumberSequence-[尝试再次生成序列号{}],seqValue={}", bizName, seqValue);
}
Assert.on(Objects.isNull(seqValue), "生成序列号失败,请稍后重试! [%s]", bizName);
return Long.parseLong(DateFormatUtils.format(now, "yyyyMMdd") + String.format( "%05d", seqValue));
}
}
package com.nuzar.fcms.framework.idgen.service;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.nuzar.fcms.framework.idgen.constant.IdGenRuleEnum;
import com.nuzar.fcms.framework.idgen.model.BizSequenceGenConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 平台ID生成器封装mybatis-plus默认实现并对特殊场景处理生成ID规则
*/
@Slf4j
public class FcmsIdentifierGenerator implements IdentifierGenerator , ApplicationListener<AvailabilityChangeEvent> {
@Resource ApplicationContext context;
private Map<String, IdGenRuleEnum> bizNameIdGenRuleMap;
private IdentifierGenerator defaultIdentifierGenerator;
public FcmsIdentifierGenerator(InetAddress inetAddress){
this.defaultIdentifierGenerator = new DefaultIdentifierGenerator(inetAddress);
this.bizNameIdGenRuleMap = new HashMap<>();
}
@Override public Number nextId(Object entity) {
FcmsIdGenerator fcmsIdGenerator = tryResovleFcmsIdGenerator(entity);
if(ObjectUtils.isNotEmpty(fcmsIdGenerator)){
return fcmsIdGenerator.nextId(extractBizName(entity));
}
return this.defaultIdentifierGenerator.nextId(entity);
}
@Override public String nextUUID(Object entity) {
return this.defaultIdentifierGenerator.nextUUID(entity);
}
public String extractBizName(Object o){
TableInfo tableInfo = TableInfoHelper.getTableInfo(o.getClass());
if(ObjectUtils.isNotEmpty(tableInfo)){
return tableInfo.getTableName().toLowerCase();
}else{
return o.getClass().getSimpleName();
}
}
/**
* 从对象中解析id生成规则
* @param o
* @return
*/
public FcmsIdGenerator tryResovleFcmsIdGenerator(Object o){
if(ObjectUtils.isEmpty(this.bizNameIdGenRuleMap)){
return null;
}
IdGenRuleEnum idGenRuleEnum = this.bizNameIdGenRuleMap.get(this.extractBizName(o));
if(ObjectUtils.isEmpty(idGenRuleEnum)){
return null;
}
// FcmsIdGenerator实现类bean name 使用枚举类名称
if(context.containsBean(idGenRuleEnum.name())){
return context.getBean(idGenRuleEnum.name(), FcmsIdGenerator.class);
}
return null;
}
@Override public void onApplicationEvent(AvailabilityChangeEvent event) {
if(ReadinessState.ACCEPTING_TRAFFIC == event.getState()){
List<BizSequenceGenConfig> configs = context.getBean(BizSequenceGenConfigService.class).listActivedConfig();
configs.forEach(cfg->{
this.bizNameIdGenRuleMap.put(cfg.getBizName(), IdGenRuleEnum.valueOf(cfg.getIdGenRule()));
});
log.info(">>>>>FcmsIdentifierGenerator loaded. bizNameIdGenRuleMap.size()={}", this.bizNameIdGenRuleMap.size());
}
}
}
package com.nuzar.fcms.framework.idgen.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.nuzar.cloud.common.utils.CollectionUtils;
import com.nuzar.cloud.mapper.CommonService;
import com.nuzar.cloud.mapper.impl.CommonServiceImpl;
import com.nuzar.cloud.utils.Assert;
import com.nuzar.fcms.common.core.enums.EnableEnum;
import com.nuzar.fcms.framework.idgen.mapper.BizSequenceGenConfigMapper;
import com.nuzar.fcms.framework.idgen.model.BizSequenceGenConfig;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 业务序列生成配置服务
*/
public class BizSequenceGenConfigService extends CommonServiceImpl<BizSequenceGenConfigMapper, BizSequenceGenConfig> implements CommonService<BizSequenceGenConfig> {
/**
* <pre>
* 根据业务名称,序列有效获取的时间范围获取自增的ID
* 1、使用前必须先配置一条业务名称对应的记录
* 2、按时间范围生成序列规则时,判断当前记录的更新时间是否在范围内,如果不在则需要重置,否则自增后返回序列号
* 3、获取的值可能为null,需要尝试重新获取(并发的情况)
* </pre>
* @param bizName 业务名,可以为小写的表名或类名简称
* @param beginDate 时间范围开始时间
* @param endDate 时间范围截止时间
* @return
*/
@Transactional
public Long tryIncrSeqAndGet(String bizName, Date beginDate, Date endDate) {
BizSequenceGenConfig seqConfig = this.findByBizName(bizName);
Assert.on(Objects.isNull(seqConfig) || EnableEnum.DISABLED.getValue().equals(seqConfig.getActiveStatus()), "业务名为[%s]的序列号生成配置当前无效!", bizName);
Long nextSeq = null;
Date lastUpdateTime = seqConfig.getUpdateTime();
if(lastUpdateTime.after(beginDate) && lastUpdateTime.before(endDate)){
// 更新时间在时间范围内,不进行序列号重置,自增后更新到记录,并返回
Long incrSeq = seqConfig.getCurrSeq() + 1L;
Assert.on(incrSeq.longValue() > seqConfig.getMaxSeq().longValue(), "业务名为[%s]的序列号生成已达到最大限额[%d]!", bizName, seqConfig.getMaxSeq());
seqConfig.setCurrSeq(incrSeq);
seqConfig.setUpdateTime(this.getDatabaseTime());
if(this.incrSeq(seqConfig)){
nextSeq = incrSeq;
}
}
if(Objects.isNull(nextSeq) && lastUpdateTime.before(beginDate)){
// 更新时间已过期,进行序列号重置,并返回
seqConfig.setCurrSeq(seqConfig.getBeginSeq() + 1L);
Assert.on(seqConfig.getCurrSeq().longValue() > seqConfig.getMaxSeq().longValue(), "业务名为[%s]的序列号生成已达到最大限额[%d]!", bizName, seqConfig.getMaxSeq());
seqConfig.setUpdateTime(this.getDatabaseTime());
if(this.resetSeq(seqConfig)){
nextSeq = seqConfig.getCurrSeq();
}
}
return nextSeq;
}
/**
* 通过业务名称获取序列记录配置
* @param bizName
* @return
*/
public BizSequenceGenConfig findByBizName(String bizName){
LambdaQueryWrapper<BizSequenceGenConfig> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(BizSequenceGenConfig::getBizName, bizName);
return CollectionUtils.get(this.list(queryWrapper), 0);
}
/**
* 重置序列, 更新当前序列, 更新时间,乐观锁版本
* @param seqConfig
* @return
*/
public boolean resetSeq(BizSequenceGenConfig seqConfig){
LambdaUpdateWrapper<BizSequenceGenConfig> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(BizSequenceGenConfig::getCurrSeq, seqConfig.getCurrSeq())
.set(BizSequenceGenConfig::getUpdateTime, seqConfig.getUpdateTime())
.set(BizSequenceGenConfig::getVersion, 1)
.eq(BizSequenceGenConfig::getId, seqConfig.getId())
.eq(BizSequenceGenConfig::getVersion, seqConfig.getVersion());
return this.update(updateWrapper);
}
/**
* 更新递增序列
* @param seqConfig
* @return
*/
public boolean incrSeq(BizSequenceGenConfig seqConfig){
LambdaUpdateWrapper<BizSequenceGenConfig> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(BizSequenceGenConfig::getCurrSeq, seqConfig.getCurrSeq())
.set(BizSequenceGenConfig::getUpdateTime, seqConfig.getUpdateTime())
.set(BizSequenceGenConfig::getVersion, seqConfig.getVersion()+1)
.eq(BizSequenceGenConfig::getId, seqConfig.getId())
.eq(BizSequenceGenConfig::getVersion, seqConfig.getVersion());
return this.update(updateWrapper);
}
/**
* 列出有效的序列生成配置
* @return
*/
public List<BizSequenceGenConfig> listActivedConfig() {
LambdaQueryWrapper<BizSequenceGenConfig> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(BizSequenceGenConfig::getActiveStatus, EnableEnum.ENABLED.getValue());
return this.list(queryWrapper);
}
}