前端從事了超過兩年,修復了無數的bug,寫了無數的bug;挖了不少次坑,填了不少次坑;犯了不少次錯,彌補了不少次,學習了不少次。通常而言,對於bug、坑,都是修復完了或者填完了,而且記住爲何會產生bug,爲何有坑,爲何犯錯,怎麼解決的,下次怎麼避免,就好了,就學習到了。而這一次的項目,本來覺得開發挺順利的,可是開發完了,才發現本身犯了一個低級而嚴重的錯,這樣的一個失誤,我一直耿耿於懷。javascript
在3月9號的這一天,公司有個活動,但願用答題活動推廣本身的小程序。結果由於開發時間太緊,小程序在3月5號才提審。在3月8號早上,小程序尚未審覈,在不得已的狀況下,只能把答題活動以網頁的形式進行,使用vue開發。因爲在3月9號要用到這個答題活動,因此3月8號必需要完成開發,測試,驗收。前端
開發的過程,都挺順利,只是把小程序的一些代碼,改爲vue開發移動端網站的方式,把標籤換了,樣式稍微重寫一下,項目就跑起來了,至於一些交互邏輯,因爲不能使用小程序的API,只能另找良方代替,但問題基本不大。vue
麻煩的一個需求就是:當用戶沒答完題中途退出的時候,要記錄用戶的答題狀態。好比答了哪些題目,哪些題目錯了,哪些題目正確了,拿了多少分等數據。在小程序裏面,很輕鬆能夠利用生命週期函數 unload()
進行監聽。當用戶沒答完題退出頁面的時候,把用戶當前的答題數據,傳給後臺,讓後臺進行保存。在用戶下次進入頁面的時候,我能夠根據後臺返回的用戶答題狀態,進行信息的展現。若是用戶沒答過題目,就從新開始答題,若是用戶上次退出的時候,沒答完題目,就按照退出時的進度,讓用戶從新答題,若是答完了題目,直接顯示答題結果頁面。java
這個需求不難實現,小程序有 onload()
和 unload()
兩個生命週期函數,只是在這兩個函數裏面,調兩次接口而已。git
但在網頁裏面,監聽用戶進入頁面簡單。可是監聽用戶退出頁面(微信瀏覽器上面的那個‘返回’或者‘關閉’按鈕)卻死活不行。網上最多的解決方案是這個,可是不知道是我使用方式有問題仍是人品問題,壓根沒用,不管是微信開發者工具,仍是安卓或者蘋果然機。github
答案來自知乎:微信自帶瀏覽器環境內左上角返回、關閉按鈕事件監控?小程序
pushHistory();
window.addEventListener("popstate", function(e) {
alert("我監聽到了瀏覽器的返回按鈕事件啦");//根據本身的需求實現本身的功能
}, false);
function pushHistory() {
var state = {
title: "title",
url: "#"
};
window.history.pushState(state, "title", "#");
}
複製代碼
根據網上的方案,試了幾個(包括vue的生命週期函數),沒一個可行的。最後無奈之下,只能用一個蠢方法,用戶點擊每一題選項的時候,就把用戶當前的記錄,經過接口發給後臺,讓後臺記錄。這個就是我該文章說的低級嚴重的失誤,想必你們也知道是怎麼回事了。瀏覽器
此次的答題活動,一共有三輪,每輪10道題,現場大概有500人答題。原本使用小程序開發,無論用戶是沒答題就讓用戶能夠開始答題,答題途中退出就記錄狀態,答完題就顯示結果。在這個過程當中,我跟後臺交互的只有兩次:一次是用戶進來的時候獲取用戶答題進度,一次是用戶答完了最後一題,發送用戶成績,讓後臺記錄;或者中途退出,發送用戶答題進度給後臺,讓後臺記錄。緩存
可是後來我在網頁中,因爲暫時無法監聽用戶是否退出,因此選擇了用戶回答完每一題的時候,把數據發給後臺,讓後臺答題進度。這樣請求數就多了N倍。服務器的壓力就大了不少。服務器
因爲用戶進來,不管是小程序仍是網站,都要請求接口,獲取用戶答題數據,此次不在對比範圍。這樣本來小程序只須要和後臺進行一次握手,可是在網頁中,採用了不合適的方式,和後臺握手次數變成了10次。足足多了9倍。若是是500人,每一輪從本來的500次,變成了5000次,三輪就從本來的1500次,變成了15000次!通常而言,10道題選擇題,是兩分鐘左右的回答時間,就至關於在2分鐘內服務器要響應的次數多了9倍,這個擔子忽然重了不少。而已這些請求,基本都有沒什麼意義的,由於絕大部分的人,10道題,大概兩分鐘的答題時間裏面,不會中途退出,至關於我作了一件沒意義,又消耗服務器性能的事情。
讓我耿耿於懷的緣由,我一貫對請求數嚴格的控制,雖然如今公司不怎麼考慮性能,服務器壓力。可是這會引發個人強迫症。
因爲答題活動,9號要使用,而我是8號晚上洗完澡的時候和同事聊天的時候纔想起,因此我沒時間改了,由於改了也是須要時間開發,測試。9號因爲同事請假,他的項目也由我負責,也是比較趕的項目,我也沒那麼多時間改。只能委屈一下服務器了。
說是這樣說,可是關於其餘的給服務器減輕負擔的方案,仍是有比較講一下,算是給本身提個醒,也算是給你們提個醒。開發要注意一點:不要急,不要急,不要急。
PS:當時就是看着時間差很少是下午四點半了,而後還有兩個零散功能沒作,又要測試。找了好久的解決方案(監聽微信的‘返回’或者‘關閉按鈕’)都沒下落的狀況下,一下急了,腦殼放空,就想了那個方法。
記錄用戶的狀態,這個應該是最好的解決方案了,也應該是最簡單的解決方案。
好比使用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)
}
})
}
複製代碼
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.不要爲了小几率的事件想得太多,給本身,同事,服務器都帶來麻煩,也影響項目進度。此次就是想得太多,結果提測的時間晚了,驗收的時間晚了,本身也犯了錯誤。想太多的後果可能就是撿了芝麻,漏了西瓜,甚至是偷雞不成蝕把米。
此次的的失誤就告一段落了,我也總結了一下,本身爲何會對此次失誤更更於懷。
1.最近一直在看怎麼優化代碼,讓代碼更有可讀性,可維護性。卻犯了請求數過多的錯。顧此失彼啊。
2.第二個就是由於此次失誤,致使的後果太嚴重了,直接多了90%的請求。以往失誤致使的後果沒怎麼嚴重。
3.以往犯錯的時候,在項目上線以前可以發現,而且有時候改,此次不同,此次是發現了,可是沒時間改了。
4.那些覺得不會有,不該該犯的錯。可能就在頭腦不清醒的時候,就會犯這些錯誤,不管何時都得留個神,此次也算是我本身提醒本身了。
不過結局是還算是好的,當天由於時間關係,答題活動沒有進行,因此服務器沒有受到考驗。若是當天服務器承受不住壓力,崩了,我也可能要引咎辭職了!
好了,故事就是這樣了,有點日記的感受,但願你們諒解下。若是文章有什麼地方寫錯了,也歡迎指點交流。
--------------------華麗的分割線-------------------
想了解更多,關注關注個人微信公衆號:守候書閣