基于redis实现每日序列

业务场景

订单编号业务规则为前缀-{yyyyMMddHHmmss}-8位序列号,其中序列号每天从使用一个新的序列

数据库实现

生成一张序列表,有如下结构:

业务类型日期序列值
ORDER2020-02-201320
ORDER2020-02-211998
ORDER2020-02-223241
ORDER2020-02-232222
ORDER2020-02-243678

操作的步骤:

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

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值