接著上一篇 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);
}
}