Skip to main content

流水号生成策略

每个业务都需要流水号来保证业务唯一性,流水号不能是毫无业务含义的随机字符串,需要一定的规则去生成。比如订单号:订单类型-年月日-0001。流水号中包含业务的信息,如果直接使用某规则生成流水号,则不能随时修改流水号的规则,不够灵活。这个策略利用枚举类的构造方法,获取业务实体的class对象和对应的字段名,通过反射获取业务字段值,拼接出流水号规则。

数据实体

  • 一个流水号规则拥有多个规则明细,例:业务类型-年月日-A-01。
  • 一个流水号规则会生成多条流水号数据,原因:修改规则时,保留之前的流水号数据做历史查询。

importing-data-sample

业务流程

  • 保存流水号规则:查询该规则是否存在,存在则置为停用,目的是保留历史数据。再新增流水号规则与其对应的明细数据。
  • 获取流水号:先获取流水号规则,根据流水号规则与明细获取流水号前缀。根据流水号前缀判断流水号表中是否已经存在,已存在当前序列号加一返回,不存在新增流水表数据,再加一返回。

importing-data-sample

具体实现

枚举类

    @Schema(title = "编码规则项")
public enum CodeRuleItem {

@Schema(title = "资产:资产分类")
ASSET_CATEGORY(AssetEntity.class, "categoryCode"),

@Schema(title = "创建日期-年(4位)")
CREATE_DATE_FULL_YEAR(AbstractVersionedEntity.class, FieldConstants.CREATE_DATE, "yyyy"),

@Schema(title = "创建日期-年(2位)")
CREATE_DATE_YEAR(AbstractVersionedEntity.class, FieldConstants.CREATE_DATE, "yy"),

@Schema(title = "创建日期-月")
CREATE_DATE_MONTH(AbstractVersionedEntity.class, FieldConstants.CREATE_DATE, "MM"),

@Schema(title = "创建日期-日")
CREATE_DATE_DAY(AbstractVersionedEntity.class, FieldConstants.CREATE_DATE, "dd"),

@Schema(title = "固定字符")
FIXED_CHARACTER;

/**
* 字段常量类。
*/
private static class FieldConstants {
public static final String CREATE_DATE = "createdAt"; // 创建时间
}
}

流水号帮助类

    /**
* 获取流水号。
* @param operator 当前操作人
* @param tenantId 租户ID
* @param commandDTO 流水号查询对象
* @return 流水号字符串
*/
public String getSerialNumber(OperatorDTO operator, String tenantId, SerialNumberCommandDTO<? extends AbstractEntity> commandDTO) {
if (commandDTO == null) {
return "";
}
// 查看租户下该业务是否有存在的规则
BooleanExpression query = T_CODE_RULE.tenantId.eq(tenantId)
.and(T_CODE_RULE.type.eq(commandDTO.getType()))
.and(T_CODE_RULE.disabled.isFalse());
if (commandDTO.getCategory() != null) {
query = query.and(T_CODE_RULE.category.eq(commandDTO.getCategory()));
}
CodeRuleCommandEntity codeRuleCommandEntity = codeRuleCommandRepository.findOne(query).orElse(null);
if (codeRuleCommandEntity == null) {
// 编码规则不存在,返回空串
return "";
}
// 获取流水号前缀字符串
String serialNumberPrefix =
getSerialNumberPrefix(codeRuleCommandEntity.getId(), commandDTO.getBusinessEntity());
// 获取流水号信息
SerialNumberCommandEntity serialNumberCommandEntity =
serialNumberRepository.findByTenantIdAndCodeRuleIdAndSerialNumberPrefixAndDeletedIsFalse(
tenantId, codeRuleCommandEntity.getId(), serialNumberPrefix).orElse(null);
// 如果不存在此规则的流水号,则新增一条
if (serialNumberCommandEntity == null) {
SerialNumberCommandEntity saveEntity = new SerialNumberCommandEntity();
saveEntity.create(operator);
saveEntity.setTenantId(tenantId);
saveEntity.setCodeRuleId(codeRuleCommandEntity.getId());
saveEntity.setSerialNumberPrefix(serialNumberPrefix);
saveEntity.setCurrentSerialNumber(0);
serialNumberCommandEntity = serialNumberRepository.save(saveEntity);
}
// 更新此规则,当前流水号加1
Integer serialNumber = serialNumberCommandEntity.getCurrentSerialNumber() + 1;
serialNumberCommandEntity.setCurrentSerialNumber(serialNumber);
serialNumberCommandEntity.modify(operator);
// 更新前的版本号
Long revision = serialNumberCommandEntity.getRevision();
// 获取当前版本号
SerialNumberCommandEntity commandEntity =
serialNumberRepository.findByIdAndDeletedIsFalse(serialNumberCommandEntity.getId())
.orElse(new SerialNumberCommandEntity());
// 3次循环解除乐观锁
int i = 0;
while (!revision.equals(commandEntity.getRevision())) {
if (i > CYCLES) {
// 循环3次版本号对不上,则返回空串
return "";
}
revision = commandEntity.getRevision();
commandEntity =
serialNumberRepository.findByIdAndDeletedIsFalse(serialNumberCommandEntity.getId())
.orElse(new SerialNumberCommandEntity());
i++;
}
serialNumberRepository.save(serialNumberCommandEntity);
// 判断流水号是否超过规则限制的长度,如果超过不报错直接返回,没超过需要在前面补0再返回
if (String.valueOf(serialNumber).length() <= codeRuleCommandEntity.getSerialNumberLength()) {
String formatStr = "%0" + codeRuleCommandEntity.getSerialNumberLength() + "d";
String serialNumberStr = String.format(formatStr, serialNumber);
return serialNumberPrefix + serialNumberStr;
} else {
return serialNumberPrefix + serialNumber;
}

}

/**
* 获取流水号前缀字符串。
* @param codeRuleId 编码规则ID
* @param businessEntity 业务实体
* @return 流水号前缀字符串
*/
private String getSerialNumberPrefix(String codeRuleId, DomainEntity businessEntity) {
List<CodeDetailCommandEntity> details =
codeDetailCommandRepository.findByCodeRuleIdAndDeletedIsFalseOrderBySortAsc(codeRuleId);
StringBuilder stringBuilder = new StringBuilder();
try {
for (CodeDetailCommandEntity detail : details) {
CodeRuleItem codeRuleItem = detail.getCodeRuleItem();
// 判断是否是固定字符
if (StringUtils.isNotBlank(detail.getFixedCharacterValue())) {
stringBuilder.append(detail.getFixedCharacterValue()).append(detail.getConnector());
} else {
// 获取class下的字段对象的get方法
String field = codeRuleItem.getField();
// 首字母大写
field = field.substring(0, 1).toUpperCase() + field.substring(1);
Method getMethod = codeRuleItem.getEntityClass().getMethod("get" + field);
// DomainEntity对象强转为子类对象,通过get方法获取字段值
String value = codeRuleItem.getCodeRuleItemValue(
getMethod.invoke(codeRuleItem.getEntityClass().cast(businessEntity)));
// 拼上值与连接符
stringBuilder.append(value).append(detail.getConnector());
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
// 不报错,没取到的视为空串
logger.error("获取流水号前缀字符串失败", e);
}
return stringBuilder.toString();
}

页面显示

importing-data-sample