短链接核心2:创建短链接分组

1. 创建短链接分组

用户创建分组限制最大数量
由于短链接项目中,用户可以创建多个分组,但是每个用户最多只能创建 groupMaxNum 个分组,这里的 groupMaxNum 为 20。

每次新建分组前都需要查询数据库,获取当前用户已经创建的分组数,如果已经超出最大分组数,则抛出异常。

而并发场景下,为了防止同一用户在不同线程(可能同一用户在多个设备)同时新增分组,而超出最大分组数。这里使用了 Redisson 的分布式锁,锁的 key 为 LOCK_GROUP_CREATE_KEY + 用户名,这样就能保证同一用户在不同线程同时新增分组时,只有一个线程能获取到锁,其他线程会阻塞等待。

在获取到锁后,再次查询数据库,判断当前用户是否已经超出最大分组数,如果没有超出,则生成唯一的分组标识 gid,并插入数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();
}
}

2. 生成唯一的分组标识 gid

生成唯一的分组标识 gid解决用户访问短链接监控数据横向越权问题

这里的gid是全局唯一,由于用户横向越权问题。
因此通过布隆过滤器gidRegisterCachePenetrationBloomFilter使得gid全局唯一。
在生成唯一的分组标识 gid 时,首先生成一个随机字符串,然后判断布隆过滤器中是否存在,如果存在则重新生成,直到生成一个布隆过滤器中不存在的 gid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private String saveGroupUniqueReturnGid() {
String gid = RandomGenerator.generateRandom();
if (gidRegisterCachePenetrationBloomFilter.contains(gid)) {
return null;
}
GroupUniqueDO groupUniqueDO = GroupUniqueDO.builder()
.gid(gid)
.build();
try {
groupUniqueMapper.insert(groupUniqueDO);
} catch (DuplicateKeyException e) {
return null;
}
return gid;
}