小程序开发另类小技巧 --用户授权篇

本文主要帮助大家解决部分功能接口二次授权+统一管理(包含对于wx对象的扩展)问题,例如获取地理位置信息,收获地址,录音等授权问题

小程序开发另类小技巧 --用户授权篇

getUserInfo较为特殊,不包含在本文范围内,主要针对需要授权的功能性api,例如:wx.startRecordwx.saveImageToPhotosAlbum, wx.getLocation

原文地址:https://www.yuque.com/jinxuanzheng/gvhmm5/arexcn

仓库地址:https://github.com/jinxuanzheng01/weapp-auth-demo

背景

小程序内如果要调用部分接口需要用户进行授权,例如获取地理位置信息,收获地址,录音等等,但是小程序对于这些需要授权的接口并不是特别友好,最明显的有两点:

  • 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调
  • 没有统一的错误信息提示,例如错误码

一般情况而言,每次授权时都应该激活弹窗进行提示,是否进行授权,例如:

而小程序内只有第一次进行授权时才会主动激活弹窗(微信提供的),其他情况下都会直接走fail回调,微信文档也在句末添加了一句请开发者兼容用户拒绝授权的场景, 这种未做兼容的情况下如果用户想要使用录音功能,第一次点击拒绝授权,那么之后无论如何也无法再次开启录音权限**,很明显不符合我们的预期。

所以我们需要一个可以进行二次授权的解决方案

常见处理方法

官方demo

下面这段代码是微信官方提供的授权代码, 可以看到也并没有兼容拒绝过授权的场景查询是否授权(即无法再次调起授权)

// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope
wx.getSetting({
  success(res) {
    if (!res.authSetting[‘scope.record‘]) {
      wx.authorize({
        scope: ‘scope.record‘,
        success () {
          // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
          wx.startRecord()
        }
      })
    }
  }
})

一般处理方式

那么正常情况下我们该怎么做呢?以地理位置信息授权为例:

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有些疑问,这里主要是因为

  • 微信返回的错误信息没有一个统一code
  • errMsg又在不同平台有不同的表现
  • 从埋点数据得出结论,调用这些api接口出错率基本集中在未授权的状态下

这里为了方便就直接调用权限检查了 ,也可以稍微封装一下,方便扩展和复用,变成:

  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里只引入了一行代码,

这里如果只针对较少页面的话我认为已经够用了,毕竟**‘如非必要,勿增实体’,但是对于小打卡这个小程序来说可能涉及到的页面,需要调用的场景偏多**,我并不希望每次都人工去调用这些方法,毕竟人总会犯错

梳理目标

上文已经提到了背景和常见的处理方法,那么梳理一下我们的目标,我们到底是为了解决什么问题?列了下大致为下面三点:

  • 兼容用户拒绝授权的场景,即提供二次授权
  • 解决多场景,多页面调用没有统一规范的问题
  • 在底层解决,业务层不需要关心二次授权的问题

扩展wx[funcName]方法

为了节省认知成本和减少出错概率,我希望他是这个api默认携带的功能,也就是说因未授权出现错误时自动调起是否开启授权的弹窗

为了实现这个功能,我们可能需要对wx的原生api进行一层包装了(关于页面的包装可以看:如何基于微信原生构建应用级小程序底层架构

为wx.getLocation添加自己的方法

这里需要注意的一点是直接使用常见的装饰模式是会出现报错,因为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()

劫持fail方法

第一步我们已经控制了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环境上进行开发的,希望小程序的社区环境更加活跃,带来更多有趣的东西

原链接地址:https://developers.weixin.qq.com/community/develop/article/doc/0000c42fea0668ff36b80d20451813

原文地址:https://www.cnblogs.com/aslxwjh/p/12151005.html

时间: 01-05

小程序开发另类小技巧 --用户授权篇的相关文章

微信小程序_微信小程序开发,小程序源码、案例、教程

原文地址:http://whosmall.com/?post=448 本文标签: 微信小程序 小程序源码案例 小程序项目 小程序源码 微信小程序教程 什么是微信小程序? 微信小程序是微信基于微信平台的一个应用发布平台,微信小程序app开发属于原生app组件提供js接口的开发方式,比混合是app的用户体验更好,仅次于原生应用. 不过微信小程序定位于小,要符合轻量易用无需下载,所以从体积上也是有限制,整个小程序应用体积不能超过1M. 微信小程序的应用场景? 微信小程序的应用场景适用于轻量应用,非强交

支付宝小程序开发——修改小程序原生radio默认样式

如下,要做这样的单选按钮,小程序原生的单选按钮使用起来还算是比较方便的,所以选择直接进行样式改造了: 对于微信小程序来说,单选按钮实质上是一个有自己内部结构的组件,内部有自己的元素结构,所以在重定义样式的时候,需要用到组件你内部的元素类名来定义,详见:微信小程序开发——修改小程序原生checkbox.radio默认样式. 不同于微信小程序,支付宝小程序的大部分组件及样式跟Html都比较接近的.对于单选按钮,则与html标签类似,可以采用同样的方法进行样式修改.具体如下: /* 单选按钮样式*/

小发包小程序开发、小发包语音红包代码编写

这种技术不会在几年之内被部署到战场,但预计它不久以后将会从军事应用拓展到商业应用--前提是它真的奏效.它将在包括西班牙.意大利和德国在内的多个欧洲国家争夺市场份额.目前,它有一半以上的智能手机业务营收来自中国以外的市场. 诸如"高冷天蝎"."夜猫子"等针对自己偏好的趣味分析,像这样的店,京东已经在北京.上海.深圳等地开设了21家,根据京东的计划年底前将在全国开设超过300家以3C为主的零售体验店. 小发包小程序开发 151.^.188.^.70^5...14  电微

[小程序开发] 微信小程序内嵌网页web-view开发教程

为了便于开发者灵活配置小程序,微信小程序开放了内嵌网页能力.这意味着小程序的内容不再局限于pages和large,我们可以借助内嵌网页丰富小程序的内容.下面附上详细的开发教程(含视频操作以及注意事项) 一.小程序内嵌网页web-view教程 1) 微信公众平台,登录小程序账号 2) 左侧-设置-开发设置-业务域名-配置 3) 小程序管理员微信扫码 4) 填写小程序业务域名,域名需ICP备案 5) 下载校检文件上传至服务器指定目录,保存 6) 小程序业务域名配置完成7) 打开微信开发者工具,添加小

微信小程序开发 [01] 小程序基本结构和官方IDE简介

1.小程序账户注册 实际上在进行开发时没有注册小程序账户也是可以的,官方提供的IDE提供实时编译模拟预览,和当前你有没有绑定小程序账户没有关系. 当然,最终你要正式上线你的小程序的话,肯定还是需要账户的,申请流程不再阐述了,请参考官方文档<小程序注册>,个人开发者选择个人就行了. 2.官方demo和基本结构 在下载官方的小程序开发工具之后,新建项目选择小程序: 然后IDE会自动生成一个简单的小程序,如下: 在中间的目录结构中可以看到,不论是根目录下还是其他单个模块的页面下,有这四类文件: .j

微信小程序之 wx.getUserInfo引导用户授权问题

首先,在page外定义一个函数用户判断是否为空对象 var isEmptyObject = function (e) { var temp; for (temp in e) return !1; return !0 } 然后,在page中的onload里面调用授权 onLoad: function () { var that = this; if (app.globalData.userInfo) { this.setData({ userInfo: app.globalData.userInf

微信小程序开发——微信小程序下拉刷新真机无法弹回

开发工具中下拉之后页面回弹有一定的延迟,这个时间也有点久.真机测试,下拉后连回弹都没有,这个问题要解决,就得在下拉函数里加上停止下拉刷新的API,如下: /** * 下拉刷新 */ onPullDownRefresh: function(e) { this.getList() wx.stopPullDownRefresh(); } 原文地址:https://www.cnblogs.com/xyyt/p/10855817.html

资讯 | 2018年1月15日微信公开课解读!微信小程序开发资源

引言:2018年1月15日 微信公开课PRO开课了,本次课时内容将涉及:小程序.智慧零售.企业微信.小游戏  (附学习参考资料) 为了快速理解「2018 微信公开课 PRO 版」上张小龙的一小时演讲内容,结合下午微信公开课课室A_B_C的课时安排,这里为大家提前整理了相应的小程序学习参考资料: 微信公开课pro_A课室(14:00-17:50) 主题:小程序产品能力.开发.应用及规则 一.如何开发一个优秀的微信小程序/小游戏 微信小程序官方工具  https://mp.weixin.qq.com

微信小程序开发——连续快速点击按钮调用小程序api返回后仍然自动重新调用的异常处理

前言: 小程序开发中诸如获取用户手机号码.调起微信支付.领取卡券等api都是会有一定的延迟的.也就是说通过点击按钮调用这些api的时候,从点击按钮调用api,到支付页面或者领取卡券界面展示出来是需要一定时间的,连续点击按钮,还是有可能会重复调用的. 虽然这种情况有点极端,正常用户是不会这么连续快速的点击按钮的,但是也不能排除有用户手抖,连续点了两下.如果重复调用的话,不仅体验不好,单击事件中涉及到后端接口操作的也可能引起其他异常.所以这个问题还是要处理下的. 刚开始想到的是使用loading开启