一顿优化IdGeneratorService

This commit is contained in:
Turbolisten 2021-10-16 18:27:06 +08:00
parent 7295b90320
commit 98d80a0a03
15 changed files with 236 additions and 160 deletions

View File

@ -12,7 +12,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* [ admin 项目启动类 ]
*
* @author 罗伊
*
*/
@SpringBootApplication
@EnableCaching

View File

@ -18,7 +18,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
@ -39,7 +39,7 @@ import java.util.Map;
* @date 2020/8/25 11:57
*/
@Slf4j
@Component
@Configuration
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = "cloud")
public class FileStorageCloudServiceImpl implements IFileStorageService {

View File

@ -12,7 +12,7 @@ import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
@ -28,7 +28,7 @@ import java.io.InputStream;
* @date 2020/8/25 11:57
*/
@Slf4j
@Component
@Configuration
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = "local")
public class FileStorageLocalServiceImpl implements IFileStorageService {

View File

@ -27,7 +27,7 @@ public class IdGeneratorController extends SupportBaseController {
@Autowired
private IdGeneratorService idGeneratorService;
@ApiOperation("生成id")
@ApiOperation("生成id by listen")
@GetMapping("/id/generator/{generatorId}")
public ResponseDTO<String> generate(@PathVariable Integer generatorId) {
IdGeneratorEnum idGeneratorEnum = SmartBaseEnumUtil.getEnumByValue(generatorId, IdGeneratorEnum.class);

View File

@ -1,32 +1,17 @@
package net.lab1024.smartadmin.service.module.support.idgenerator;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorEntity;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* zhuo
* id生成 dao
*
* @author zhuo
*/
@Mapper
@Component
public interface IdGeneratorDao {
List<IdGeneratorEntity> selectAll();
int replaceIdGeneratorRecord(@Param("generatorId") Integer generatorId,
@Param("year") int year,
@Param("month") int month,
@Param("day") int day,
@Param("lastNumber") Long lastNumber,
@Param("count") long count);
IdGeneratorRecordDTO selectHistoryLastNumber(@Param("generatorId") Integer generatorId,
@Param("year") int year,
@Param("month") int month,
@Param("day") int day);
public interface IdGeneratorDao extends BaseMapper<IdGeneratorEntity> {
}

View File

@ -0,0 +1,26 @@
package net.lab1024.smartadmin.service.module.support.idgenerator;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* id生成 dao
*
* @author zhuo
*/
@Mapper
@Component
public interface IdGeneratorRecordDao extends BaseMapper<IdGeneratorRecordEntity> {
/**
* 查询id最后生成记录
*
* @param generatorId
* @param timeFormat
* @return
*/
IdGeneratorRecordEntity selectHistoryLastNumber(@Param("generatorId") Integer generatorId, @Param("time") String timeFormat);
}

View File

@ -13,15 +13,13 @@ import net.lab1024.smartadmin.service.common.enumeration.BaseEnum;
@Getter
public enum IdGeneratorEnum implements BaseEnum {
ORDER(1, "订单id"),
CONTRACT(2, "合同id"),
;
private final Integer value;
private final String desc;
@Override
public String toString() {
return "IdGeneratorEnum{" + "id=" + value + ", keyName='" + desc + '\'' + '}';
}
}

View File

@ -2,6 +2,7 @@ package net.lab1024.smartadmin.service.module.support.idgenerator.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.lab1024.smartadmin.service.common.constant.StringConst;
import net.lab1024.smartadmin.service.common.enumeration.BaseEnum;
/**
@ -15,7 +16,7 @@ public enum IdGeneratorRuleTypeEnum implements BaseEnum {
/**
* 没有周期
*/
NO_CYCLE("", "没有周期"),
NO_CYCLE(StringConst.EMPTY_STR, "没有周期"),
/**
* 年周期
*/

View File

@ -4,8 +4,9 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorEnum;
import net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorRuleTypeEnum;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
@ -15,22 +16,20 @@ import java.time.LocalDateTime;
*/
@Data
@TableName("t_id_generator")
public class IdGeneratorEntity implements Serializable {
private static final long serialVersionUID = 5582354131134766548L;
public class IdGeneratorEntity {
/**
* 主键id
*
* @see net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorEnum
* @see IdGeneratorEnum
*/
@TableId(type = IdType.AUTO)
@TableId(type = IdType.INPUT)
private Integer id;
/**
* 英文key
* 业务
*/
private String keyName;
private String businessName;
/**
* 前缀
@ -38,15 +37,17 @@ public class IdGeneratorEntity implements Serializable {
private String prefix;
/**
* 最低补位长度
*/
private Integer minLength;
/**
* 类型
* 生成规则
*
* @see IdGeneratorRuleTypeEnum
*/
private String ruleType;
/**
* 最低生成id长度
*/
private Integer minLength;
/**
* 初始值
*/

View File

@ -1,5 +1,8 @@
package net.lab1024.smartadmin.service.module.support.idgenerator.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@ -10,15 +13,15 @@ import java.time.LocalDateTime;
* @Description:
*/
@Data
public class IdGeneratorRecordDTO {
@TableName("t_id_generator_record")
public class IdGeneratorRecordEntity {
@TableId(type = IdType.AUTO)
private Long id;
private Integer generatorId;
private Integer year;
private Integer month;
private Integer day;
private String time;
private Long lastNumber;

View File

@ -1,28 +1,29 @@
package net.lab1024.smartadmin.service.module.support.idgenerator.service;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.smartadmin.service.common.code.UnexpectedErrorCode;
import net.lab1024.smartadmin.service.common.code.UserErrorCode;
import net.lab1024.smartadmin.service.common.constant.RedisKeyConst;
import net.lab1024.smartadmin.service.common.domain.ResponseDTO;
import net.lab1024.smartadmin.service.common.exception.BusinessException;
import net.lab1024.smartadmin.service.common.util.SmartRandomUtil;
import net.lab1024.smartadmin.service.module.support.idgenerator.IdGeneratorDao;
import net.lab1024.smartadmin.service.module.support.idgenerator.IdGeneratorRecordDao;
import net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorEnum;
import net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorRuleTypeEnum;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorEntity;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordDTO;
import net.lab1024.smartadmin.service.third.SmartRedisService;
import net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordEntity;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -34,28 +35,25 @@ import java.util.stream.Collectors;
@Service
public class IdGeneratorService {
private static final int MAX_GET_LOCK_COUNT = 5;
private static final long SLEEP_MILLISECONDS = 500L;
private static volatile long lastSleepMilliSeconds = SLEEP_MILLISECONDS;
private ConcurrentHashMap<Integer, IdGeneratorEntity> idGeneratorMap;
private static final Interner<Integer> POOL = Interners.newWeakInterner();
@Autowired
private IdGeneratorDao idGeneratorDao;
@Autowired
private SmartRedisService redisService;
private IdGeneratorRecordDao idGeneratorRecordDao;
private Map<Integer, IdGeneratorEntity> idGeneratorMap;
@PostConstruct
void init() {
List<IdGeneratorEntity> idGeneratorEntityList = idGeneratorDao.selectAll();
idGeneratorMap = idGeneratorEntityList.stream().collect(Collectors.toMap(IdGeneratorEntity::getId, Function.identity(), (x, y) -> y, ConcurrentHashMap::new));
public void init() {
List<IdGeneratorEntity> idGeneratorEntityList = idGeneratorDao.selectList(null);
idGeneratorMap = idGeneratorEntityList.stream().collect(Collectors.toMap(IdGeneratorEntity::getId, Function.identity()));
log.info("##################### init IdGenerator #####################");
}
/**
* id 生成
* id生成
*
* @param idGeneratorEnum 类型
* @return
@ -64,84 +62,78 @@ public class IdGeneratorService {
int generatorId = idGeneratorEnum.getValue();
IdGeneratorEntity idGeneratorEntity = this.idGeneratorMap.get(generatorId);
if (null == idGeneratorEntity) {
throw new BusinessException("IdGenerator 生成器 不存在 " + idGeneratorEnum.getDesc());
throw new BusinessException("IdGenerator生产业务不存在 " + idGeneratorEnum.getDesc());
}
// 获取全局唯一锁
String lockKey = RedisKeyConst.Support.ID_GENERATOR + idGeneratorEnum.getDesc();
boolean lock = false;
for (int i = 0; i < MAX_GET_LOCK_COUNT; i++) {
try {
//60秒
lock = redisService.getLock(lockKey, 60 * 1000L);
if (lock) {
break;
}
Thread.sleep(Math.max(SLEEP_MILLISECONDS, lastSleepMilliSeconds));
} catch (Throwable e) {
log.error(e.getMessage(), e);
}
}
if (!lock) {
throw new BusinessException("IdGenerator 生成器繁忙,无法处理: " + idGeneratorEnum.getDesc());
}
try {
long beginTime = System.currentTimeMillis();
LocalDateTime now = LocalDateTime.now();
int year = now.getYear();
int monthValue = now.getMonthValue();
int dayOfMonth = now.getDayOfMonth();
IdGeneratorRecordDTO generatorRecordDTO = idGeneratorDao.selectHistoryLastNumber(generatorId, year, monthValue, dayOfMonth);
if (generatorRecordDTO == null) {
generatorRecordDTO = new IdGeneratorRecordDTO();
generatorRecordDTO.setGeneratorId(generatorId);
generatorRecordDTO.setYear(year);
generatorRecordDTO.setMonth(monthValue);
generatorRecordDTO.setDay(dayOfMonth);
generatorRecordDTO.setLastNumber(idGeneratorEntity.getInitNumber());
generatorRecordDTO.setCount(0L);
generatorRecordDTO.setUpdateTime(now);
}
Long lastNumber = generatorRecordDTO.getLastNumber();
// 校验生成规则
IdGeneratorRuleTypeEnum ruleTypeEnum = this.getIdGeneratorRuleTypeEnum(idGeneratorEntity.getRuleType());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ruleTypeEnum.getValue());
String nowFormat = now.format(formatter);
Assert.notNull(ruleTypeEnum, "IdGenerator rule type 不存在 " + idGeneratorEntity.getRuleType());
// 默认起始值
Long startNumber = idGeneratorEntity.getInitNumber();
// 判断是否有循环规则
String timeFormat = null;
DateTimeFormatter timeFormatter = null;
if (IdGeneratorRuleTypeEnum.YEAR_CYCLE == ruleTypeEnum || IdGeneratorRuleTypeEnum.MONTH_CYCLE == ruleTypeEnum || IdGeneratorRuleTypeEnum.DAY_CYCLE == ruleTypeEnum) {
if (!Objects.equals(generatorRecordDTO.getUpdateTime().format(formatter), nowFormat)) {
lastNumber = idGeneratorEntity.getInitNumber();
}
timeFormatter = DateTimeFormatter.ofPattern(ruleTypeEnum.getValue());
timeFormat = LocalDateTime.now().format(timeFormatter);
}
lastNumber += SmartRandomUtil.nextInt(1, idGeneratorEntity.getStepRandomRange());
long count = generatorRecordDTO.getCount() + 1;
idGeneratorDao.replaceIdGeneratorRecord(generatorId, year, monthValue, dayOfMonth, lastNumber, count);
synchronized (POOL.intern(generatorId)) {
// 获取最后一次生成记录
boolean isFirst = false;
IdGeneratorRecordEntity recordEntity = idGeneratorRecordDao.selectHistoryLastNumber(generatorId, timeFormat);
if (recordEntity == null) {
recordEntity = new IdGeneratorRecordEntity();
recordEntity.setGeneratorId(generatorId);
recordEntity.setTime(timeFormat);
recordEntity.setLastNumber(startNumber);
recordEntity.setCount(1L);
idGeneratorRecordDao.insert(recordEntity);
// 格式化num 不足位数则补零
int minLength = idGeneratorEntity.getMinLength();
minLength = minLength <= 0 ? 1 : minLength;
// 补位
String finalId = String.format("%0" + minLength + "d", lastNumber);
String prefix = StringUtils.isBlank(idGeneratorEntity.getPrefix()) ? StringUtils.EMPTY : idGeneratorEntity.getPrefix();
lastSleepMilliSeconds = System.currentTimeMillis() - beginTime + 100;
return prefix + nowFormat + finalId;
} catch (Throwable e) {
log.error(e.getMessage(), e);
throw e;
} finally {
redisService.unLock(lockKey);
}
isFirst = true;
}
// 没有循环 在同个循环周期内起始值 = 上次id
if (IdGeneratorRuleTypeEnum.NO_CYCLE == ruleTypeEnum || Objects.equals(recordEntity.getUpdateTime().format(timeFormatter), timeFormat)) {
startNumber = recordEntity.getLastNumber();
}
// 在范围内随机生成此次增加的数值 更新id生成记录
if (!isFirst) {
startNumber += SmartRandomUtil.nextInt(1, idGeneratorEntity.getStepRandomRange());
IdGeneratorRecordEntity updateRecordEntity = new IdGeneratorRecordEntity();
updateRecordEntity.setId(recordEntity.getId());
updateRecordEntity.setLastNumber(startNumber);
updateRecordEntity.setCount(recordEntity.getCount() + 1);
idGeneratorRecordDao.updateById(updateRecordEntity);
}
// 默认 最低长度 1
int minLength = NumberUtils.max(idGeneratorEntity.getMinLength(), 1);
// id长度补位
String finalId = String.format("%0" + minLength + "d", startNumber);
if (null != timeFormat) {
finalId = timeFormat + finalId;
}
// 前缀
if (StringUtils.isNotBlank(idGeneratorEntity.getPrefix())) {
finalId = idGeneratorEntity.getPrefix() + finalId;
}
return finalId;
}
}
/**
* 查询生成规则
*
* @param ruleType
* @return 没有则返回null
*/
private IdGeneratorRuleTypeEnum getIdGeneratorRuleTypeEnum(String ruleType) {
for (IdGeneratorRuleTypeEnum en : IdGeneratorRuleTypeEnum.values()) {
if (en.name().equalsIgnoreCase(ruleType)) {
return en;
}
}
return null;
return Arrays.stream(IdGeneratorRuleTypeEnum.values())
.filter(e -> StringUtils.equalsIgnoreCase(e.name(), ruleType))
.findFirst().orElse(null);
}
}

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.lab1024.smartadmin.service.module.support.idgenerator.IdGeneratorDao">
<!-- 查询上次id -->
<select id="selectAll" resultType="net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorEntity">
select * from t_id_generator
</select>
<select id="selectHistoryLastNumber" resultType="net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordDTO">
select * from t_id_generator_record
where generator_id = #{generatorId} and year=#{year} and month=#{month} and day=#{day}
</select>
<update id="replaceIdGeneratorRecord">
REPLACE INTO t_id_generator_record (generator_id, `year`, `month`, `day`, last_number,`count`)
values (#{generatorId}, #{year}, #{month}, #{day}, #{lastNumber},#{count})
</update>
</mapper>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.lab1024.smartadmin.service.module.support.idgenerator.IdGeneratorRecordDao">
<select id="selectHistoryLastNumber" resultType="net.lab1024.smartadmin.service.module.support.idgenerator.domain.IdGeneratorRecordEntity">
select * from t_id_generator_record
where generator_id = #{generatorId}
<if test="null != time">
and `time`= #{time}
</if>
ORDER BY id DESC LIMIT 1
</select>
<update id="replaceIdGeneratorRecord">
UPDATE t_id_generator_record
<set>
<if test="null != year">
and `year`= #{year}
</if>
<if test="null != month">
and `month`= #{month}
</if>
<if test="null != day">
and `day`= #{day}
</if>
</set>
where generator_id = #{generatorId}
</update>
</mapper>

View File

@ -0,0 +1,25 @@
package net.lab1024.smartadmin.service;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SmartAdminApplicationTest {
@BeforeEach
public void before() {
System.out.println("----------------------- 测试开始 -----------------------");
}
@AfterEach
public void after() {
System.out.println("----------------------- 测试结束 -----------------------");
}
}

View File

@ -0,0 +1,36 @@
package net.lab1024.smartadmin.service.module.support.idgenerator.service;
import net.lab1024.smartadmin.service.SmartAdminApplicationTest;
import net.lab1024.smartadmin.service.module.support.idgenerator.constant.IdGeneratorEnum;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.CountDownLatch;
class IdGeneratorServiceTest extends SmartAdminApplicationTest {
@Autowired
private IdGeneratorService generatorService;
/**
* id 生成测试
*/
@Test
void generateTest() throws InterruptedException {
int thread = 10;
CountDownLatch countDownLatch = new CountDownLatch(thread);
Runnable task = () -> {
String id = generatorService.generate(IdGeneratorEnum.CONTRACT);
System.out.println(countDownLatch.getCount() + "生成id->" + id);
countDownLatch.countDown();
};
for (int i = 0; i < thread; i++) {
new Thread(task).start();
}
countDownLatch.await();
}
}