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

0 前言


最近的工做重心一直在小程序,也開發了幾個小程序,對小程序開發的流程及相關技術相對比較熟悉,在開發過程當中也總結了一些心得經驗、瞭解一些小程序文檔上沒有的東西、踩了一些坑。因此想着寫篇文章記錄下來,並藉此將小程序開發的相關知識進行梳理,方便之後參考,也做爲本身工做的階段性總結。同時也但願能夠經過文章,結識更多朋友,多交流,互相學習,共同進步。另文章如有不對之處,還望指出與不吝賜教。javascript

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


微信小程序開發,入門算是很是簡單,只要看官文文檔便可小程序簡易教程。如何申請小程序帳號,如何開發本身第一個小程序,如何發佈,這一系列hello world操做官方文檔都有手把手教學。小程序開發的每一個步驟,提供的能力文檔裏都有,我的以爲,作小程序開發,有事沒事都看下文檔,由於小程序更新比較快速,同時一些細小的能力咱們可能會漏掉,因此多看文檔。html

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


文件目錄結構很靈活

先來看看小程序項目的文件目錄結構前端

文件目錄結構

除了app.json必須位於根目錄下,其餘文件隨意,而且均可以刪。而且頁面文件能夠放到如何位置,只要在app.json中的pages中配置了就能夠。能夠說是很靈活。你還能夠多個頁面放在同個文件夾下(我相信你不會這樣作的,何須糟蹋本身呢)。 java

接下來簡單介紹下各個文件:android

全局配置文件app.json

對於一個小程序項目而言,最重要的文件是app.json,它也是開發工具識別一個文件夾是否爲小程序項目的標識。當使用開發者工具建立一個項目是,若是選擇的是空文件夾,它會建立一個新的項目。若是是一個有文件的文件夾,它會看該文件夾中是否有app.jon文件,若是有,則它會認爲是一個小程序項目,則會打開該項目,若是文件夾中沒有app.json文件,則提示沒法建立項目。ios

app.json必須放置於項目的根目錄下,它是小程序項目的全局配置文件。在小程序代碼包準備完成進行啓動後(下文會詳細介紹小程序從用戶點擊打開小程序到小程序銷燬的整個過程),會先讀取app.json文件,進行小程序的初試化,好比初始化整個小程序外框樣式,獲取首頁頁面地址等。web

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

下面介紹下小程序的全局配置選項:canvas

注意:小程序

  • 全部配置項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 ) || ""
							}
							//請求成功,
							//收集規則爲:
							// 一、 statusCode非2xx,3xx
							// 二、 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 總結


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

相關文章
相關標籤/搜索