getUserInfo較爲特殊,不包含在本文範圍內,主要針對須要受權的功能性api,例如:wx.startRecord,wx.saveImageToPhotosAlbum, wx.getLocationjavascript
倉庫地址:github.com/jinxuanzhen…html
小程序內若是要調用部分接口須要用戶進行受權,例如獲取地理位置信息,收穫地址,錄音等等,可是小程序對於這些須要受權的接口並非特別友好,最明顯的有兩點:java
通常狀況而言,每次受權時都應該激活彈窗進行提示,是否進行受權,例如:git
而小程序內只有第一次進行受權時纔會主動激活彈窗(微信提供的),其餘狀況下都會直接走fail回調,微信文檔也在句末添加了一句請開發者兼容用戶拒絕受權的場景
這種未作兼容的狀況下若是用戶想要使用錄音功能,第一次點擊拒絕受權,那麼以後不管如何也沒法再次開啓錄音權限**,很明顯不符合咱們的預期。github
因此咱們須要一個能夠進行二次受權的解決方案web
下面這段代碼是微信官方提供的受權代碼, 能夠看到也並無兼容拒絕過受權的場景查詢是否受權(即沒法再次調起受權)小程序
// 能夠經過 wx.getSetting 先查詢一下用戶是否受權了 "scope.record" 這個 scope
wx.getSetting({
success(res) {
if (!res.authSetting['scope.record']) {
wx.authorize({
scope: 'scope.record',
success () {
// 用戶已經贊成小程序使用錄音功能,後續調用 wx.startRecord 接口不會彈窗詢問
wx.startRecord()
}
})
}
}
})
複製代碼
那麼正常狀況下咱們該怎麼作呢?以地理位置信息受權爲例:api
wx.getLocation({
success(res) {
console.log('success', res);
},
fail(err) {
// 檢查是不是由於未受權引發的錯誤
wx.getSetting({
success (res) {
// 當未受權時直接調用modal窗進行提示
!res.authSetting['scope.userLocation'] && wx.showModal({
content: '您暫未開啓權限,是否開啓',
confirmColor: '#72bd4a',
success: res => {
// 用戶確認受權後,進入設置列表
if (res.confirm) {
wx.openSetting({
success(res){
// 查看設置結果
console.log(!!res.authSetting['scope.userLocation'] ? '設置成功' : '設置失敗');
},
});
}
}
});
}
});
}
});
複製代碼
上面代碼,有些同窗可能會對在fail回調裏直接使用wx.getSetting有些疑問,這裏主要是由於緩存
這裏爲了方便就直接調用權限檢查了 ,也能夠稍微封裝一下,方便擴展和複用,變成:微信
bindGetLocation(e) {
let that = this;
wx.getLocation({
success(res) {
console.log('success', res);
},
fail(err) {
that.__authorization('scope.userLocation');
}
});
},
bindGetAddress(e) {
let that = this;
wx.chooseAddress({
success(res) {
console.log('success', res);
},
fail(err) {
that.__authorization('scope.address');
}
});
},
__authorization(scope) {
/** 爲了節省行數,不細寫了,能夠參考上面的fail回調,大體替換了下變量res.authSetting[scope] **/
}
複製代碼
看上去好像沒有什麼問題,fail裏只引入了一行代碼,
這裏若是隻針對較少頁面的話我認爲已經夠用了,畢竟**‘如非必要,勿增實體’,可是對於小打卡這個小程序來講可能涉及到的頁面,須要調用的場景偏多**,我並不但願每次都人工去調用這些方法,畢竟人總會犯錯
上文已經提到了背景和常見的處理方法,那麼梳理一下咱們的目標,咱們究竟是爲了解決什麼問題?列了下大體爲下面三點:
爲了節省認知成本和減小出錯機率,我但願他是這個api默認攜帶的功能,也就是說因未受權出現錯誤時自動調起是否開啓受權的彈窗
爲了實現這個功能,咱們可能須要對wx的原生api進行一層包裝了(關於頁面的包裝能夠看:如何基於微信原生構建應用級小程序底層架構)
這裏須要注意的一點是直接使用常見的裝飾模式是會出現報錯,由於wx這個對象在設置屬性時沒有設置set方法,這裏須要單獨處理一下
// 直接裝飾,會報錯 Cannot set property getLocation of #<Object> which has only a getter
let $getLocation = wx.getLocation;
wx.getLocation = function (obj) {
$getLocation(obj);
};
// 須要作一些小處理
wx = {...wx}; // 對wx對象從新賦值
let $getLocation = wx.getLocation;
wx.getLocation = function (obj) {
console.log('調用了wx.getLocation');
$getLocation(obj);
};
// 再次調用時會在控制檯打印出 '調用了wx.getLocation' 字樣
wx.getLocation()
複製代碼
第一步咱們已經控制了wx.getLocation這個api,接下來就是對於fail方法的劫持,由於咱們須要在fail里加入咱們本身的受權邏輯
// 方法劫持
wx.getLocation = function (obj) {
let originFail = obj.fail;
obj.fail = async function (errMsg) {
// 0 => 已受權 1 => 拒絕受權 2 => 受權成功
let authState = await authorization('scope.userLocation');
// 已受權報錯說明並非權限問題引發,因此繼續拋出錯誤
// 拒絕受權,走已有邏輯,繼續排除錯誤
authState !== 2 && originFail(errMsg);
};
$getLocation(obj);
};
// 定義檢查受權方法
function authorization(scope) {
return new Promise((resolve, reject) => {
wx.getSetting({
success (res) {
!res.authSetting[scope]
? wx.showModal({
content: '您暫未開啓權限,是否開啓',
confirmColor: '#72bd4a',
success: res => {
if (res.confirm) {
wx.openSetting({
success(res){
!!res.authSetting[scope] ? resolve(2) : resolve(1)
},
});
}else {
resolve(1);
}
}
})
: resolve(0);
}
})
});
}
// 業務代碼中的調用
bindGetLocation(e) {
let that = this;
wx.getLocation({
type: 'wgs84',
success(res) {
console.log('success', res);
},
fail(err) {
console.warn('fail', err);
}
});
}
複製代碼
能夠看到如今已實現的功能已經達到了咱們最開始的預期,即因受權報錯做爲了wx.getLocation默認攜帶的功能,咱們在業務代碼裏不再須要處理任何再次受權的邏輯
也意味着wx.getLocation這個api不論在任何頁面,組件,出現頻次如何,**咱們都不須要關心它的受權邏輯(**效果原本想貼gif圖的,後面發現有圖點大,具體效果去git倉庫跑一下demo吧)
上面所述大體是整個原理的一個思路,可是應用到實際項目中還須要考慮到總體的擴展性和維護成本,那麼就讓咱們再來優化一波
代碼包結構:
本質上只要在app.js這個啓動文件內,引用./x-wxx/index文件對原有的wx對象進行覆蓋便可
**簡單的代碼邏輯: **
// 大體流程:
//app.js
wx = require('./x-wxx/index'); // 入口處引入文件
// x-wxx/index
const apiExtend = require('./lib/api-extend');
module.exports = (function (wxx) { // 對原有方法進行擴展
wxx = {...wxx};
for (let key in wxx) {
!!apiExtend[key] && (()=> {
// 緩存原有函數
let originFunc = wxx[key];
// 裝飾擴展的函數
wxx[key] = (...args) => apiExtend[key](...args, originFunc);
})();
}
return wxx;
})(wx);
// lib/api-extend
const Func = require('./Func');
(function (exports) { // 須要擴展的api(相似於config)
// 獲取權限
exports.authorize = function (opts, done) {
// 當調用爲"確認受權方法時"直接執行,避免死循環
if (opts.$callee === 'isCheckAuthApiSetting') {
console.log('optsopts', opts);
done(opts);
return;
}
Func.isCheckAuthApiSetting(opts.scope, () => done(opts));
};
// 選擇地址
exports.chooseAddress = function (opts, done) {
Func.isCheckAuthApiSetting('scope.address', () => done(opts));
};
// 獲取位置信息
exports.getLocation = function (opts, done) {
Func.isCheckAuthApiSetting('scope.userLocation', () => done(opts));
};
// 保存到相冊
exports.saveImageToPhotosAlbum = function (opts, done) {
Func.isCheckAuthApiSetting('scope.writePhotosAlbum', () => done(opts));
}
// ...more
})(module.exports);
複製代碼
能夠看到咱們不管後續擴展任何的微信api,都只須要在lib/api-extend.js 配置便可,這裏不只僅侷限於受權,也能夠作一些日誌,傳參的調整,例如:
// 讀取本地緩存(同步)
exports.getStorageSync = (key, done) => {
let storage = null;
try {
storage = done(key);
} catch (e) {
wx.$logger.error('getStorageSync', {msg: e.type});
}
return storage;
};
複製代碼
這樣是否是很方便呢,至於Func.isCheckAuthApiSetting這個方法具體實現,爲了節省文章行數請自行去git倉庫裏查看吧
錄音受權略爲特殊,以wx.getRecorderManager爲例,它並不能直接調起錄音受權,因此並不能直接用上述的這種方法,不過咱們能夠曲線救國,達到相似的效果,還記得咱們對於wx.authorize的包裝麼,本質上咱們是能夠直接使用它來進行受權的,好比將它用在咱們已經封裝好的錄音管理器的start方法進行校驗
wx.authorize({
scope: 'scope.record'
});
複製代碼
實際上,爲方便統一管理,Func.isCheckAuthApiSetting方法其實都是使用wx.authorize來實現受權的
exports.isCheckAuthApiSetting = async function(type, cb) {
// 簡單的類型校驗
if(!type && typeof type !== 'string') return;
// 聲明
let err, result;
// 獲取本地配置項
[err, result] = await to(getSetting()); // 這裏能夠作一層緩存,檢查緩存的狀態,若是已受權能夠沒必要再次走下面的流程,直接return出去便可
if (err) {
return cb('fail');
}
// 當受權成功時,直接執行
if (result.authSetting[type]) {
return cb('success');
}
// 調用獲取權限
[err, result] = await to(authorize({scope: type, $callee: 'isCheckAuthApiSetting'}));
if (!err) {
return cb('success');
}
}
複製代碼
用戶受權極爲特殊,由於微信將wx.getUserInfo升級了一版,沒有辦法直接喚起了,詳見《公告》,
因此須要單獨處理,關於這裏會拆出單獨的一篇文章來寫一些有趣的玩法
最後稍微總結下,經過上述的方案,咱們解決了最開始目標的同時,也爲wx這個對象上的方法提供了統一的裝飾接口(lib/api-extend文件),便於後續其餘行爲的操做好比埋點,日誌,參數校驗
仍是那麼一句話吧,小程序無論和web開發有多少不一樣,本質上都是在js
境上進行開發的,
但願小程序的社區環境更加活躍,帶來更多有趣的東西