2017-08-05
wx
xcx
微信小程序的授权问题
在微信小程序中,如果要使用某些敏感接口,比如录音、获取用户信息等等,需要弹窗让用户先获取到相关接口的权限,才能继续使用。
这段时间在开发微信小程序,刚开始没有注意到授权问题,之后认真考虑之后才发现这个坑有多深。
以下,是我的思考。

# 一切从回调地狱开始

原生的微信接口,均不是 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 状态。
而调用授权的代码,是一个异步的过程。
此外,不是说每一次都要授权,弹出授权的窗口的情况有如下特点:
  1. 在未授权的情况下调用接口,微信会自动弹出授权接口。此时原操作会失败 (走 fail 回调)
  2. 主动调用 wx.authorize 并给出需要授权的接口名,即可授权,这一步之后调用敏感接口才不会走 fail
  3. 用户选择的授权会被微信缓存,即一旦授权,在一段时间内无须再授权即可成功调用敏感接口,但是,如果用户拒绝授权,则接口均无法使用,程序也无法正常运行。
  4. wx.getSetting 可以得到被缓存过的授权情况
  5. wx.openSetting 可以让用户修改缓存过的授权情况 (微信的弹窗)

# 长按录音问题

不应该在长按的时候录音,这样录音的流程就迷路了。应该在页面 onLoad 的时候处理。

# 全局 userInfo 问题

不只是 userInfo 很多全局的数据请求,应该放在 app.js 里完成,并提供 Promise

# 用户拒绝授权

这是个问题,待会讨论

# 用户的行为

用户的行为要么就是允许了,要么就是拒绝了,前者没什么好说的,继续业务即可,对于后者,其原因只能归为两种:
  1. 误触
  2. 搞事… 或者测试…
针对第二种搞事的,我认为可以耗,即他点拒绝的时候,再 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 到授权才开始干活 !




回到顶部