前言:
之前用vue做h5項目,對於接口請求,都是根據前端訪問域名來判斷運行環境,然后自動適配對應的服務器地址的。這樣的好處就是在開發、測試及發布上線全程都不需要手動去改接口請求地址,只要提前配置好就行了。這樣處理之后,只需要打包一次,就能同時適應所有環境而不需要再去改代碼,打不同的包了。
對於微信小程序,發現前端並沒有可以區分小程序運行環境(開發者工具、開發版、體驗版及正式版)的API(真的沒有),這就直接導致了開發的時候鏈接測試服務器就需要手動的去修改服務器地址了。最近找到一種解決方法,實現上雖然還是有點曲折,但是總算能解決問題了,也希望騰訊后邊能開發這方面的API。
實現原理:
如圖,小程序網絡請求的請求頭中的Reffer為固定格式:
Referer:https://servicewechat.com/小程序的appid/運行環境/page-frame.html
經驗證,開發者工具中,為devtools,開發版及體驗版為0,正式版則為1,這樣就能區分運行環境了。
但是這個請求頭通過前端並不能獲取,所以只有讓后端在第一個接口請求中獲取referer,然后返回給前端就好了。
實現步驟:
1. 后端將接口訪問請求頭中的reffer返回給前端:
注:只需要在小程序第一個接口(必須訪問)中將reffer返回給前端就好了,如果第一個接口不一定訪問,那么可以讓后端單獨開放一個接口給前端來判斷即可。
2. 前端對接口請求封裝代碼進行改造,如圖:
代碼解析:
貼下代碼,加粗字體為修改部分:
/** * 封裝http請求方法,已實現根據訪問環境自動匹配服務器環境,原理詳見README.md */ var apiUrl = "https://xxx.xxx.cn"; //生產環境 var apiUrlDev = "http://xxx.xxx.cn"; //測試環境 //優先設置為緩存中的服務器地址 var storageApi = wx.getStorageSync("apiUrl") if (storageApi) { apiUrl = storageApi } //封裝http方法給api.js直接使用 const http = (params) => { //返回promise 對象 return new Promise((resolve, reject) => { wx.request({ url: apiUrl + params.url, data: params.data, header: params.header || { "Content-Type": "application/x-www-form-urlencoded", "token": wx.getStorageSync("token") }, method: params.method || 'POST', dataType: params.dataType, responseType: params.responseType, success: function(res) { //1. 根據小程序打開后第一個接口請求判斷小程序訪問環境 if (!wx.getStorageSync("apiUrl") && params.url == "/goods/img" && res.statusCode == 200 && res.data) { //前端根據后端返回的reffer內容進行截取,獲取判斷環境的變量(后端在第一個約定的接口中將請求頭中的reffer返回) const version = res.data.reffer && res.data.reffer.split('/')[4] if (!version || version == 0 || version == "devtools") { //非正式環境(開發者環境,開發版、體驗版),保存測試服務器地址到緩存,並設置為測試服務器,然后重調本接口 wx.setStorageSync("apiUrl", apiUrlDev) apiUrl = apiUrlDev //返回-1狀態碼給調用該接口的方法進行回調 resolve({ retCode: "-1" }) } } else { //2. 非正式環境,所有接口訪問都在控制台輸出訪問接口及響應數據,以便於調試 if (storageApi) console.log("api", params.url, '::', res) //3. 接口響應數據正常處理邏輯,僅在生產環境接口訪問出錯時,控制台輸出接口及響應數據 if (res.statusCode == 200) { if (res.data.retCode != "000000" && !storageApi) console.log("api", params.url, '::', res) resolve(res.data) } else { wx.showToast({ title: "系統繁忙,請稍后再試~", icon: "none" }) if (!storageApi) console.log("api", params.url, '::', res) } } }, fail: function(e) { wx.showToast({ title: "系統繁忙,請稍后再試~", icon: "none" }) reject(e) } }) }) } module.exports = { http: http }
如上,主要做了兩個比較大的改動:
1.在服務器地址設置的邏輯中,默認為生產環境服務器地址,如果緩存中有apiUrl,則使用緩存中的地址:
1 var apiUrl = "https://xxx.xxxx.cn"; //生產環境服務器地址 2 var apiUrlDev = "http://xxx.xxxx.cn";//測試環境服務器地址 3 var storageApi = wx.getStorageSync("apiUrl") 4 //緩存中有服務器地址,則使用緩存中的服務器地址 5 if (storageApi) { 6 apiUrl = storageApi 7 }
2.在響應數據處理的邏輯中,如果緩存中沒有保存服務器地址(apiUrl)且是指定的接口(小程序第一個必須訪問且與后端約定返回請求頭中的reffer給前端使用),則獲取響應數據中的reffer,並截取reffer中的version字段:
1 if (!wx.getStorageSync("apiUrl") && params.url == "/goods/img" && res.statusCode == 200 && res.data) { 2 //緩存中無apiUrl且是第一個必須訪問的接口,則獲取reffer(與后端約定返回這個值) 3 var version = res.data.reffer && res.data.reffer.split('/')[4] 4 ...
3.對reffer中的version進行判斷,如果是0或“devtools”,則將測試服務器地址保存到緩存中,並返回-1給調用該接口的方法進行回調:
1 if (!version || version == 0 || version == "devtools") { 2 //非正式環境(開發者環境,開發版、體驗版),保存測試服務器地址到緩存,並設置為測試服務器,然后重調本接口 3 wx.setStorageSync("apiUrl", apiUrlDev) 4 apiUrl = apiUrlDev 5 //返回-1狀態碼給調用該接口的方法進行回調 6 resolve({ 7 retCode: "-1" 8 }) 9 }
4. 頁面業務邏輯代碼部分也要做相應調整:
1 if (data.retCode == "-1"){ 2 self.loadGood(goods_id) 3 return; 4 }
經過上邊的改造,正式版小程序第一個接口訪問中不符合 version == 0 || version == "devtools" 條件而不再執行條件判斷后續代碼,對當前接口數據處理及后續其他接口訪問都無影響。之所以加了 !version 這個條件,是因為開發階段,新增的這個字段還未同步到正式環境,所以做了這個兼容,即沒有這個字段則直接訪問測試環服務器。
對於測試環境,則在啟動小程序的時候,訪問第一個接口 "/goods/img" ,服務器返回reffer值可以判斷出非正式環境,則將測試服務器地址保存到緩存中,並回調當前接口 http(params); ,這樣就會重新調用當前接口,后續其他接口訪問則直接訪問測試服務器。
至此,代碼改造完成,剩下的就是在不同環境中進行驗證了。
注意事項:
1. 本方法只能算曲線救國,如果是非正式環境,則第一個請求接口會請求兩次,第一次訪問正式服務器,第二次訪問測試服務器,其他就沒多大影響了。可以直接讓后端單獨寫一個接口來判斷小程序運行環境,這樣就不需要改動原有接口了。
2. 無論是采用第一個接口,還是單獨寫接口,都是需要先訪問一次正式環境的,這個沒辦法,因為我們目前采用的是小程序網絡請求的請求頭來判斷運行環境的。
3. 雖然不盡完美,但在目前的情況下,貌似也只能這樣處理了,至少以后不用每次發布版本的時候再手動改服務器訪問地址了。
后續:
2018.12.29
發現小程序提審的時候,騰訊是通過體驗版進行審核驗證的,所以如果要使用本文中對生產、非生產(開發、體驗)環境進行區分的方法,測試服務器也需要支持https訪問,並綁定到小程序管理后台的request域名中去。不然應該是審核不通過的了。
還有另外一種方法,就是復用代碼包再創建一個測試用的小程序(無需申請小程序,仍使用原來的appid)進行開發調試,帶開發環境驗證沒問題,再將代碼合並到正式小程序代碼中,這樣測試小程序鏈接測試服務器,正式小程序項目鏈接生產環境,這樣開發調試就不會影響到正式小程序的提審發布了。