Java自定义id或编号 生成规则

配置表

配置类

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);
    }
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值