1. 程式人生 > >微信小程式開發總結與心得

微信小程式開發總結與心得

0 前言


最近的工作重心一直在小程式,也開發了幾個小程式,對小程式開發的流程及相關技術相對比較熟悉,在開發過程中也總結了一些心得經驗、瞭解一些小程式文件上沒有的東西、踩了一些坑。所以想著寫篇文章記錄下來,並藉此將小程式開發的相關知識進行梳理,方便以後參考,也作為自己工作的階段性總結。同時也希望可以通過文章,結識更多朋友,多交流,互相學習,共同進步。另文章若有不對之處,還望指出與不吝賜教。

1 微信小程式基本知識與概念


微信小程式開發,入門算是非常簡單,只要看官文文件即可小程式簡易教程。如何申請小程式賬號,如何開發自己第一個小程式,如何釋出,這一系列hello world操作官方文件都有手把手教學。小程式開發的每個步驟,提供的能力文件裡都有,個人覺得,做小程式開發,有事沒事都看下文件,因為小程式更新比較快速,同時一些細小的能力我們可能會漏掉,所以多看文件。

1.1 簡單說下目錄結構和app.json


檔案目錄結構很靈活

先來看看小程式專案的檔案目錄結構

檔案目錄結構

除了app.json必須位於根目錄下,其他檔案隨意,並且都可以刪。並且頁面檔案可以放到如何位置,只要在app.json中的pages中配置了就可以。可以說是很靈活。你還可以多個頁面放在同個資料夾下(我相信你不會這樣做的,何必糟蹋自己呢)。

接下來簡單介紹下各個檔案:

全域性配置檔案app.json

對於一個小程式專案而言,最重要的檔案是app.json,它也是開發工具識別一個資料夾是否為小程式專案的標識。當使用開發者工具建立一個專案是,如果選擇的是空資料夾,它會建立一個新的專案。如果是一個有檔案的資料夾,它會看該資料夾中是否有app.jon檔案,如果有,則它會認為是一個小程式專案,則會開啟該專案,如果資料夾中沒有app.json檔案,則提示無法建立專案。

app.json必須放置於專案的根目錄下,它是小程式專案的全域性配置檔案。在小程式程式碼包準備完成進行啟動後(下文會詳細介紹小程式從使用者點選開啟小程式到小程式銷燬的整個過程),會先讀取app.json檔案,進行小程式的初試化,比如初始化整個小程式外框樣式,獲取首頁頁面地址等。

其實小程式就是微信提供的一個容器,各個頁面就在這個容器里加載執行銷燬

下面介紹下小程式的全域性配置選項:

注意:

  • 所有配置項key必須使用雙引號括起來,value值為字串型別的也必須使用雙引號,不支援單引號
  • 因為小程式功能迭代非常迅速,基礎庫版本更新也很快,所以下面的介紹是截止目前的最新版本庫2.4.0
  • pages

    "pages": [
        "pages/index/index",
        "pages/log/log"
    ]
複製程式碼

在app.json中,pages選項是必須配置的。該配置項註冊了小程式所有頁面的地址,其中每一項都是頁面的 路徑+檔名 。配置的字串其實就是每個頁面wxml路徑,去掉.wxml字尾。因為框架會自動去尋找路徑下.json、.js、.wxml、.wxss四個檔案進行整合。也就意味著.json、.js、.wxss這三個檔案的檔名必須要和.wxml的一致,否則不生效。所以一個頁面至少必須得有.wxml檔案。

總結:

  • 頁面的.json、.js、.wxss檔案必須與.wxml檔案同名,否則不生效
  • 每個頁面都必須pages下注冊,沒有註冊的頁面,如果不訪問,編譯能通過,一旦試圖訪問該頁面則會報錯
  • 可以通過在pages下新增一個選項快速新建一個頁面,開發工具會自動生成對應的檔案
  • window

  "window":{
    "enablePullDownRefresh": ture,
    "navigationStyle": "custom"
  }
複製程式碼

該配置項用於配置小程式的全域性外觀樣式,具體請查閱文件。這裡重點提一下兩個比較實用的

//去掉預設的導航欄,輕鬆實現全面屏
"navigationStyle": "custom" , 
//開啟自帶的下拉重新整理,減少自己寫樣式
"enablePullDownRefresh": ture, 
複製程式碼
  • tabBar

該選項可以讓我們輕鬆實現導航欄tab效果,不過有個不足就是跳轉可操作性非常低。就是每個tab只能跳當前小程式頁面,不同跳到其他小程式。如果需要跳到其他小程式,還需自己封裝個元件。

  • networkTimeout

這是網路請求超時時間,可以設定不同型別請求的超時時間,比如wx.request、wx.uploadFile等。其實很多時候我們都會忽略這個選項,小程式預設是60s超時,但我們應該手動設定更低的值,因為我們的介面一般都會在10s內完成請求(如果超過10s,那你是時候優化了),所以如果網路或者伺服器出問題了,那麼會讓使用者等60s,最後還是失敗,這對使用者很不友好,還不如提前告訴使用者,現在出問題了,請稍後再試。

前段時間由於公司伺服器網關出現了點小問題,導致有些請求連線不上,出現大量連線超時。通過之前新增的錯誤資訊收集外掛(這個是效能優化,下文有講到)看到了很多介面返回time-out 60s。讓使用者等了60s還是失敗,這不友好。所以這個超時時間一般設定15s-30s比較好。

  • debug

是否開啟debug功能,開啟後檢視更多的除錯資訊,方便定位問題,開發階段可以考慮開啟

  • functionalPages

這個是結合外掛使用的,因為微信小程式外掛有很大限制,外掛裡提供的api很有限,wx.login 和 wx.requestPayment 在外掛中不能使用,如果需要獲取使用者資訊和進行支付,就必須通過外掛提供的功能也實現。當你的小程式下的外掛啟用了外掛功能也時,必須設定該選項為true

小程式外掛必須掛載在一個微信小程式中,一個小程式也只能開通一個外掛。當你小程式開通的外掛啟用了外掛功能也時,必須設定該選項為true

  • plugins

    "plugins": {
        "myPlugin": {
            "version": "1.0.0",
            "provider": "wxidxxxxxxxxxxxxxxxx"
        }
    }
複製程式碼

當小程式使用了外掛就必須在這裡宣告引入。小程式自身開通的小程式不能在本身應用

  • navigateToMiniProgramAppIdList

    "navigateToMiniProgramAppIdList": [
        "wxe5f52902cf4de896"
    ]
複製程式碼

之前小程式之間只要是關聯了通過公眾號就可以相互跳轉,如今微信做出了限制,要這個這裡配置好需要跳轉的小程式,上限為10個,還必須寫死,不支援配置。所以當小程式有跳轉到其他小程式,一定要配好這個,否則無法跳轉。

  • usingComponents

  "usingComponents": {
    "hello-component": "plugin://myPlugin/hello-component"
  }
複製程式碼

使用自定義元件或者外掛提供的元件前,必須先在這裡宣告

1.2 小程式啟動與生命週期


下面來說說小程式從使用者點選開啟到銷燬的整個過程。用圖說話更清晰,特地畫了個流程圖:

小程式啟動會有兩種情況,一種是「冷啟動」,一種是「熱啟動」。 假如使用者已經開啟過某小程式,然後在一定時間內再次開啟該小程式,此時無需重新啟動,只需將後臺態的小程式切換到前臺,這個過程就是熱啟動;冷啟動指的是使用者首次開啟或小程式被微信主動銷燬後再次開啟的情況,此時小程式需要重新載入啟動。

上面的流程圖包含了所有內容,但畢竟文字有限,接下來詳細說下幾個點。

  1. 小程式會先檢測本地是否有程式碼包,然後先使用原生代碼包進行小程式啟動,再非同步去檢測遠端版本。這就是小程式的離線能力,相對於H5,這是優點,能加快小程式啟動速度。
  2. 當本地有小程式程式碼包時,會非同步去請求遠端是否有最新版本。有則下載到本地,但該次的啟動還是會用之前的程式碼。所以當我們釋出了最新的版本,需要使用者兩次冷啟動,才能使用到最新版本。如果想要使用者一次冷啟動就可以使用到最新版本,可以使用小程式提供的版本更新API更新。程式碼如下,只要在app.js的onShow函式加上以下程式碼,每次小程式有更新,都會提示使用者更新小程式。不過這個每次提示更新,一定程度上影響使用者體驗。如果結合後端配置,每次進來讀取配置,就可以實現根據需要是否進行該版本的更新,比如一定需要使用者更新才能使用的,那就使用強制更新。對於一些小版本,就不需要使用這個強制更新。
    if (wx.canIUse('getUpdateManager')) {
        //檢測是否有版本更新
        var updateManager = wx.getUpdateManager()
        updateManager.onCheckForUpdate(function (res) {
            // 請求完新版本資訊的回撥,有更新
            if (res.hasUpdate) {
                wx.showLoading({
                    title: '檢測到新版本',
                })
            }
        })
        updateManager.onUpdateReady(function () {
            wx.hideLoading();
            wx.showModal({
                title: '更新提示',
                content: '新版本已經準備好,是否重啟應用?',
                success: function (res) {
                    if (res.confirm) {
                        //清楚本地快取
                        try {
                            wx.clearStorageSync()
                        } catch (e) {
                            // Do something when catch error
                        }
                        // 新的版本已經下載好,呼叫 applyUpdate 應用新版本並重啟
                        updateManager.applyUpdate()
                    }
                }
            })
        })
        updateManager.onUpdateFailed(function () {
            // 新的版本下載失敗
            console.log('新版本下載失敗');
        })
    }
複製程式碼

1.3 開發工具


對於小程式開發工具,還沒有一款讓開發者滿意的工具,至少我不滿意,哈哈哈!微信提供的微信開發者工具。除了編譯器不行外,其他都還行。但由於開發工具、ios、android三個平臺執行小程式的核心不同。所以有時會出現開發工具上沒問題,真機有問題的情況,特別是樣式,可以通過在開發工具中設定上傳程式碼時樣式自動補全來解決大多數問題。另外微信開發者工具提供了真機除錯功能,該功能對真機除錯非常方便

還有就是可以自定義編譯條件

可以模擬任意場景值、設定頁面引數、模擬更新等。基本滿足了所有的除錯。不過還有一些效果,開發工具和真機可能會不同,所以還是需要在真機上確認。

1.4 測試-稽核-上線的那些事


伺服器域名request合法域名每個月只能修改5次。所以不應該每次請求一個新域名就新增一次。在開發階段,在微信開發者工具上勾上不校驗合法域名,真機上需要開啟除錯模式,就可以先不配置合法域名的情況下請求任何域名甚至ip地址。待開發完成了,再一次性配置所有合法域名,在微信開發者工具上取消不校驗合法域名,真機上關閉除錯模式,然後開始測試。

使用體驗版+線上環境的介面,這就是和線上環境一模一樣的,所以在釋出前,使用體驗版+線上環境過一遍。如果沒問題,釋出以後也就沒問題了。

小程式二維碼只要釋出了線上版本呼叫生成小程式二維碼接口才能成功返回二維碼。而且二維碼識別是線上版本,所以還未釋出的小程式是無法生成二維碼的。

線上版本有個版本回退功能,這裡有個坑,就是版本回退以後,退回的版本需要重新稽核才能釋出

還有設定體驗版時可以設定指定路徑和引數,這樣很方便測試

2 重點介紹幾個元件


接下來說說使用頻率比較多,功能強大,但又有比較多坑的幾個元件

2.1 web-view


web-view的出現,讓小程式和H5網頁之前的跳轉成為了可能。通過把H5頁面放置到web-view中,可以讓H5頁面在小程式內執行。同時在H5頁面中也可以跳轉回小程式頁面。可以說是帶來了很大的便利,但同時由於web-view的諸多限制,用起來也不是很舒服。

  1. 需要開啟的H5頁面必須在後臺業務頁面中配置,這其中還有個服務校驗。另外H5頁面必須是https協議,否則無法開啟
  2. web-view中無法在頁面中調起分享,如果需要分享,比如跳回小程式原生頁面
  3. 小程式與web-view裡H5通訊問題。小程式向web-view傳遞,不敏感資訊可以通過頁面url傳遞。如果是敏感資訊比如使用者token等,可以讓服務端重定向,比如請求服務端一個地址,讓他把敏感資訊寫在cookie中,再重定向到我們的H5頁面。之後H5頁面就可以通過在cookie中拿這些敏感資料了,或者http-only,傳送請求時直接帶上。
  4. 每次web-view中src值有變化就會重新載入一次頁面。所以個src拼接引數時,需要先賦值給個變數拼接好再一次性setData給web-view的src,防止頁面重複重新整理
  5. 從微信客戶端6.7.2版本開始,navigationStyle: custom對元件無效。也就意味著使用web-view時,自帶的導航欄無法去掉。
  6. 因為導航欄無法去掉,這裡就出現了一個巨大的坑。實現全屏效果問題。如果想要實現H5頁面全屏,就是不滑動,全屏顯示完所有內容。這時如果你使用width:100%;height:100%,你會發現,你頁面底部可能會缺失一段。上圖:

因為web-view是預設鋪滿全屏的,也就是web-view寬高和螢幕寬高一樣。然後H5頁面這是高度100%,這是相對web-view的高度,也是螢幕高度。但是關鍵問題:web-view裡H5頁面是從導航欄下開始渲染的。這就導致了H5頁面溢位了螢幕,無法達到全屏效果。

解決方法

這個問題我在前段時間的實際專案碰到過,我們要做個H5遊戲,要求是全屏,剛開始我也是設定高度100%。後來發現底部一塊不見了。我的解決方法比較粗暴,如果有更好的解決方法,歡迎評論交流。 我的解決方法是:通過拼接寬高參數在H5頁面url上,這個寬高是在web-view外層計算好的。H5頁面直接讀取url上的寬高,動態設定頁面的寬高。頁面高度的計算,根據上圖,很顯然就是螢幕高度減去導航欄高度。寬度都是一樣的,直接是螢幕寬度。

但問題又來了,貌似沒有途徑獲取導航欄高度。而且對於不同機型的手機,導航欄高度不同。經過了對多個機型導航欄跟螢幕高度的比較。發現了一個規律,導航欄高度與螢幕高度、螢幕寬高比有一定的關係。所以根據多個機型就計算出了這個比例。這解決了95%以上手機的適配問題,只有少數機型適配不是很好。到基本實現了全屏效果。具體程式碼如下:

onLoad (options) {
    //同步獲取螢幕資訊,現在用到的是螢幕寬高
    var res = wx.getSystemInfoSync();
	if (res) {
		var widHeight = res.screenHeight;
		//對於大多數手機,螢幕高度/螢幕寬度 = 1.78。此時導航欄佔螢幕高度比為0.875
		var raito = 0.875;
		if (res.screenHeight / res.screenWidth > 1.95) {
		    //對於全屏手機,這個佔比會更高些
			raito = 0.885;
		} else if (res.screenHeight / res.screenWidth > 1.885) {
			raito = 0.88;
		}
		//做相容處理,只有微信版本庫高於6.7.2,有導航欄才去相容,否則可以直接使用高度100%。res.statusBarHeight是手機頂部狀態列高度
		//如果微信版本號大於6.7.2,有導航欄
		if (util.compareVersion(res.version, "6.7.2") > 0) {
			widHeight = Math.round(widHeight * raito) + (res.statusBarHeight || 0);
		}
		this.setDate({
		    //將H5頁面寬高拼接在url上,賦值給web-view的src即可加載出H5頁面
		    webview_src: util.joinParams(h5_src, {
		        "height": widHeight, 
		        "width": res.screenWidth
		    })
		})
	}
}
複製程式碼

2.2 scroll-view


當我們要實現一個區域內滑動效果時,在H5頁面中我們設定overflow-y: scroll即可。但在小程式中,沒有該屬性。需要用到scroll-view標籤。具體操作實現我們可以檢視檔案scroll-view

錨點定位在前端開發中會經常用到,在H5頁面中,我們會在url後面加上#來實現錨點定位效果。但是在小程式中這樣是不起作用的,因為小程式內渲染頁面的容易不是一個瀏覽器,無法實時監聽Hash值得變化。但是使用scroll-view,我們可以實現錨點點位效果。主要是使用scroll-into-vie屬性具體實現我們直接上程式碼

scroll-into-view | String | 值應為某子元素id(id不能以數字開頭)。設定哪個方向可滾動,則在哪個方向滾動到該元素

wxml檔案

    <!--toView的值動態變化,當toView為luckydraw時,會定位到id為luckydraw的view
    需要注意的是,這裡需要設定高度為螢幕高度-->
    <scroll-view scroll-y scroll-into-view="{{toView}}" 
    scroll-with-animation = "true" style="height: 100%; white-space:nowrap">
        <view id="top"></view>
        <view id="luckydraw"></view>
        <view id="secskill"></view>
    <scroll-view>
複製程式碼

2.3 canvas


畫布標籤,它是原生元件,所以它必須位於螢幕最上邊,而且是不能隱藏的。所以如果想要使用canvas動態生成分享照片。那你要設定她的寬高和螢幕一樣。要不匯出為照片時就會失真。因為這個原因,所以生成分享照片還是有服務端實現吧,照片失真太嚴重了。

3 formid收集


給使用者傳送訊息對一個小程式是非常重要的,它可以召喚回使用者,導量效果非常明顯。我們可以通過模板訊息想小程式使用者傳送訊息,但前提是我們得獲取到openid和formid。使用者登入我們即可即可獲取到使用者openid。而只要使用者有點選行為,我們即可獲取到formid獲取formid。所以說formid是很重要的。我們可以提前收集好formid,在需要的時候給使用者推送訊息。我們可以個每個button都包上form標籤,只要有使用者點選行為都可以收集到formid.

    <form bindsubmit="formSubmit" report-submit='true'>
        <button  formType="submit">點選</button>
    </form>
複製程式碼

我們實現一個formid收集系統,為了儘量減少冗餘程式碼和減少對業務的影響,我們的設計是這樣的

  1. 在整個頁面的最外層包裹form標籤,不是每個button都包裹一個,這樣只要是頁面中formTpye=submit的button有點選都能獲取到formid。
  2. formid儲存在全域性變數陣列中,當小程式切換到後臺是一次性發送。
  3. 對於需要實時傳送訊息的,不新增值全域性陣列中,直接儲存在頁面變數中。

wxml檔案

    <!--在整個頁面的最外層包裹form標籤,這樣就不同對每個button都包裹一個form標籤,程式碼簡潔-->
    <form bindsubmit="formSubmit" report-submit='true'>
        <view>頁面內容</view>
        <view>頁面內容</view>
        <button  formType="submit">點選</button>
        <view>頁面內容</view>
        <view>
            <button  formType="submit">點選</button>
        </view>
    </form>
複製程式碼

page.js檔案

    //每次使用者有點選,都將formid新增到全域性陣列中
    formSubmit(e) {
        //需要實時傳送的,不新增
        if(e.target.dataset.sendMsg){
            formid =  e.detail.formId;
            return;
        }
        app.appData.formIdArr.push(e.detail.formId);
    }
複製程式碼

app.js

    onHide: function () {
        //小程式切到後臺時上傳formid
        this.submitFormId();
    },
複製程式碼

4 效能優化相關


從使用者開啟小程式到小程式銷燬,我們可以想想有哪些地方是可以優化的。首先是開啟速度。小程式開啟速度直接影響了使用者留存。在小程式後臺,運維中心-監控告警下有個載入效能監控資料,我們可以看到小程式啟動總耗時、下載耗時、首次渲染耗等載入相關的資料。而這裡的開啟速度其實就是小程式的啟動總耗時。它包括了程式碼包下載、首次渲染,微信內環境初始化等步湊。在這一步,我們能做的就是如何加快程式碼包下載速度和減少首次渲染時間

在小程式呈現給使用者之後,接下來如何提高使用者體驗,增強小程式健壯性的問題了。每個程式都有bug。只是我們沒發現而已,儘管在測試階段,我們進行了詳盡的測試。但是在實際生產環境,不同的使用者環境,不同的操作路徑,隨時會觸發一些隱藏的bug。這時如果使用者沒有向我們報告,我們是無法獲知的。所以有必要給我們的小程式增加錯誤資訊收集,js指令碼錯誤,意味著整個程式掛掉了,無法響應使用者操作。所以對於執行時的指令碼錯誤,我們應該上報。對出現的bug及時修復,增強程式健壯性,提供使用者體驗。

每個程式都有大量的前後端資料互動,這是通過http請求進行的。因此,還有一個錯誤資訊收集就是介面錯誤資訊收集。對那些請求狀態碼非2XX、3XX的,或者請求介面成功了,但是資料不是我們預期的,都可以進行資訊採集。

通過對小程式執行時指令碼和http請求進行監控,我們就可以實時瞭解我們線上小程式的執行狀況,有什麼問題可以及時發現,及時修復,極高地提高了使用者體驗性。

4.1 讓小程式更快


讓小程式快,主要因素有兩個,程式碼包下載和首屏渲染。 我們來看一個數據:

前面狀態小程式程式碼大小是650Kb左右,這是下載耗時(雖然跟使用者網路有關,但這個是全部使用者平均時間)是1.3s左右。但是經過優化,將程式碼包降低至200kb左右時。下載耗時只有0.6s左右。所以說,程式碼包減少500kb,下載耗時能減少0.5s。這個資料還是非常明顯和。所以說,在不影響業務邏輯的情況下,我們小程式程式碼包應該儘可能地小。那麼如何降低程式碼包大小呢?以下有幾點可以參考

  1. 因為我們上傳程式碼到微信伺服器時,它會將我們的程式碼進行壓縮的,所以使用者下載的程式碼包並不是我們開發時的那個大小。對此,開發時也沒必要刪空行、刪註釋這些。在開發工具專案詳情中可以看到上次上傳大小,這個大小就是使用者最終使用的大小。如果覺得微信壓縮還不夠好,可以通過第三方工具對我們程式碼進行一次壓縮再上傳,然後對比效果,有沒有更小。這個沒有使用過。如果有什麼好工具,歡迎推薦。
  2. 將靜態資原始檔防止到我們自己伺服器或者cdn上。一個小程式,最耗空間的往往是圖片檔案。所以我們可以抽離出來,圖片檔案可以非同步獲取,在小程式啟動以後再去獲取。這樣,程式碼包就會小很多。
  3. 使用分包載入。小程式提供了分包載入功能。如果你的小程式很龐大,可以考慮使用分包載入功能,先載入必要功能程式碼。這樣就是可以極大降低程式碼包大小

接下來是首屏渲染,從上圖的小程式生命週期可以看出,從載入首頁程式碼帶首頁完成渲染,這段時間就是白屏時間,也就是首次渲染時間。而小程式在這段時間內,主要工作是:載入首頁程式碼、建立View和AppService層、初試資料傳輸、頁面渲染。在這四個步驟中,載入首頁程式碼,前面已經說過;建立View和AppService層,是微信完成的,跟使用者手機有關,這不是我們可控的。我們能做的就是減少初試資料傳輸時間和頁面渲染時間。

  1. 我們知道page.js中的data物件在首次渲染時會通過資料管道傳個檢視層進行頁面渲染。所以我們應該控制這個data物件的大小。對於與檢視渲染無關的資料,不要放在data裡面,可以設定個全域性變數來儲存。
    Page({
        //與頁面渲染有關的資料放這裡
        data: {
            goods_list:[]
        },
        //與頁面渲染無關的資料放這裡
        _data: {
            timer: null
        }
    })
複製程式碼
  1. 頁面渲染速度還跟html的dom結構有關。這一點的優化空間算是非常少了,就是寫高質量html程式碼,減少dom巢狀,讓頁面渲染速度快一丟丟。

4.2 讓小程式更強


接下來就是給小程式增加錯誤資訊收集,包括js指令碼錯誤資訊收集和http請求錯誤資訊收集。前段時間,在時間工作開發中,為了更好的複用和管理,我把這個錯誤資訊收集功能做成了外掛。然而做成外掛並沒有想象中的那麼美好,下面再具說。

指令碼錯誤收集

對於指令碼錯誤收集,這個相對比較簡單,因為在app.js中提供了監聽錯誤的onError函式

只不過錯誤資訊是包括堆疊等比較詳細的錯誤資訊,然後當上傳時我們並不需要這麼資訊,第一浪費寬頻,第二看著累又無用。我們需要的資訊是:錯誤型別、錯誤資訊描述、錯誤位置。

thirdScriptError
aa is not defined;at pages/index/index page test function
ReferenceError: aa is not defined
    at e.test (http://127.0.0.1:62641/appservice/pages/index/index.js:17:3)
    at e.<anonymous> (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:31500)
    at e.a (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:26386)
    at J (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:20800)
    at Function.<anonymous> (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:22389)
    at http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:27889
    at http://127.0.0.1:62641/appservice/__dev__/WAService.js:6:16777
    at e.(anonymous function) (http://127.0.0.1:62641/appservice/__dev__/WAService.js:4:3403)
    at e (http://127.0.0.1:62641/appservice/appservice?t=1543326089806:1080:20291)
    at r.registerCallback.t (http://127.0.0.1:62641/appservice/appservice?t=1543326089806:1080:20476)
複製程式碼

這是錯誤資訊字串,接下來我們對它進行擷取只需要拿我們想要的資訊即可。我們發現這個字串是有規則的。第一行是錯誤型別,第二行是錯誤詳情和發生的位置,並且是";"分好分開。所以我們還是很容易就可以拿到我們想要的資訊。

    //格式化錯誤資訊
    function formateErroMsg(errorMsg){
        //包一層try catch 不要讓資訊收集影響了業務
        try{
            var detailMsg = '';
            var detailPosition= '';
            var arr = errorMsg.split('\n')
            if (arr.length > 1) {
                //錯誤詳情和錯誤位置在第二行並用分好隔開
                var detailArr = arr[1].split(';')
                detailMsg = detailArr.length > 0 ? detailArr[0] : '';
                if (detailArr.length > 1) {
                    detailArr.shift()
                    detailPosition = detailArr.join(';') 
                }
            }

            var obj = {
                //錯誤型別就是第一行
                error_type: arr.length > 0 ? arr[0] : '',
                error_msg: detailMsg,
                error_position: detailPosition
            };
            return obj
        }catch(e){}
    }
複製程式碼

獲取到我們想要的資訊,就可以傳送到我們服務後臺,進行資料整理和顯示,這個需要服務端配合,就不深入講了,我們拿到了資料,其他都不是事。

http請求錯誤資訊收集 對於http請求錯誤資訊收集方式,我們儘量不要暴力埋點,每個請求傳送前傳送後加上我們的埋點。這樣工作量太大,也不易維護。因此,我們可以從底層出發,攔截wx.request請求。使用Object.definePropert對wx物件的request進行重新定義。具體實現如下

function rewriteRequest(){
	try {
      	const originRequest = wx.request;
		Object.defineProperty(wx, 'request', {
		  	configurable:true,
		  	enumerable: true,
		  	writable: true,
			value: function(){
				let options = arguments[0] || {};
				//對於傳送錯誤資訊的介面不收集,防止死迴圈
				var regexp = new RegExp("https://xxxx/error","g");
				if (regexp.test(options.url)) {
				    //這裡要執行原來的方法
					return originRequest.call(this, options)
				}
				//這裡攔截請求成功或失敗介面,拿到請求後的資料
				["success", "fail"].forEach((methodName) => {
					let defineMethod = options[methodName];
					options[methodName] = function(){
						try{	      //在重新定義函式中執行原先的函式,不影響正常邏輯
						    defineMethod && defineMethod.apply(this, arguments);
						    //開始資訊收集
							let statusCode, result, msg;
							//請求失敗
							if (methodName == 'fail') {
								statusCode = 0;
								result = 'fail';
								msg = ( arguments[0] && arguments[0].errMsg ) || ""
							}
							//請求成功,
							//收集規則為:
							// 1、 statusCode非2xx,3xx
							// 2、 statusCode是2xx,3xx,但介面返回result不為ok
							if (methodName == 'success') {
								let data = arguments[0] || {};
								statusCode = data.statusCode || "";
								if (data.statusCode && Number(data.statusCode) >= 200 && Number(data.statusCode) < 400 ) {
									let resData = data.data ? (typeof data.data == 'object' ? data.data : JSON.parse(data.data)) : {};
									//請求成功,不收集
									if (resData.result == 'ok') {
										return;
									}
									result = resData.result || "";
									msg = resData.msg || "";
								}else{
									result = "";
									msg = data.data || "";
								}
							}
							//過濾掉header中的敏感資訊
							if (options.header) {	
								options.header.userid && (delete options.header.userid)
							}
							//過濾掉data中的敏感資訊
							if (options.data) {	
								options.data.userid && (delete options.data.userid)
							}
							
					        var collectInfo = {
								"url": options.url || '',	//請求地址
								"method": options.method || "GET",	//請求方法
								"request_header": JSON.stringify(options.header || {}), //請求頭部資訊
								"request_data": JSON.stringify(options.data || {}), //請求引數
								"resp_code": statusCode + '',	//請求狀態碼
								"resp_result": result, //請求返回結果
								"resp_msg": msg, //請求返回描述資訊
					        }
					        //提交引數與上一次不同,或者引數相同,隔了1s
					        if (JSON.stringify(collectInfo) != lastParams.paramStr || (new Date().getTime() - lastParams.timestamp > 1000)) {
					        	//上傳錯誤資訊
					        	Post.post_error(_miniapp, 'http', collectInfo)
					        	lastParams.paramStr = JSON.stringify(collectInfo);
					        	lastParams.timestamp = new Date().getTime()
					        }

						}catch(e){
							//console.log(e);
						}
					};	
				})
			  	return originRequest.call(this, options)
			}
		})
	} catch (e) {
		// Do something when catch error
	}
}
複製程式碼

在不使用外掛的小程式中,我們可以在使用wx.request方法執行上面的程式碼,對wx.request進行攔截,然後其他無需加任何程式碼就可以收集http請求了。 上面說了,當我們封裝成到外掛時,這個就不管用了,因為當使用外掛時,小程式不允許我們修改全域性變數。所以執行上面程式碼時會報錯。這時,我們退而求其次,只能是在外掛中自己封裝個方法,這個方法其實就是wx.request傳送請求,但是在外掛中我們就有可以攔截wx.request了。具體實現如下:

    function my_request(){
        //只要執行一次攔截程式碼即可
        !_isInit && rewriteRequest();
        return  wx.request(options)
    }
複製程式碼

接下來我們看下後臺資料

持續監控,會幫我們找出很多隱藏的bug

4 總結


洋洋灑灑寫了這麼多,或許有些地方說的不太清楚,慢慢鍛鍊吧。然後後面幾點只是挑了重要的講,我相信有過小程式開發經驗的朋友應該沒問題。然後有時間再補充和優化了。先到此,有緣看到的朋友,歡迎留言交流。