springboot-stater + redis + lua 实现一个简单的发号器(3)-- 实现篇

接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。

1、基础知识

发号器的实现主要用到了下面的一些知识点:

1. php中的位运算的操作和求值

2. 计算机原码、补码、反码的基本概念

3. redis中lua脚本的编写和调试

4. 如何自己定一个spring-boot-starter

2、具体实现

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── srorders
│   │   │           └── starter
│   │   │               ├── SignGenerator.java
│   │   │               ├── UuidConfiguration.java
│   │   │               └── UuidProperties.java
│   │   └── resources
│   │       ├── META-INF
│   │       │   └── spring.factories
│   │       └── application.yml
│   └── test
│       └── java
│           └── com
│               └── srorders
│                   └── starter
│                       └── SignGeneratorTest.java

pom的相关依赖:



    4.0.0
    com.srorders.starter
    uuid
    1.0.0
    uuid
    uuid
    
        1.8
    
    
        
        
            org.springframework.boot
            spring-boot-starter
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
            org.projectlombok
            lombok
            true
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

        
            org.springframework.boot
            spring-boot-autoconfigure
        
    

    
        
            
                
                org.springframework.boot
                spring-boot-dependencies
                2.6.1
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    


定义一个spring-boot-starter主要分为4个部分:

1、定义一个法号器服务

package com.srorders.starter;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class SignGenerator {

    /**
     * 申请64位内存
     */
    public static final int BITS_FULL = 64;

    /**
     * uuid
     */
    public static final String BITS_FULL_NAME = "id";

    /**
     * 1位符号位
     */
    public static final int BITS_PREFIX = 1;

    /**
     * 41时间位
     */
    public static final int BITS_TIME = 41;

    /**
     * 时间位名称
     */
    public static final String BITS_TIME_NAME = "diffTime";

    /**
     * 产生的时间
     */
    public static final String BITS_GENERATE_TIME_NAME = "generateTime";

    /**
     * 5个服务器位
     */
    public static final int BITS_SERVER = 5;

    /**
     * 服务位名称
     */
    public static final String BITS_SERVER_NAME = "serverId";

    /**
     * 5个worker位
     */
    public static final int BITS_WORKER = 5;

    /**
     * worker位名称
     */
    public static final String BITS_WORKER_NAME = "workerId";

    /**
     * 12个自增位
     */
    public static final int BITS_SEQUENCE = 12;

    /**
     * 自增位名称
     */
    public static final String BITS_SEQUENCE_NAME = "sequenceNumber";


    /**
     * uuid配置
     */
    private UuidProperties uuidProperties;

    /**
     * redis client
     */
    private StringRedisTemplate redisTemplate;

    /**
     * 构造
     *
     * @param uuidProperties
     */
    public SignGenerator(UuidProperties uuidProperties, StringRedisTemplate redisTemplate) {
        this.uuidProperties = uuidProperties;
        this.redisTemplate = redisTemplate;
    }

    private long getStaterOffsetTime() {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return LocalDateTime.parse(uuidProperties.getOffsetTime(), dateTimeFormatter).toInstant(OffsetDateTime.now().getOffset())
                .toEpochMilli();
    }

    /**
     * 获取uuid
     *
     * @return
     */
    public Map getNumber() throws InterruptedException {
        HashMap result = new HashMap<>();
        do {
            long id = 0L;
            long diffTime = Instant.now().toEpochMilli() - this.getStaterOffsetTime();
            long maxDiffTime = (long) (Math.pow(2, BITS_TIME) - 1);

            if (diffTime > maxDiffTime) {
                throw new RuntimeException(String.format("the offsetTime: %s is too small", uuidProperties.getOffsetTime()));
            }

            // 对时间位进行计算
            int shift = BITS_FULL - BITS_PREFIX - BITS_TIME;
            id |= diffTime << shift;
            result.put(BITS_TIME_NAME, diffTime);

            // 对server进行计算
            shift = shift - BITS_SERVER;
            id |= uuidProperties.getServerId() << shift;
            result.put(BITS_SERVER_NAME, uuidProperties.getServerId());

            // 对worker进行计算
            shift = shift - BITS_WORKER;
            id |= uuidProperties.getWorkerId() << shift;
            result.put(BITS_WORKER_NAME, uuidProperties.getWorkerId());

            // 对sequence进行计算
            Long sequence = this.getSequence("uuid_" + diffTime);
            long maxSequence = (long) (Math.pow(2, BITS_SEQUENCE) - 1);
            if (sequence > maxSequence) {
                Thread.sleep(1);
            } else {
                id |= sequence;
                result.put(BITS_SEQUENCE_NAME, sequence);
                result.put(BITS_FULL_NAME, id);
                return result;
            }
        } while (true);
    }

    /**
     * 获取自增id
     *
     * @param id
     * @return
     */
    private Long getSequence(String id) {
        String lua = " local sequenceKey = KEYS[1]; " +
                "local sequenceNumber = redis.call(\"incr\", sequenceKey); " +
                "redis.call(\"pexpire\", sequenceKey, 100); " +
                "return sequenceNumber";
        RedisScript redisScript = RedisScript.of(lua, Long.class);
        return redisTemplate.execute(redisScript, Collections.singletonList(id));
    }

    /**
     * 反解id
     *
     * @param id
     * @return
     */
    public Map reverseNumber(Long id) {
        HashMap result = new HashMap<>();

        //time
        int shift = BITS_FULL - BITS_PREFIX - BITS_TIME;
        Long diffTime = (id >> shift) & (long) (Math.pow(2, BITS_TIME) - 1);
        result.put(BITS_TIME_NAME, diffTime);

        //generateTime
        Long generateTime = diffTime + this.getStaterOffsetTime();
        result.put(BITS_GENERATE_TIME_NAME, generateTime);

        //server
        shift = shift - BITS_SERVER;
        Long server = (id >> shift) & (long) (Math.pow(2, BITS_SERVER) - 1);
        result.put(BITS_SERVER_NAME, server);

        //worker
        shift = shift - BITS_WORKER;
        Long worker = (id >> shift) & (long) (Math.pow(2, BITS_WORKER) - 1);
        result.put(BITS_WORKER_NAME, worker);

        //sequence
        Long sequence = id & (long) (Math.pow(2, BITS_SEQUENCE) - 1);
        result.put(BITS_SEQUENCE_NAME, sequence);
        return result;
    }
}

2、定义一个产生bean的自动配置类

package com.srorders.starter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * @author zero
 */
@Configuration
@EnableConfigurationProperties(UuidProperties.class)
@ConditionalOnClass({StringRedisTemplate.class, UuidProperties.class})
public class UuidConfiguration {
   @Bean
   @ConditionalOnMissingBean(SignGenerator.class)
   public SignGenerator signGenerator(UuidProperties  uuidProperties, StringRedisTemplate stringRedisTemplate) {
       return new SignGenerator(uuidProperties, stringRedisTemplate);
   }
}

3、定义一个映射application.properties(application.yml)配置的对象

package com.srorders.starter;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author zero
 */
@ConfigurationProperties("spring.uuid")
@Data
public class UuidProperties {

    private Long serverId = 0L;

    private Long workerId = 0L;

    private String offsetTime = "2021-12-07 00:00:00";
}

4、在resources目录下创建 META-INF 目录,然后在该目录下创建 spring.factories 文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.srorders.starter.UuidConfiguration

3、运行一把

1、建立一个简单的spring-boot-web项目, pom.xml 文件内容如下:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.6.1
         
    
    com.srorders.spring.boot
    starter-demo
    0.0.1-SNAPSHOT
    starter-demo
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-autoconfigure
        

        
        
            com.srorders.starter
            uuid
            1.0.0
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    


2、新建一个控制器内容如下

package com.srorders.spring.boot.controller;

import com.srorders.starter.SignGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
public class UuidController {

    @Autowired
    SignGenerator signedService;

    @GetMapping("/getUuid")
    public String getUuid() throws InterruptedException {
        return this.signedService.getNumber().get(SignGenerator.BITS_FULL_NAME).toString();
    }

    @GetMapping("/reverse")
    public Map reverse(@RequestParam(value = "id") Long id) throws InterruptedException {
        return this.signedService.reverseNumber(id);
    }
}

你可能感兴趣的:(springboot-stater + redis + lua 实现一个简单的发号器(3)-- 实现篇)