「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
这篇文章是关于之前产品提的一个需求,大概是:用户打开某个页面时,将这个页面上锁,其他人不能进入,等用户关闭页面才释放锁。
初步解决
一开始,我的想法自然是在入口处(即路由跳转之前)调用接口上锁,再在beforeDestroy中释放锁。
发现问题
后面发现,进到页面后,在当前页面强制关闭页签,是不会走beforeDestroy这个钩子的,故引入第2步:页面created生命周期里增加beforeunload监听,destroyed里取消掉该监听,代码如下:
created() {
window.addEventListener('beforeunload', e => this.beforeunloadFn(e))
},
beforeDestroy () {
this.releaseLock();
},
destroyed () {
window.removeEventListener('beforeunload', e => this.beforeunloadFn(e))
},
methods: {
beforeunloadFn() {
this.releaseLock();
},
releaseLock() {
// 调用接口释放锁
}
}
以上方法实现了路由跳转时、浏览器页签关闭以及重新刷新之后的释放锁。问题来了~重新刷新的时候用户的页面还是会在编辑页面的,这个时候没有锁了,别的用户就也能进来编辑了,不允许,于是引入第3步:在页面挂载请求页面数据完之后,页面重新渲染完成之后再请求一次加锁的接口。
if(!localStorage.getItem('actProLockHash')) {
this.$nextTick(()=>{
that.handleViewProjectDetail(that.projectInfo.actInfoHash, that.projectInfo.id)
})
}
进一步完善
问题1
但是但是,beforeunload,unload事件属于浏览器事件,同时也没有相应的规范,不同浏览器会有不同的写法,好坑!!!现在测试的结果是:谷歌浏览器beforeunload事件不是任意时候都能触发的,需要进到页面后,有页面的交互之后关闭浏览器页签或者刷新,才能触发。
问题2
原本beforeunload一般只是用在发起弹框,提示用户浏览器将关闭,这里用来加异步请求,百度了下看到其他人踩过的坑,ajax请求是不支持的,axios是可以的,具体原理还不清楚。虽然axios支持,但是axios方法之后的then方法还没有进去,页签就己经关闭了,也无法很好的解决问题。
对于问题2,监听浏览器页签关闭之后触发的事件中,可以加一个延时,等执行完再关闭浏览器,这样子能基本解决问题2,只设置了100ms,使用过程中用户不会感受到明显的关闭页签缓慢,又能有时间去调用接口释放锁。代码如下:
beforeunloadFn(e,flag) {
if(flag = 'onUnload') {
let now = new Date()
while (new Date() - now < 100) { }
}
this.releaseLock();
}
问题解决
上锁解锁的标志是通过唯一哈希值实现,上锁时服务器返回哈希值,前端把哈希值存储在本地缓存中,解锁时将哈希值作为参数给后端。上面的步骤都是基于这个方法。
但是由于问题1,且后面需求有改动:同个用户要求可以打开多个窗口,且这多个窗口可以是同个锁页面,也可以是不同的锁页面。
因此后端改为通过用户id拼接哈希值来唯一标志锁,之前将锁的哈希值存在本地缓存当中,存的是字符串类型,现也改成对象存储。
基于以上,后端将锁的哈希值拼上用户ID,通过判断ID,使用户可以多次打开同个页面,页面中再通过websocket监听页面关闭,一旦用户关闭其中一个页面,则其它相同锁页面也都关闭。