业务场景
订单编号业务规则为前缀-{yyyyMMddHHmmss}-8位序列号
,其中序列号
每天从使用一个新的序列
。
数据库实现
生成一张序列表,有如下结构:
业务类型 | 日期 | 序列值 |
---|---|---|
ORDER | 2020-02-20 | 1320 |
ORDER | 2020-02-21 | 1998 |
ORDER | 2020-02-22 | 3241 |
ORDER | 2020-02-23 | 2222 |
ORDER | 2020-02-24 | 3678 |
操作的步骤:
1
.从该表中,根据业务类型
+日期
获取序列值,- 若未存在则跳转到
2
- 若已存在序列则跳转到
3
- 若未存在则跳转到
2
. 向数据库中插入一条记录,序列值为1,并返回。流程结束3.
数据库已存在记录值更新自增1
,返回自增后的值。流程结束
redis实现
主要使用redis字段的失效特性
.来实现。
伪代码
if(!redisTemplate.hasKey(redisKey) && redisTemplate.opsForValue().setIfAbsent(redisKey,0)) {
//当第一次设置时, 设置有效时间 ---- 有效期至 23:59:59,999999999
Long ttl = Duration.between(LocalTime.now(),LocalTime.MAX).getSeconds();
redisTemplate.expire(redisKey, ttl, TimeUnit.SECONDS);
}
//自增1
Long value = redisTemplate.opsForValue().increment(redisKey, 1);
return value;
结合spring-boot实现
sequence:
## 由配置实现,是否启用"每日序列"功能
enable: true
list:
- type: ORDER
prefix: ${sequence.list[0].type}-
pattern: "yyMMdd"
length: 4
- type: PROJECT
prefix: ${sequence.list[1].type}_
pattern: "yyyyMMddHHmmss"
length: 8
config-bean
@Configuration
@ConfigurationProperties(prefix = SEQUENCE_CONFIG_PREFIX)
@Data
public class SequenceConfig {
public static final String SEQUENCE_CONFIG_PREFIX = "sequence";
private static final String CACHE_KEY_SEPARATOR = "::";
private List<Sequence> list;
@Data
public static class Sequence {
/**
* 类型,作为redis-key
*/
private String type;
/**
* 业务编号的前缀
*/
private String prefix;
/**
* 时间格式
*/
private String pattern;
/**
* 序列号长度, 不足补0
*/
private int length;
///
public String getCacheKey() {
return SEQUENCE_CONFIG_PREFIX + CACHE_KEY_SEPARATOR + type;
}
private DateTimeFormatter dft ;
private DateTimeFormatter getDateTimeFormatter(){
if(this.dft == null) {
this.dft = DateTimeFormatter.ofPattern(this.pattern);
}
return this.dft;
}
public String next(Long seq) {
String timeStr = LocalDateTime.now().format(getDateTimeFormatter());
String content = String.format("%0" + this.length + "d", seq);
StringBuilder sb = new StringBuilder();
sb.append(this.prefix)
.append(timeStr)
.append(content);
return sb.toString();
}
}
}
实现方法service
@Service
@ConditionalOnProperty(prefix = SequenceConfig.SEQUENCE_CONFIG_PREFIX, name = "enable", havingValue = "true")
public class SequenceServiceImpl implements SequenceService {
@Autowired
private SequenceConfig sequenceConfig;
@Autowired
private RedisTemplate redisTemplate;
@Override
public String dailyNext(String type) {
SequenceConfig.Sequence sequence = sequenceConfig.getList()
.stream()
.filter(item -> item.getType().equalsIgnoreCase(type))
.findAny()
.orElseThrow(() -> new ServiceException("未找到序列{" + type + "}的配置"));
String redisKey = sequence.getCacheKey();
//需要确保 set和expire在一个事务中
if(!redisTemplate.hasKey(redisKey)) {
redisTemplate.execute((RedisConnection connection) -> {
Long ttl = Duration.between(LocalTime.now(),LocalTime.MAX).getSeconds();
return connection.set(redisKey.getBytes(), "0".getBytes(), Expiration.seconds(ttl), RedisStringCommands.SetOption.SET_IF_ABSENT);
});
}
//自增1
Long value = redisTemplate.opsForValue().increment(redisKey, 1);
return sequence.next(value);
}
}
测试用例
public class SequenceServiceImplTest {
@Autowired
private SequenceService sequenceService;
@ParameterizedTest
@MethodSource("typeProvider")
public void dailyNext(String type) {
IntStream.range(1,30).forEach(item -> {
System.out.println("item------------"+sequenceService.dailyNext(type));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
Stream<String> typeProvider(){
return Stream.of("ORDER", "PROJECT");
}
}