背景:公司有一个数据看板,,需求是要统计看板有多少人看过,,每个人停留的曝光时间

使用技术:目前使用的技术是 后端Springboot 权限用的是SpringSecurity,前端页面是
Vue+Element,公司看板的使用人大多利用谷歌浏览器查看。。

前端请求权限流程:用户登陆后会把用户的信息加密处理放进cookies,,然后会用vue的拦截器,,拦截要发送的请求,,并且在每个请求的headers里面带上Cookies里面的用户加密信息

// 创建axios实例
const service = axios.create({baseURL: xxx    // api 的 base_urltimeout: 2000 // 请求超时时间
})// request拦截器
service.interceptors.request.use(config => {if (getToken()) {config.headers['AESAuthor'] = 'AESAES ' + getToken() // 让每个请求携带自定义token}config.headers['Content-Type'] = 'application/json'return config},error => {// Do something with request errorconsole.log(error) // for debugPromise.reject(error)}
)

就像Java中的WebMvcConfigurer,可以通过这个实现跨域的处理,或者单独针对某一路径的请求特殊处理

第一个重点!!!(这个问题后面会讲到)
线上环境:在服务器上面运行项目 是通过nginx访问vue的页面,并拦截vue发送的请求转发到后端服务
本地环境:vue通过nodejs转发请求到后端

本地环境处理:vue的页面请求是(例子) http://110.110.110.110:1000/api/url
但实际的后端接口路径应该是:http://110.110.110.110:1001/api/url

    //nginx配置规则location / {     //页面请求走这个路径root /data/app/web/xxx-xxx-xxx-xxx/dist;try_files $uri $uri/ /index.html =404;index  index.html index.htm;}location /api {    //api请求走这个路径if ($request_uri ~* "WhchatUser/sendCode") {return 403;}proxy_set_header        Host $host:$server_port;proxy_set_header        X-Real-IP $remote_addr;proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header        X-Forwarded-Proto $scheme;# Fix the "It appears that your reverse proxy set up is broken" error.proxy_pass          http://110.110.110.110:1001/api;proxy_read_timeout  90;}

请求链路:通过vue的mounted事件创建一个createThisTime用以记录用户打开页面的时间,通过js的 beforeunload 和 unload 事件可以监听到页面的刷新和关闭事件,,在页面关闭的时候发送HTTP请求到后端,带上(1)用户的加密信息做鉴权,(2)当前时间 -createThisTime= 用户停留在这个页面的曝光时间
后端接口接收到请求之后往数据库插入一条数据。至此整个链路完成。

解决此需求的页面加载时只执行 onload 事件
页面关闭时,先 onbeforeunload 事件,再 onunload 事件
页面刷新时先执行 onbeforeunload事件,然后 onunload 事件,最后 onload 事件

直接上代码了!!!


data() {    //第一步初始化createTime参数return {createThisTime:0, //mounted时期赋值,用于统计曝光时间_beforeUnload_time: 0,    //beforeunload时期赋值,在unload时期用于判断是页面关闭还是刷新_gap_time: 0,    //unload时期赋值,在unload时期用于判断是页面关闭还是刷新is_fireFox: false    //判断是否是火狐浏览器,火狐浏览器的beforeunload就是关闭}
}mounted() {//赋值this.is_fireFox = navigator.userAgent.indexOf("Firefox")>-1this.createThisTime = new Date().getTime()this._beforeUnload_time = 0this._gap_time= 0//!!!!创建监听事件(ListenerEvent)!!!!window.addEventListener("beforeunload", e => {this.beforeunloadFn(e)});window.addEventListener("unload", e => {this.unloadFn(e)});
}//!!!核心,你夹紧点,我要来了!!!
methods(){beforeunloadFn(e){//对_beforeUnload_time进行赋值在unloadFn时期与_gap_time进行差距计算判断刷新还是关闭this._beforeUnload_time = new Date().getTime();//统计曝光时间,用于火狐浏览器的关闭let waitTime = this._beforeUnload_time - this.createThisTime;if(this.is_fireFox) {//火狐浏览器执行beforeunloadFn方法代表关闭页面this.countKanbanData(waitTime)}},unloadFn(e){//统计曝光时间let waitTime = new Date().getTime() - this.createThisTime;//计算unload事件和beforeunload事件执行间隔,,用于判断是关闭还是刷新this._gap_time = new Date().getTime() - this._beforeUnload_time;//第二个重点!!!为何刷新页面的时候也要发送请求?if(this._gap_time < 5){    //关闭页面this.countKanbanData(waitTime)} else {                   //刷新页面this.countKanbanData(waitTime)}},destroyed() {//remove监听事件window.removeEventListener("beforeunload", e => {this.beforeunloadFn(e)});window.removeEventListener("unload", e => {this.unloadFn(e)});this._beforeUnload_time = 0this._gap_time = 0}
}

//!!!再夹紧点,努力努力快到了!!!
大家执行到这一步,基本流程没啥问题了,然后测试的时候会感觉,请求好像发出去了但感觉好像又没法发出去。。时灵时不灵的,主要原因是什么呢?
真相!!!:vue发送请求是异步的,这个请求的的却却是在发送,但是页面关闭的时候卸载的太快了,这个请求可能还没发送出去页面就卸载了,导致发射失败。好嘛,原因找到了,原来vue是个秒男。

那怎么解决呢?
1.写个foreach,,拖延vue卸载的事件,给异步的http发射时间,俗话就是吃点药

2.通过div的img标签,,原理也是延迟vue的卸载事件。

这两种方法会导致客人的体验感贼差,,像我这种粗又硬肯定是不屑使用的。

一番查找之后我有了个更好的办法。因为vue发送是异步的而且页面卸载的事件确实很快,关闭事件一般都是 0-5毫秒之间(this._gap_time<5)。。名副其实的秒男~。所以我们不一定非要vue去发送请求,,可以让浏览器去异步的发送请求。

//大家准备好,我要冲刺了//
3.navigator.sendBeacon 超链接是官方文档
这个方法的作用就是,,将vue发送的任务,,交给浏览器的任务队列去执行,方法也是异步的,vue的页面卸载是不会影响到浏览器任务的执行的。而且该方法也不会对vue的卸载有影响,用户体验感贼好,说不定还会给点小费。
但是~,这个方法会有些小的限制
3.1这个方法必须是post,我们可以尝试利用自带的blog类型去传毒参数,后端可以直接通过@RequestBody标签接收参数,反正我们前后端交互最常用的格式就是json
3.2.这个方法的headers只能设置content_type,也就是说无法把鉴权用的token放进headers里面,无法放进headers里面就直接放到参数中传呗,,反正我抓包也可以看到headers里面的内容,加密主要还是要靠aes,想防篡改的话那就加上时间戳再MD5。这些就不讨论了

直接上代码

    countKanbanData(waitTime){//判断浏览器是否支持navigator.sendBeacon,,大部分的浏览器都支持if (!navigator.sendBeacon) return;//参数let body = {waitTime: waitTime,tokenStr: getToken()  //headrs中无法记录,,当参数传入再解析};let headers = {type: 'application/json; charset=UTF-8'};let blob = new Blob([JSON.stringify(body)], headers);//线上或者测试因为用的nginx做的代理 所以这么写console.log("sendBeacon  ", navigator.sendBeacon((this.currentEnv + 'openapi/countKanbanData'), blob));//本地dev 通过node// console.log("sendBeacon  ", navigator.sendBeacon('openapi/countKanbanData', blob));}

这三种方法我写的比较简单,,我找了个更加详细的文章大家可以细看
方法链接

开始填坑了~~~
上文我提了两个重点问题,,这两个如果不特殊处理的话会有一定的bug

4.第一个重点问题:上文提到了,我本地开发的环境和线上的环境是不一样的。。
本地通过node做代理如果是api的请求那么拦截器会拦截请求并且填充ip+port,那么此时的api调用的方式为

navigator.sendBeacon('openapi/countKanbanData', blob)

此时不需要写全路径
如果是线上的话,那么是由nginx去做代理,页面和api的请求由ngxin去拦截管理
此时需要写全路径,不然这个请求的url是不完整的

navigator.sendBeacon('http://110.110.110.110:1000/openapi/countKanbanData', blob)

5.第二个重点问题:为毛不论是 刷新还是关闭都需要发送一次请求并记录
不论是刷新和关闭都记录
5.1 if close 此时waitTime是准确的 需要记录
5.2 if flush 因为用户(点击浏览器的刷新按钮) 或者 (ctrl+R) 或者 (通过网址再次键入) 此时都会将mounted中的createThisTime 刷新
如果不记录的话 会导致有部分停留时间被刷掉 而每次都记录的话 只会将此次的waitTime分割成多条

上后端代码

//自定义requesti接收参数
@Data
public class KanbanReuqest {private String waitTime;private String tokenStr;
}
//控制层
@RestController
@RequestMapping("/openapi")
public class OpenApiController {@Autowiredprivate LogService logService;@PostMapping(value = "/countKanbanData")public ResponseEntity countKanbanData(@RequestBody(required = false) KanbanReuqest req, HttpServletRequest request) {if(StringUtils.isBlank(req.getTokenStr())){return new ResponseEntity<>(HttpStatus.NO_CONTENT);}//可以通过线程池或者@Async标签  实现异步插入数据的操作,尽量早点HttpStatus.NO_CONTENT状态码logService.save(jwtTokenUtil.getUsernameFromToken(req.getTokenStr()), req.getWaitTime(), StringUtils.getIp(request), StringUtils.getBrowser(request));return new ResponseEntity<>(HttpStatus.NO_CONTENT);}
}

后端唯一需要注意的一点就是日志插入数据异步,尽量早点HttpStatus.NO_CONTENT状态码

OK,就酱

VUE监听页面刷新和关闭事件相关推荐

  1. vue监听浏览器刷新和关闭事件,并在页面关闭/刷新前发送请求

    vue监听浏览器刷新和关闭事件,并在页面关闭/刷新前发送请求 1.需求背景: 2.需求分析: 3.实现方式: 4.实现方式解析: 1)浏览器页面事件基础 2)在mounted监听beforeunloa ...

  2. vue 监听页面刷新或关闭

    参考:https://blog.csdn.net/weixin_43915587/article/details/93628935 发现 beforeDestroy 只能监听到页面间的跳转,无法监听到 ...

  3. Vue中监听页面刷新和关闭beforeunload事件

    代码 在methods中定义事件方法,在mounted 生命周期钩子中绑定事件, 在destoryed钩子中卸载事件 mounted () {window.addEventListener('befo ...

  4. vue项目中监听页面刷新和关闭

    在实际开发项目中,有时候我们需要在刷新和关闭时,触发一些功能,那么如何监听到页面的刷新和关闭呢? 1. 在methods中定义事件方法: methods: {beforeunloadFn(e) {co ...

  5. vue监听浏览器刷新和关闭;

    注意:区分不了浏览器是触发了刷新还是关闭,而且提示的弹框是无法自定义的:如果有大佬有方法能区分,还请评论学习一下!感谢! 代码可直接复制: <template><div>< ...

  6. js 监听浏览器刷新还是关闭事件

    // $(window).bind('beforeunload',function(){return '您输入的内容尚未保存,确定离开此页面吗?';}); // window.onbeforeunlo ...

  7. vue 监听页面滚动事件:触发animate.min.css动画特效

    一.问题答疑: 1. animate.css 如何在vue项目中引入?或引用? 2. 如何监听滚动事件,触发animate.class动画播放? vue 监听滚轮滚动事件,for循环 ,动态id,代码 ...

  8. vue监听页面滚动事件

    方法:监听滚动实现 通过addEventListener方式监听 通过scroll获取到滚动 export default {data () {return {topNavBg: {backgroun ...

  9. vue监听移动设备的返回事件

    在公共方法文件common.js中实现一个存储当前历史记录的方法 common.js // 存储当前历史记录点,实现控制手机物理返回键的按钮事件 var pushHistory = function ...

最新文章

  1. seaborn系列 (18) | 线性回归图regplot()
  2. html 字号自适应,自适应网页中字体大小自适应屏幕 - YangJunwei
  3. MySQL Windows ZIP 免费安装和启动设置
  4. 天猫11.11:手机淘宝 521 性能优化项目揭秘
  5. How to get information of all attachments belonging to a given appointment
  6. 基于Redis实现分布式锁,避免重复执行定时任务
  7. accsess转成mysql语句_access数据库转mysql经验分享
  8. 运算符 python
  9. AD19原理图背景栅格去掉(改为纯色)
  10. java jvm内存模型_Java(JVM)内存模型– Java中的内存管理
  11. 每日英语:Targeting Grandpa: China’s Seniors Hunger for Ads
  12. [境内法规]中国人民银行关于分支行反洗钱工作的指导意见—银发[2005]56号
  13. 罗克韦尔AB PLC与西门子Basic精简触摸屏进行通信的具体方法演示
  14. tree.js实现3D效果,官网demo
  15. 系统学习机器学习之维度归约(完整篇)
  16. 微博评论数据——requests——保存在本地
  17. 联想Z5:0%电量还能通话半小时,网友质疑是造假?
  18. MySql存储过程与函数
  19. MapGuide源码分析----MapGuide Web扩展源码分析
  20. mysql 当前时间小时制_日期函数——MYSQL

热门文章

  1. 程序员小说 Out Of Memory (一)
  2. 股票类网站php,php 股票信息查询类
  3. 《论语》原文及其全文翻译 学而篇3
  4. STM32、NBIOT、Lora模块烧写方法-Hex文件烧录步骤详解-新大陆物联网设备-NEWLab开发板
  5. 2017衢州联赛第四题题解
  6. textarea 中的换行、空格; 如何处理
  7. js中的change事件
  8. 压缩感知与临近点算子
  9. 算法题Nuts and Bolts(螺母螺钉)快速排序详细讲解(含流程图)
  10. C语言_因数、因子_质数(素数)、合数