previous article 161b1680d22046 php + redis + lua to implement a simple issuer (1) , this article talks about the specific implementation of the spring-boot-starter version of the issuer. ✋github address🤚 welcome start, clone

1. Basic knowledge

The realization of the issuer mainly uses the following knowledge points:

1. The operation and evaluation of bit operations in php

2. The basic concepts of computer original code, complement and inverse code

3. Writing and debugging of lua scripts in redis

4. How to order a spring-boot-starter by

2. Concrete realization

The directory structure of the entire starter is as follows

├── 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

Related dependencies of pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.srorders.starter</groupId>
    <artifactId>uuid</artifactId>
    <version>1.0.0</version>
    <name>uuid</name>
    <description>uuid</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--Spring Boot Starter: START-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 引入redis的starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.6.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

defines a spring-boot-starter mainly divided into 4 parts:

1. Define an issuer service

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<String, Long> getNumber() throws InterruptedException {
        HashMap<String, Long> 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<Long> redisScript = RedisScript.of(lua, Long.class);
        return redisTemplate.execute(redisScript, Collections.singletonList(id));
    }

    /**
     * 反解id
     *
     * @param id
     * @return
     */
    public Map<String, Long> reverseNumber(Long id) {
        HashMap<String, Long> 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. Define an automatic configuration class that generates beans

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. Define an object mapping application.properties (application.yml) configuration

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. Create a META-INF directory in the resources directory, and then create a spring.factories file in this directory, with the following content:

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

3. Run it

1. Create a simple spring-boot-web project, the content of the pom.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.srorders.spring.boot</groupId>
    <artifactId>starter-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>starter-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <!-- 这里我们引入我们自定义的 starter -->
        <dependency>
            <groupId>com.srorders.starter</groupId>
            <artifactId>uuid</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2. The content of a new controller is as follows

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<String,Long> reverse(@RequestParam(value = "id") Long id) throws InterruptedException {
        return this.signedService.reverseNumber(id);
    }
}

maweibinguo
783 声望36 粉丝

后端开发工程师一枚, keep moving