短链接记录变更分组
# 持久层
ShortLinkUpdateReqDTO中添加:originGid
;
ShortLinkDO中添加:delTime
,为了复用某个short-url
,将delTime
假如唯一索引fullShortUrl
中;
包装统计记录ShortLinkStatsRecordDTO
:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ShortLinkStatsRecordDTO {
/**
* 完整短链接
*/
private String fullShortUrl;
/**
* 访问用户IP
*/
private String remoteAddr;
/**
* 操作系统
*/
private String os;
/**
* 浏览器
*/
private String browser;
/**
* 操作设备
*/
private String device;
/**
* 网络
*/
private String network;
/**
* UV
*/
private String uv;
/**
* UV访问标识
*/
private Boolean uvFirstFlag;
/**
* UIP访问标识
*/
private Boolean uipFirstFlag;
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 数据迁移
如果涉及数据分片,先删除再新增;不涉及分片直接修改数据库表记录;
# 读写锁
如果短链接正在修改分组,此时有用户正在访问短链接,统计监控相关的分组还是之前的数据;
读写锁优势:允许多个线程同时读取共享资源,提供读并发性;
@Transactional(rollbackFor = Exception.class)
@Override
public void updateShortLink(ShortLinkUpdateReqDTO requestParam) {
LambdaQueryWrapper<ShortLinkDO> queryWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
.eq(ShortLinkDO::getGid, requestParam.getOriginGid())
.eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getEnableStatus, 0);
ShortLinkDO hasShortLinkDO = baseMapper.selectOne(queryWrapper);
if (hasShortLinkDO == null) {
throw new ClientException("短链接记录不存在");
}
if (Objects.equals(hasShortLinkDO.getGid(), requestParam.getGid())) {
LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
.eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(ShortLinkDO::getGid, requestParam.getGid())
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getEnableStatus, 0)
.set(Objects.equals(requestParam.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()), ShortLinkDO::getValidDate, null);
ShortLinkDO shortLinkDO = ShortLinkDO.builder()
.domain(hasShortLinkDO.getDomain())
.shortUri(hasShortLinkDO.getShortUri())
.favicon(hasShortLinkDO.getFavicon())
.createdType(hasShortLinkDO.getCreatedType())
.gid(requestParam.getGid())
.originUrl(requestParam.getOriginUrl())
.describe(requestParam.getDescribe())
.validDateType(requestParam.getValidDateType())
.validDate(requestParam.getValidDate())
.build();
baseMapper.update(shortLinkDO, updateWrapper);
} else {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl()));
RLock rLock = readWriteLock.writeLock();
if (!rLock.tryLock()) {
throw new ServiceException("短链接正在被访问,请稍后再试...");
}
try {
LambdaUpdateWrapper<ShortLinkDO> linkUpdateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
.eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(ShortLinkDO::getGid, hasShortLinkDO.getGid())
.eq(ShortLinkDO::getDelFlag, 0)
.eq(ShortLinkDO::getDelTime, 0L)
.eq(ShortLinkDO::getEnableStatus, 0);
ShortLinkDO delShortLinkDO = ShortLinkDO.builder()
.delTime(System.currentTimeMillis())
.build();
delShortLinkDO.setDelFlag(1);
baseMapper.update(delShortLinkDO, linkUpdateWrapper);
ShortLinkDO shortLinkDO = ShortLinkDO.builder()
.domain(createShortLinkDefaultDomain)
.originUrl(requestParam.getOriginUrl())
.gid(requestParam.getGid())
.createdType(hasShortLinkDO.getCreatedType())
.validDateType(requestParam.getValidDateType())
.validDate(requestParam.getValidDate())
.describe(requestParam.getDescribe())
.shortUri(hasShortLinkDO.getShortUri())
.enableStatus(hasShortLinkDO.getEnableStatus())
.totalPv(hasShortLinkDO.getTotalPv())
.totalUv(hasShortLinkDO.getTotalUv())
.totalUip(hasShortLinkDO.getTotalUip())
.fullShortUrl(hasShortLinkDO.getFullShortUrl())
.favicon(getFavicon(requestParam.getOriginUrl()))
.delTime(0L)
.build();
baseMapper.insert(shortLinkDO);
LambdaQueryWrapper<LinkStatsTodayDO> statsTodayQueryWrapper = Wrappers.lambdaQuery(LinkStatsTodayDO.class)
.eq(LinkStatsTodayDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkStatsTodayDO::getGid, hasShortLinkDO.getGid())
.eq(LinkStatsTodayDO::getDelFlag, 0);
List<LinkStatsTodayDO> linkStatsTodayDOList = linkStatsTodayMapper.selectList(statsTodayQueryWrapper);
if (CollUtil.isNotEmpty(linkStatsTodayDOList)) {
linkStatsTodayMapper.deleteBatchIds(linkStatsTodayDOList.stream()
.map(LinkStatsTodayDO::getId)
.toList()
);
linkStatsTodayDOList.forEach(each -> each.setGid(requestParam.getGid()));
linkStatsTodayService.saveBatch(linkStatsTodayDOList);
}
LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
.eq(ShortLinkGotoDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(ShortLinkGotoDO::getGid, hasShortLinkDO.getGid());
ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapper);
shortLinkGotoMapper.deleteById(shortLinkGotoDO.getId());
shortLinkGotoDO.setGid(requestParam.getGid());
shortLinkGotoMapper.insert(shortLinkGotoDO);
LambdaUpdateWrapper<LinkAccessStatsDO> linkAccessStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkAccessStatsDO.class)
.eq(LinkAccessStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkAccessStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkAccessStatsDO::getDelFlag, 0);
LinkAccessStatsDO linkAccessStatsDO = LinkAccessStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkAccessStatsMapper.update(linkAccessStatsDO, linkAccessStatsUpdateWrapper);
LambdaUpdateWrapper<LinkLocaleStatsDO> linkLocaleStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkLocaleStatsDO.class)
.eq(LinkLocaleStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkLocaleStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkLocaleStatsDO::getDelFlag, 0);
LinkLocaleStatsDO linkLocaleStatsDO = LinkLocaleStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkLocaleStatsMapper.update(linkLocaleStatsDO, linkLocaleStatsUpdateWrapper);
LambdaUpdateWrapper<LinkOsStatsDO> linkOsStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkOsStatsDO.class)
.eq(LinkOsStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkOsStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkOsStatsDO::getDelFlag, 0);
LinkOsStatsDO linkOsStatsDO = LinkOsStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkOsStatsMapper.update(linkOsStatsDO, linkOsStatsUpdateWrapper);
LambdaUpdateWrapper<LinkBrowserStatsDO> linkBrowserStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkBrowserStatsDO.class)
.eq(LinkBrowserStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkBrowserStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkBrowserStatsDO::getDelFlag, 0);
LinkBrowserStatsDO linkBrowserStatsDO = LinkBrowserStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkBrowserStatsMapper.update(linkBrowserStatsDO, linkBrowserStatsUpdateWrapper);
LambdaUpdateWrapper<LinkDeviceStatsDO> linkDeviceStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkDeviceStatsDO.class)
.eq(LinkDeviceStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkDeviceStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkDeviceStatsDO::getDelFlag, 0);
LinkDeviceStatsDO linkDeviceStatsDO = LinkDeviceStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkDeviceStatsMapper.update(linkDeviceStatsDO, linkDeviceStatsUpdateWrapper);
LambdaUpdateWrapper<LinkNetworkStatsDO> linkNetworkStatsUpdateWrapper = Wrappers.lambdaUpdate(LinkNetworkStatsDO.class)
.eq(LinkNetworkStatsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkNetworkStatsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkNetworkStatsDO::getDelFlag, 0);
LinkNetworkStatsDO linkNetworkStatsDO = LinkNetworkStatsDO.builder()
.gid(requestParam.getGid())
.build();
linkNetworkStatsMapper.update(linkNetworkStatsDO, linkNetworkStatsUpdateWrapper);
LambdaUpdateWrapper<LinkAccessLogsDO> linkAccessLogsUpdateWrapper = Wrappers.lambdaUpdate(LinkAccessLogsDO.class)
.eq(LinkAccessLogsDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(LinkAccessLogsDO::getGid, hasShortLinkDO.getGid())
.eq(LinkAccessLogsDO::getDelFlag, 0);
LinkAccessLogsDO linkAccessLogsDO = LinkAccessLogsDO.builder()
.gid(requestParam.getGid())
.build();
linkAccessLogsMapper.update(linkAccessLogsDO, linkAccessLogsUpdateWrapper);
} finally {
rLock.unlock();
}
}
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# 短链接统计
将之前的shortLinkStats
方法拆分为buildLinkStatsRecordAndSetUser
与shortLinkStats
;
短链接跳转方法restoreUrl
中复用上述两种方法;
# 延迟队列
如果用户正在修改短链接分组,因为涉及到表操作很多,可能需要操作300ms,期间如果其他用户访问,需要将此访问记录扔进延迟队列;
在shorLinkStats方法中:
@Override
public void shortLinkStats(String fullShortUrl, String gid, ShortLinkStatsRecordDTO statsRecord) {
fullShortUrl = Optional.ofNullable(fullShortUrl).orElse(statsRecord.getFullShortUrl());
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, fullShortUrl));
RLock rLock = readWriteLock.readLock();
// 没有拿到读锁,说明有线程拿到写锁,正在修改;
if (!rLock.tryLock()) {
delayShortLinkStatsProducer.send(statsRecord);
return;
}
//....
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
生产者:
@Component
@RequiredArgsConstructor
public class DelayShortLinkStatsProducer {
private final RedissonClient redissonClient;
/**
* 发送延迟消费短链接统计
* @param statsRecord 短链接统计实体参数
*/
public void send(ShortLinkStatsRecordDTO statsRecord) {
RBlockingDeque<ShortLinkStatsRecordDTO> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE_STATS_KEY);
RDelayedQueue<ShortLinkStatsRecordDTO> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
delayedQueue.offer(statsRecord, 5, TimeUnit.SECONDS);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
消费者:
@Component
@RequiredArgsConstructor
public class DelayShortLinkStatsConsumer implements InitializingBean {
private final RedissonClient redissonClient;
private final ShortLinkService shortLinkService;
public void onMessage() {
Executors.newSingleThreadExecutor(
runnable -> {
Thread thread = new Thread(runnable);
thread.setName("delay_short-link_stats_consumer");
thread.setDaemon(Boolean.TRUE);
return thread;
})
.execute(() -> {
RBlockingDeque<ShortLinkStatsRecordDTO> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE_STATS_KEY);
RDelayedQueue<ShortLinkStatsRecordDTO> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
for (; ; ) {
try {
ShortLinkStatsRecordDTO statsRecord = delayedQueue.poll();
if (statsRecord != null) {
shortLinkService.shortLinkStats(null, null, statsRecord);
continue;
}
LockSupport.parkUntil(500);
} catch (Throwable ignored) {
}
}
});
}
@Override
public void afterPropertiesSet() throws Exception {
onMessage();
}
}
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
37
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
37
# 回收站删除
添加了del_time
,回收站删除功能改造:
@Override
public void removeRecycleBin(RecycleBinRemoveReqDTO requestParam) {
LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
.eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
.eq(ShortLinkDO::getGid, requestParam.getGid())
.eq(ShortLinkDO::getEnableStatus, 1)
.eq(ShortLinkDO::getDelTime, 0L)
.eq(ShortLinkDO::getDelFlag, 0);
ShortLinkDO delShortLinkDO = ShortLinkDO.builder()
.delTime(System.currentTimeMillis())
.build();
delShortLinkDO.setDelFlag(1);
baseMapper.update(delShortLinkDO, updateWrapper);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14