1:新增短链接分组
分组sql
CREATE TABLE `t_group` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`gid` varchar(32) DEFAULT NULL COMMENT '分组标识',
`name` varchar(64) DEFAULT NULL COMMENT '分组名称',
`username` varchar(256) DEFAULT NULL COMMENT '创建分组用户名',
`sort_order` int(3) DEFAULT NULL COMMENT '分组排序',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`del_flag` tinyint(1) DEFAULT NULL COMMENT '删除标识 0:未删除 1:已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_unique_username_gid` (`gid`,`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;;
controller
@PostMapping("/api/short-link/admin/v1/group")
public Result<Void> save(@RequestBody ShortLinkGroupSaveReqDTO requestParam) {
groupService.saveGroup(requestParam.getName());
return Results.success();
}
service
@Value("${short-link.group.max-num}")
private Integer groupMaxNum;
@Override
public void saveGroup(String groupName) {
saveGroup(UserContext.getUsername(), groupName);
}
@Override
public void saveGroup(String username, String groupName) {
RLock lock = redissonClient.getLock(String.format(LOCK_GROUP_CREATE_KEY, username));
lock.lock();
try {
LambdaQueryWrapper<GroupDO> queryWrapper = Wrappers.lambdaQuery(GroupDO.class)
.eq(GroupDO::getUsername, username)
.eq(GroupDO::getDelFlag, 0);
List<GroupDO> groupDOList = baseMapper.selectList(queryWrapper);
if (CollUtil.isNotEmpty(groupDOList) && groupDOList.size() == groupMaxNum) {
throw new ClientException(String.format("已超出最大分组数:%d", groupMaxNum));
}
int retryCount = 0;
int maxRetries = 10;
String gid = null;
while (retryCount < maxRetries) {
gid = saveGroupUniqueReturnGid();
if (StrUtil.isNotEmpty(gid)) {
GroupDO groupDO = GroupDO.builder()
.gid(gid)
.sortOrder(0)
.username(username)
.name(groupName)
.build();
baseMapper.insert(groupDO);
gidRegisterCachePenetrationBloomFilter.add(gid);
break;
}
retryCount++;
}
if (StrUtil.isEmpty(gid)) {
throw new ServiceException("生成分组标识频繁");
}
} finally {
lock.unlock();
}
}
private String saveGroupUniqueReturnGid() {
String gid = RandomGenerator.generateRandom();
if (!gidRegisterCachePenetrationBloomFilter.contains(gid)) {
GroupUniqueDO groupUniqueDO = GroupUniqueDO.builder()
.gid(gid)
.build();
try {
groupUniqueMapper.insert(groupUniqueDO);
} catch (DuplicateKeyException e) {
return null;
}
}
return gid;
}
分组ID随机生成器 RandomGenerator
public final class RandomGenerator {
private static final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final SecureRandom RANDOM = new SecureRandom();
public static String generateRandom() {
return generateRandom(6);
}
public static String generateRandom(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int randomIndex = RANDOM.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(randomIndex));
}
return sb.toString();
}
}
布隆过滤器 RBloomFilterConfiguration
@Configuration(value = "rBloomFilterConfigurationByAdmin")
public class RBloomFilterConfiguration {
@Bean
public RBloomFilter<String> userRegisterCachePenetrationBloomFilter(RedissonClient redissonClient) {
RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("userRegisterCachePenetrationBloomFilter");
cachePenetrationBloomFilter.tryInit(100000000L, 0.001);
return cachePenetrationBloomFilter;
}
@Bean
public RBloomFilter<String> gidRegisterCachePenetrationBloomFilter(RedissonClient redissonClient) {
RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("gidRegisterCachePenetrationBloomFilter");
cachePenetrationBloomFilter.tryInit(200000000L, 0.001);
return cachePenetrationBloomFilter;
}
}
短链接分组唯一路由实体 GroupUniqueDO
@Data
@TableName("t_group_unique")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GroupUniqueDO {
private Long id;
private String gid;
}
用户信息传输过滤器 UserTransmitFilter
@RequiredArgsConstructor
public class UserTransmitFilter implements Filter {
@SneakyThrows
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String username = httpServletRequest.getHeader("username");
if (StrUtil.isNotBlank(username)) {
String userId = httpServletRequest.getHeader("userId");
String realName = httpServletRequest.getHeader("realName");
UserInfoDTO userInfoDTO = new UserInfoDTO(userId, username, realName);
UserContext.setUser(userInfoDTO);
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
UserContext.removeUser();
}
}
}
用户配置自动装配 UserConfiguration
@Configuration
public class UserConfiguration {
@Bean
public FilterRegistrationBean<UserTransmitFilter> globalUserTransmitFilter() {
FilterRegistrationBean<UserTransmitFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new UserTransmitFilter());
registration.addUrlPatterns("/*");
registration.setOrder(0);
return registration;
}
}
用户上下文信息 UserContext
package com.nageoffer.shortlink.admin.common.biz.user;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Optional;
public final class UserContext {
private static final ThreadLocal<UserInfoDTO> USER_THREAD_LOCAL = new TransmittableThreadLocal<>();
public static void setUser(UserInfoDTO user) {
USER_THREAD_LOCAL.set(user);
}
public static String getUserId() {
UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getUserId).orElse(null);
}
public static String getUsername() {
UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getUsername).orElse(null);
}
public static String getRealName() {
UserInfoDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserInfoDTO::getRealName).orElse(null);
}
public static void removeUser() {
USER_THREAD_LOCAL.remove();
}
}