1. 前言
前端从事了超过两年, 修复了无数的 bug, 写了无数的 bug; 挖了很多次坑, 填了很多次坑; 犯了很多次错, 弥补了很多次, 学习了很多次一般而言, 对于 bug 坑, 都是修复完了或者填完了, 并且记住为什么会产生 bug, 为什么有坑, 为什么犯错, 怎么解决的, 下次怎么避免, 就行了, 就学习到了而这一次的项目, 原本以为开发挺顺利的, 但是开发完了, 才发现自己犯了一个低级而严重的错, 这样的一个失误, 我一直耿耿于怀
2. 起因
在 3 月 9 号的这一天, 公司有个活动, 希望用答题活动推广自己的小程序结果因为开发时间太紧, 小程序在 3 月 5 号才提审在 3 月 8 号早上, 小程序还没有审核, 在不得已的情况下, 只能把答题活动以网页的形式进行, 使用 vue 开发由于在 3 月 9 号要用到这个答题活动, 所以 3 月 8 号必须要完成开发, 测试, 验收
开发的过程, 都挺顺利, 只是把小程序的一些代码, 改成 vue 开发移动端网站的方式, 把标签换了, 样式稍微重写一下, 项目就跑起来了, 至于一些交互逻辑, 由于不能使用小程序的 API, 只能另找良方代替, 但问题基本不大
麻烦的一个需求就是: 当用户没答完题中途退出的时候, 要记录用户的答题状态比如答了哪些题目, 哪些题目错了, 哪些题目正确了, 拿了多少分等数据在小程序里面, 很轻松可以利用生命周期函数 unload() 进行监听当用户没答完题退出页面的时候, 把用户当前的答题数据, 传给后台, 让后台进行保存在用户下次进入页面的时候, 我可以根据后台返回的用户答题状态, 进行信息的展示如果用户没答过题目, 就重新开始答题, 如果用户上次退出的时候, 没答完题目, 就按照退出时的进度, 让用户重新答题, 如果答完了题目, 直接显示答题结果页面
这个需求不难实现, 小程序有 onload()和 unload()两个生命周期函数, 只是在这两个函数里面, 调两次接口而已
但在网页里面, 监听用户进入页面简单但是监听用户退出页面 (微信浏览器上面的那个返回或者关闭按钮) 却死活不行网上最多的解决方案是这个, 但是不知道是我使用方式有问题还是人品问题, 压根没用, 无论是微信开发者工具, 还是安卓或者苹果真机
答案来自知乎: 微信自带浏览器环境内左上角返回关闭按钮事件监控?
- pushHistory();
- window.addEventListener("popstate", function(e) {
- alert("我监听到了浏览器的返回按钮事件啦");// 根据自己的需求实现自己的功能
- }, false);
- function pushHistory() {
- var state = {
- title: "title",
- url: "#"
- };
- window.history.pushState(state, "title", "#");
- }
根据网上的方案, 试了几个(包括 vue 的生命周期函数), 没一个可行的最后无奈之下, 只能用一个蠢方法, 用户点击每一题选项的时候, 就把用户当前的记录, 通过接口发给后台, 让后台记录这个就是我该文章说的低级严重的失误, 想必大家也知道是怎么回事了
3. 失误分析
这次的答题活动, 一共有三轮, 每轮 10 道题, 现场大概有 500 人答题本来使用小程序开发, 不管用户是没答题就让用户可以开始答题, 答题途中退出就记录状态, 答完题就显示结果在这个过程中, 我跟后台交互的只有两次: 一次是用户进来的时候获取用户答题进度, 一次是用户答完了最后一题, 发送用户成绩, 让后台记录; 或者中途退出, 发送用户答题进度给后台, 让后台记录
但是后来我在网页中, 由于暂时没法监听用户是否退出, 所以选择了用户回答完每一题的时候, 把数据发给后台, 让后台答题进度这样请求数就多了 N 倍服务器的压力就大了很多
由于用户进来, 无论是小程序还是网站, 都要请求接口, 获取用户答题数据, 这次不在对比范围这样原本小程序只需要和后台进行一次握手, 但是在网页中, 采用了不合适的方式, 和后台握手次数变成了 10 次足足多了 90% 如果是 500 人, 每一轮从原本的 500 次, 变成了 5000 次, 三轮就从原本的 1500 次, 变成了 15000 次! 一般而言, 10 道题选择题, 是两分钟左右的回答时间, 就相当于在 2 分钟内服务器要响应的次数多了 90%, 这个担子突然重了很多而已这些请求, 基本都有没什么意义的, 因为绝大部分的人, 10 道题, 大概两分钟的答题时间里面, 不会中途退出, 相当于我做了一件没意义, 又消耗服务器性能的事情
让我耿耿于怀的原因, 我一向对请求数严格的控制, 虽然现在公司不怎么考虑性能, 服务器压力但是这会引起我的强迫症
4. 解压方案
由于答题活动, 9 号要使用, 而我是 8 号晚上洗完澡的时候和同事聊天的时候才想起, 所以我没时间改了, 因为改了也是需要时间开发, 测试 9 号由于同事请假, 他的项目也由我负责, 也是比较赶的项目, 我也没那么多时间改只能委屈一下服务器了
说是这样说, 但是关于其他的给服务器减轻负担的方案, 还是有比较讲一下, 算是给自己提个醒, 也算是给大家提个醒开发要注意一点: 不要急, 不要急, 不要急
PS: 当时就是看着时间差不多是下午四点半了, 然后还有两个零散功能没做, 又要测试找了很久的解决方案 (监听微信的返回或者关闭按钮) 都没下落的情况下, 一下急了, 脑袋放空, 就想了那个方法
cookie 或者 localstore
记录用户的状态, 这个应该是最好的解决方案了, 也应该是最简单的解决方案
比如使用 cookie 记录用户的答题进度在用户每答一题的时候, 就把 cookie 记录到的数据, 更新一次这样只需要在用户答完了最后一题的时候再把用户的成绩发给后台就好, 至于用户中途退出也没有, 根据 cookie 判断就好, 如果 cookie 有记录到用户的数据就显示上次用户退出时候的题目, 让用户继续答题
原代码:
- /**
- * @dedependson 点击选项
- * @index 题目索引 number
- * @item 当前选项对象 object
- */
- chooseDo(index,item){
- /* 其他代码略 */
- let _this=this;
- let _data={
- qid:_this.qid,// 答题轮次, 如'2'代表第二轮答题
- questions:_this.questions,// 已答题目,'1,2,3'这个表示 id 为 1,2,3 的题目已经回答了
- totalScore:_this.totalScore// 当前得分
- }
- // 发送请求, 让后台记录用户答题进度
- this.$http.post(http_url.submit,_data,{emulateJSON:true}).then(res=>{
- })
- }
然后再到页面加载的时候
- mounted(){
- this.$http.get(http_url.getQuestions,{
- params:{
- qid:this.qid
- }
- }).then(res=>{
- res=res.body;
- // 如果请求成功
- if(res.code===0){
- // 如果用户没答完题 0 - 没开始答题 1 - 没答完题 2 - 答完题目
- if(res.datas.status!==2){
- // 获取答题的题目
- this.questionList=res.datas.entryList;
- // 如果题目长度小于 10, 就是开始答题了, 但是没答完(中途退出的原因)
- if(this.questionList.length<10){
- // 显示答题页面, 让用户答题
- this.questionListShow=true;
- }
- // 否则就是没答过题目, 让用户答题
- else{
- // 显示开始答题页面(答题首页, 用户需要点击开始答题)
- }
- }
- // 如果用户已经答完题, 显示结果页
- else{
- // 代码略
- }
- }
- else{
- alert(res.msg)
- }
- })
- }
cookie 方案
- chooseDo(index,item){
- /* 其他代码略 */
- let _this=this;
- let _data={
- qid:_this.qid,// 答题轮次, 如'2'代表第二轮答题
- questions:_this.questions,// 已答题目,'1,2,3'这个表示 id 为 1,2,3 的题目已经回答了
- totalScore:_this.totalScore// 当前得分
- }
- // 保存 cookie 一天
- //_this.qid 作为答题轮次的标识
- setCookie('answer-qid'+_this.qid,_this.qid,1);
- setCookie('answer-questions'+_this.qid,_this.questions,1);
- setCookie('answer-totalScore'+_this.qid,_this.totalScore,1);
- }
cookie 函数参考: ec-do
- // 设置 cookie
- setCookie(name, value, iDay) {
- let oDate = new Date();
- oDate.setDate(oDate.getDate() + iDay);
- document.cookie = name + '=' + value + ';expires=' + oDate;
- },
- // 获取 cookie
- getCookie(name) {
- let arr = document.cookie.split(';'),
- arr2;
- for (let i = 0; i < arr.length; i++) {
- arr2 = arr[i].split('=');
- if (arr2[0] == name) {
- return arr2[1];
- }
- }
- return '';
- },
- // 删除 cookie
- removeCookie(name) {
- this.setCookie(name, 1, -1);
- },
然后再到页面加载的时候, 处理方式的改变
- mounted(){
- this.$http.get(http_url.getQuestions,{
- params:{
- qid:this.qid
- }
- }).then(res=>{
- res=res.body;
- // 如果请求成功
- if(res.code===0){
- // 如果用户没答完题 0 - 没开始答题 1 - 没答完题 2 - 答完题目
- if(res.datas.status!==2){
- // 记录答题轮次
- this.qid=res.datas.qid;
- // 获取答题的题目
- this.questionList=res.datas.entryList;
- // 如果用户中途退出, 我们没有和后台对接口, 后台无法记录用户答题进度, 所以这次请求, 返回的结果要么是没开始答题, 要么是答完题了
- // 要还原用户答题记录, 要使用 cookie
- // 如果存在 cookie 记录, 那么用户肯定是至少答过一题, 还原用户答题进度
- let _answerQid=getCookie('answer-qid'+this.qid)
- _answerQuestions=getCookie('answer-qid'+this.qid).split(',');
- // 字符串转整数
- _answerQuestions.map(item=>+item);
- if(_answerQid&&_answerQuestions){
- this.questionList.fifler(item=>{
- //item.id 是题目的 id
- // 如果题目的 id 存在, 就过滤掉
- _answerQuestions.indexOf(item.id)===-1
- });
- // 显示答题页面, 让用户答题
- this.questionListShow=true;
- }
- // 否则就是没答过题目, 让用户答题
- else{
- // 显示开始答题页面(答题首页, 用户需要点击开始答题)
- }
- }
- // 如果用户已经答完题, 显示结果页
- else{
- // 代码略
- }
- }
- else{
- alert(res.msg)
- }
- })
- }
代码上面, 可能用了 cookie 会复杂些, 但是就多了几行而已, 差不了多少, 反倒是减轻了很多请求
在小程序没有使用这个方案, 就是考虑到用户退出小程序, 可能会清除缓存, 虽然这个几率不大, 所以使用生命周期函数进行 unload()进行监听, 用户退出就把用户答题进度提交给后台, 让后台记录, 这样的情况不会很多, 甚至没有, 请求不会很多, 所以当时就用了这个方案没有使用 cookie 或者 localstore
注意几点:
1. 无论什么情况, 开发都需要一个清醒的头脑, 因为头脑不清醒, 写的都是 bug, 那个活动是一个一次性的项目, 如果是长期的, 我肯定会重构的, 因为当时写的代码太烂了也容易犯一些低级的错误
2. 不要为了小概率的事件想得太多, 给自己, 同事, 服务器都带来麻烦, 也影响项目进度这次就是想得太多, 结果提测的时间晚了, 验收的时间晚了, 自己也犯了错误想太多的后果可能就是捡了芝麻, 漏了西瓜, 甚至是偷鸡不成蚀把米
2. 小结
这次的的失误就告一段落了, 我也总结了一下, 自己为什么会对这次失误更更于怀
1. 最近一直在看怎么优化代码, 让代码更有可读性, 可维护性却犯了请求数过多的错顾此失彼啊
2. 第二个就是因为这次失误, 导致的后果太严重了, 直接多了 90% 的请求以往失误导致的后果没怎么严重
3. 以往犯错的时候, 在项目上线之前能够发现, 并且有时候改, 这次不一样, 这次是发现了, 但是没时间改了
4. 那些以为不会有, 不应该犯的错可能就在头脑不清醒的时候, 就会犯这些错误, 无论什么时候都得留个神, 这次也算是我自己提醒自己了
不过结局是还算是好的, 当天因为时间关系, 答题活动没有进行, 所以服务器没有受到考验如果当天服务器承受不住压力, 崩了, 我也可能要引咎辞职了!
好了, 故事就是这样了, 有点日记的感觉, 希望大家谅解下如果文章有什么地方写错了, 也欢迎指点交流
来源: https://juejin.im/post/5aa3f1fa6fb9a028b547809b