基于redis实现每日序列

本文介绍了一种订单编号的生成规则,采用前缀加日期与序列号的组合方式,确保了编号的唯一性和有序性。详细解释了数据库和Redis两种实现方案,并提供了Spring Boot配置和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业务场景

订单编号业务规则为前缀-{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");
    }
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值