接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。✋github地址🤚欢迎start、clone
1、基础知识
发号器的实现主要用到了下面的一些知识点:
2、具体实现
整个starter的目录结构如下
├── 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的相关依赖
<?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>
定义一个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<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、定义一个产生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 文件内容如下:
<?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、新建一个控制器内容如下
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);
}
}