看了网上很多生成自增ID的策略,最终给出的都是雪花算法,leaf算法。但是却没有满足咱们对于自定义生成规则的需求。
在业务上有一部分ID往往是有规则的,比如某个产品的订单号往往是“产品标志+时间+n位流水”,类似这样的订单规则,使用雪花算法是满足不了业务需求的,所以我们得设计一套自己的自定义ID生成器。
“产品标志+时间+n位流水”规则中,难点无非在于n位流水号的生成,因为这个流水号需要保证在多次请求中不会产生重复的订单号。
首先,咱们根据业务需求,先制定对应的规则表达式:
id: generator: expressions: # 产品标志 TEST: # 表达式,pid指的是产品标志 exp: "$(pid)$(yearMonthDayHms)$(id:6:0)" # 字段名:初始值:最大值:最小数量:扩容数量:初始数量:增长步长 initFields: ["id"] 复制代码
表达式对应的实体类:
@Data @Configuration @ConfigurationProperties(prefix = "id.generator") public class IDExpressionProperties { private Map<String, SerialIdConfig> expressions; @Data public static class SerialIdConfig { // 表达式 private String exp; // 初始化字段 private String[] initFields; } } 复制代码
通过spring解析获得对应的IDExpressionProperties实体类,拿到咱们自定义的配置
通过规则表达式可以看出,类似“$(pid)”这样的表达式,咱们可以抽象成接口自定义生成。比如咱们定义一个VariableGenerator接口或者抽象类,遇到pid就从spring ioc容器中调用pidVariableGenerator这个Bean的生成方法获取pid的值。遇到yearMonthDayHms就调用yearMonthDayHmsVariableGenerator这个Bean的生成方法获取yearMonthDayHms指定的值。
@Configuration public class SerialConfig { @Autowired private IDExpressionProperties idExpressionProperties; /** ** 咱们要创建一个ID工厂类,专门用来生成ID的类 ** 使用方法: ** @Autowired ** private IDFactory idFactory; ** String id = idFactory.get("产品标志"); */ @Bean(initMethod = "init") public IDFactory serialIdFactory() { return new IDFactory(idExpressionProperties.getExpressions()); } } 复制代码
变量生成器
import org.apache.commons.lang3.StringUtils; /** * 变量生成器 * @className VariableGenerator * @date: 2021/2/18 下午2:53 * @description: */ public abstract class VariableGenerator { public static final String COLON = ":"; /** * apply是生成目标字符串的方法 */ protected abstract String apply(ExpressionElement e, Expression expression); /** * apply的后置处理方法,默认处理字符串不足的情况下,补足对应的填充数据 */ public String andThen(ExpressionElement e, Expression expression) { String variableValue = apply(e, expression); int count = e.getCount(); String fillStringValue = e.getFillStringValue(); if (StringUtils.isNotBlank(variableValue)) { if (count > 0) { variableValue = StringUtils.leftPad(variableValue, count, fillStringValue); } else { variableValue = StringUtils.rightPad(variableValue, Math.abs(count), fillStringValue); } } return variableValue; } } 复制代码
ID生成器
- 初始化的时候根据表达式配置确定是哪些字段需要初始化,根据初始化字段调用指定的Bean执行初始化
- 通过get方法传入的参数key获取指定的规则表达式,根据指定的表达式调用对应的生成器Bean实例,调用指定的方法生成目标值,最后拼接出最终的ID
public class IDFactory { // 变量生成器 @Autowired(required = false) private Map<String, VariableGenerator> variableGeneratorMap; // 字段初始化生成器 @Autowired(required = false) private Map<String, InitFieldGenerator> initFieldGeneratorMap; // 构造函数,参数是生成规则 public IDFactory(Map<String, IDExpressionProperties.SerialIdConfig> expressionMap) { this.expressionMap = expressionMap; } // 实例化后执行 public void init() { // 如果没有规则表达式,那么直接就结束 if (CollectionUtils.isEmpty(this.expressionMap)) { return; } for (Map.Entry<String, IDExpressionProperties.SerialIdConfig> e : this.expressionMap.entrySet()) { String key = e.getKey(); // 规则表达式 IDExpressionProperties.SerialIdConfig config = e.getValue(); // 初始化字段参数 String[] initFields = config.getInitFields(); // 如果没有初始化字段生成器,直接结束 if (CollectionUtils.isEmpty(initFieldGeneratorMap)) { return; } // 根据初始化规则,执行初始化操作 for (String initField : initFields) { String fieldName = initField; // 获取初始化字段名称 if (StringUtils.contains(initField, VariableGenerator.COLON)) { fieldName = StringUtils.substringBefore(initField, VariableGenerator.COLON); } // 根据字段名称获取对应的初始化生成器的Bean实例 InitFieldGenerator initFieldGenerator = initFieldGeneratorMap.get( fieldName + InitFieldGenerator.INIT_FIELD_GENERATOR); if (Objects.nonNull(initFieldGenerator)) { // 执行字段初始化操作 initFieldGenerator.generator(key, initField); } } } } /** * 表达式 * * <p>pid:expression格式 */ private Map<String, IDExpressionProperties.SerialIdConfig> expressionMap; /** * 根据指定的key规则生成id * * @param key * @return */ public String get(String key) { // key为空直接抛异常 if (StringUtils.isBlank(key)) { throw new IllegalArgumentException("无效的参数值:" + key); } // 获取规则表达式 IDExpressionProperties.SerialIdConfig serialIdConfig = expressionMap.get(key); // 表达式字符串 String expressionString = serialIdConfig.getExp(); // 为空直接抛异常 if (StringUtils.isBlank(expressionString)) { throw new IllegalArgumentException("没有找到对应的表达式"); } // 解析指定的表达式 Expression expression = parse(key, expressionString); // 匹配得出最终结果 return matchExpression(expression); } // 生成器名称后缀 private static final String VARIABLE_GENERATOR = "VariableGenerator"; // 循环遍历表达式中所有的自定义变量,获取指定Bean实例,执行目标方法后得出最终ID private String matchExpression(Expression expression) { // 获取变量列表,例如pid,yearMonthDayHms等 List<ExpressionElement> elements = expression.getElements(); // 如果没有任何变量,那么直接返回原表达式,说明表达式是一个常量 if (CollectionUtils.isEmpty(elements)) { return expression.getExpression(); } // 获取原表达式,用来替换变量,生成最终的ID String expressionString = expression.getExpression(); // 循环遍历变量列表 for (ExpressionElement e : elements) { // 拼接Bean的名称 String beanName = e.getVariableName() + VARIABLE_GENERATOR; // 从map中取出指定的Bean VariableGenerator variableGenerator = variableGeneratorMap.get(beanName); // 如果没有取到,那么直接忽略,说明没有创建该表达式对应的生成器 if (Objects.isNull(variableGenerator)) { continue; } // 调用目标方法生成字符串 String variableValue = variableGenerator.andThen(e, expression); // 如果不为空,就替换掉原表达式中的变量;就是用具体生成的值替换变量表达式 // “$(pid)$(yearMonthDayHms)$(id:6:0)”会被替换成“TEST$(yearMonthDayHms)$(id:6:0)” if (StringUtils.isNotBlank(variableValue)) { expressionString = StringUtils.replace(expressionString, e.getOriginString(), variableValue); } } // 返回最终结果 return expressionString; } // 正则表达式,用来解析$(pid)$(yearMonthDayHms)$(id:6:0)表达式 private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\((.+?)\\)"); private static final Map<String, Expression> EXPRESSION_MAP = Maps.newConcurrentMap(); /** * 解析$(pid)$(yearMonthDayHms)$(id:6:0) * * @param expressionString * @return */ private Expression parse(String key, String expressionString) { // 检查一下缓存中是否有解析号的表达式 Expression expression = EXPRESSION_MAP.get(key); // 缓存不为空的话,直接返回 if (Objects.nonNull(expression)) { return expression; } // 否则,直接解析 synchronized (EXPRESSION_MAP) { // 双重检查,避免重复解析 expression = EXPRESSION_MAP.get(key); if (Objects.nonNull(expression)) { return expression; } // 生成表达式对象 expression = new Expression(); expression.setKey(key); expression.setExpression(expressionString); List<ExpressionElement> expressionElements = Lists.newArrayList(); Matcher matcher = EXPRESSION_PATTERN.matcher(expressionString); while (matcher.find()) { // 正则表达式,找出$()变量表达式,类似id:6:0 String expressionVariable = matcher.group(1); // 表达式切割,分离出冒号分隔的参数 String[] expressionVariables = StringUtils.splitByWholeSeparatorPreserveAllTokens( expressionVariable, VariableGenerator.COLON); ExpressionElement expre = new ExpressionElement(); // 变量名称id expre.setVariableName(expressionVariables[0]); // 原生表达式$(id:6:0),便于后面直接替换 expre.setOriginString(matcher.group()); if (expressionVariables.length > 1) { // 获取填充的最终长度 expre.setCount(CastUtil.castInt(expressionVariables[1])); } if (expressionVariables.length > 2) { // 获取填充值 expre.setFillStringValue(expressionVariables[2]); } expressionElements.add(expre); } expression.setElements(expressionElements); // 放入本地缓存 EXPRESSION_MAP.put(key, expression); } // 返回解析出来的表达式 return expression; } } 复制代码
import lombok.Data; import java.util.List; /** * @className Expression * @date: 2021/2/18 下午2:53 * @description: 解析$(pid)$(year)$(month)$(day)$(id:6:0)这种类型的表达式 */ @Data public class Expression { /** pid */ private String key; /** 表达式 */ private String expression; /** 解析结果 */ private List<ExpressionElement> elements; } /** * @author zouwei * @className ExpressionElement * @date: 2021/2/18 下午2:56 * @description: 解析${id:6:0}这种类型的标记 */ @Data public class ExpressionElement { // 原生变量表达式 private String originString; // 变量名称 private String variableName; // 总长度 private int count; // 填充值,默认是空字符 private String fillStringValue = StringUtils.SPACE;