在微信小程序中,如果要使用某些敏感接口,比如录音、获取用户信息等等,需要弹窗让用户先获取到相关接口的权限,才能继续使用。
这段时间在开发微信小程序,刚开始没有注意到授权问题,之后认真考虑之后才发现这个坑有多深。
以下,是我的思考。
# 一切从回调地狱开始 ↵
原生的微信接口,均不是
promise
风格,因此需要改写成 Promise 风格。一开始我没有特别的去把全部接口 Promise 化,后来我发现,不这样做,很可能会把自己搞死,因为嵌套会非常深 …
查阅文档可知微信小程序用的 JSCore 支持
大部分
ES6 特性,其中包括 Promise 对象
,因此无须 polyfill
直接可以改写:首先是
wxPromisify
用于处理单个接口的 Promise 化 :00function wxPromisify(fn) { 01 return function (obj = {}) { 02 return new Promise((resolve, reject) => { 03 obj.success = function (res) { 04 //成功 05 resolve(res) 06 } 07 obj.fail = function (err) { 08 //失败 09 reject(err) 10 } 11 fn(obj) 12 }) 13 } 14}
然后
reduce
需要 Promisify
的列表,即暴露接口:00export default [ 01 'login', 'getUserInfo', 'authorize', 'getSetting', 'startRecord', 'stopRecord', 02 'showModal', 'openSetting' 03].reduce((acc, cur) => { 04 acc[cur] = wxPromisify(wx[cur]); 05 06 return acc; 07}, { 08 wxPromisify: wxPromisify 09});
保存为
wx.promise.js
或者什么别的文件 …按照如下方式使用:
00import _ from 'path/to/your/wx.promise'; 01 02_.getUserInfo(res => { 03 // do some thing ... 04});
# 授权问题 ↵
敏感接口在使用前,都必须经过用户的同意之后才可以使用,否则 Promise 实例会转向 rejected 状态。
而调用授权的代码,是一个异步的过程。
此外,不是说每一次都要授权,弹出授权的窗口的情况有如下特点:
- 在未授权的情况下调用接口,微信会自动弹出授权接口。此时原操作会失败 (走
fail
回调) - 主动调用
wx.authorize
并给出需要授权的接口名,即可授权,这一步之后调用敏感接口才不会走fail
- 用户选择的授权会被微信缓存,即一旦授权,在一段时间内无须再授权即可成功调用敏感接口,但是,如果用户拒绝授权,则接口均无法使用,程序也无法正常运行。
- 用
wx.getSetting
可以得到被缓存过的授权情况 - 用
wx.openSetting
可以让用户修改缓存过的授权情况 (微信的弹窗)
# 长按录音问题
不应该在长按的时候录音,这样录音的流程就迷路了。应该在页面
onLoad
的时候处理。# 全局 userInfo 问题
不只是 userInfo 很多全局的数据请求,应该放在
app.js
里完成,并提供 Promise
# 用户拒绝授权
这是个问题,待会讨论
# 用户的行为 ↵
用户的行为要么就是允许了,要么就是拒绝了,前者没什么好说的,继续业务即可,对于后者,其原因只能归为两种:
- 误触
- 搞事… 或者测试…
针对第二种搞事的,我认为可以耗,即他点拒绝的时候,再
getSetting
判断是不是拿到权限了,如果不是,递归请求授权之,否则,继续业务。这样来看,如果用户误触,上面这种解决方法也适用。
# auth.js 源码 ↵
对此,编写
auth.js
,首先,获取 Promise 过的微信接口:00import _ from './wx.promise';
再来编写
get
函数00function get(key){ 01 // 合成接口对应的授权名 02 var scope = 'scope.' + key; 03 // Promise 04 return new Promise((authRes, authRej) => { 05 // 获取授权情况 06 _.getSetting().then(res => { 07 if (res.authSetting[scope]){ 08 // 曾经确实是授过权,直接 resolved 09 authRes(true); 10 } else { 11 // 尚未授权,则需要主动挑起授权 12 _.authorize({ 13 // 希望授权的名字 14 scope: scope 15 }).then(suc => { 16 // 用户同意授权, resolved 17 authRes(suc); 18 }, rej => { 19 // 不然的话 就是我刚刚提到的递归请求授权了 20 // 因此编写 reGet 21 reGet(scope, authRes); 22 }) 23 } 24 }) 25 }); 26}
以下是
reGet
的实现:00function reGet(scope, authRes){ 01 // 弹窗询问 02 _.showModal({ 03 title: '授权提醒', 04 content: '拒绝授权会影响小程序的使用, 请点击重新授权', 05 confirmText: '重新授权', 06 // 只有确定,没有取消 07 showCancel: false 08 }).then(() => { 09 // 打开设置 用户自己设置被拒绝的权限为允许 10 _.openSetting().then(() => { 11 // 用户结束了设置框 12 // 但是还要检查 13 _.getSetting().then(res => { 14 if (!res.authSetting[scope]){ 15 // 居然什么也没做就退出来设置了 16 // 递归地 reGet 17 // 写一个 setTimeout 防止太阻塞 JSCore ... 18 setTimeout(() => { 19 reGet(scope, authRes); 20 }); 21 } else { 22 // resolved! 23 authRes(); 24 } 25 }) 26 }); 27 }); 28}
# Promise 的内涵 ↵
写好
auth.js
就可以高枕无忧的使用接口了:00import auth from 'path/to/your/auth'; 01 02// 获取录音权限 03auth.get('record').then(suc => { 04 // resolved ~ 05 // 可以干活儿了 06});
Promise 的内涵即是字面意思上的承诺,auth 的 get 向程序员承诺了这样一件事:
我 a 某人一定会 get 到授权才开始干活 !