文
章
目
录
章
目
录
遇到个需求就是针对某个路由页面,只能打开一个tab标签页,不管是跨浏览器还是不跨浏览器,都进行限制,也就是说针对这一台设备的ip,只允许打开一个标签页,那么该怎么实现呢?
纯前端的方案肯定不行,没办法解决跨浏览器问题,只能寻求后端解决方案,这里的方案思路是:
该页面定时给后台发心跳,如果发现该页面已存在打开的情况,就保留最新打开的,关闭之前打开的,优点就是可以跨浏览器,缺点就是心跳频率较高,后端如果使用缓存多节点需要保证缓存一致性较麻烦,不用缓存就可能就要刷数据库。
这里设定为单节点,直接使用缓存,缓存用的是hutool缓存,每个页面定时任务发送心跳都要携带打开或刷新页面的时间戳作为pageId进行区分,
前端代码实现
// data下面
heartbeatInterval: null,
pageId: (new Date()).getTime(),
// methods下面
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
this.sendHeartbeat();
}, 2000);
},
stopHeartbeat() {
clearInterval(this.heartbeatInterval);
},
sendHeartbeat() {
send({"pageId":this.pageId})
.then(response => {
console.log (response.data);
// 如果当前pageId时间戳值小于后台最新打开时记录的时间戳,则关闭当前
if(this.pageId < response.data) {
console.log("close")
var currentView = this.$store.getters.visitedViews[0]
for (currentView of this.$store.getters.visitedViews) {
if (currentView.path === this.$route.path) {
break
}
}
// 关闭当前标签
this.$store.dispatch("tagsView/delView", currentView)
this.$router.push("/dashboard")
}else {
console.log("open")
}
})
.catch(error => {
// Handle errors if needed
});
},
//挂载执行
mounted() {
this.startHeartbeat();
},
api的send方法:
export function send(data) {
return request({
url: '/sync/heartbeat',
params: data,
method: 'get'
})
}
后端代码实现
private static final Cache<String, String> heartbeatCache = CacheUtil.newTimedCache(10 * 1000);
@GetMapping("/heartbeat")
public ResponseResult handleHeartbeat(HttpServletRequest request) {
String ipAddress = request.getRemoteAddr();
String pageId = request.getParameter("pageId");
String pageIdMax = heartbeatCache.get(ipAddress);
if(pageIdMax == null) {
heartbeatCache.put(ipAddress, pageId);
}else if(Long.valueOf(pageId) > Long.valueOf(pageIdMax)){
heartbeatCache.put(ipAddress, pageId);
}
return ResponseResult.success(heartbeatCache.get(ipAddress));
}
主要就是根据ip进行缓存判断是否是最新打开的,如果是最新打开的直接替换pageId为最新打开的时间戳。
实验结果
同浏览器和跨浏览器都测试成功,左边为chrome,右边为edge,右边重复打开,左边自动关闭,延迟取决于你的心跳频率。
总结
这是一种比较容易想到的解决方案,如果你这样的页面还不止一个,那就继续优化下缓存的key,将其和页面名称挂钩即可,如果你有更好的方案,欢迎留言!