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

接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。
1、基础知识 发号器的实现主要用到了下面的一些知识点:
1. php中的位运算的操作和求值
2. 计算机原码、补码、反码的基本概念
3. redis中lua脚本的编写和调试
【springboot-stater|springboot-stater + redis + lua 实现一个简单的发号器(3)-- 实现篇】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 uuid1.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(UuidPropertiesuuidProperties, 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.0org.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 Boot1.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 = "https://www.it610.com/article/id") Long id) throws InterruptedException { return this.signedService.reverseNumber(id); } }

    推荐阅读