微信小程序登录授权及手机号获取
实现效果:
1.开发工具uniapp
2.功能模块
① 登录模块(微信登录,授权获取昵称,头像,地区等,授权获取手机号,解密手机号)
② UI模块 (uview,vant)
③ 请求模块 (axios封装)
④ 问题汇总
3.功能拆解(附代码)
① 微信登录:文档(https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html)
步骤:
<1> 通过微信官方提供的登录api(wx.login)获取code,用code当参数请求后台接口获取openid,以此作为登录凭证
<2> 分段代码:
wx.login({ success(res) { if (res.code) { //向后台发起网络请求 wx.request({ url: '后台提供的接口地址', data: { code: res.code }, success(res) { console.log("获取到的openid是:", res) } }) } else { console.log('登录失败!' + res.errMsg) } } })
② 授权获取昵称,头像,地区:文档(https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html)
步骤:
<1> 页面产生点击事件(例如 button
上 bindtap
的回调中)后才可调用
<2> 分段代码:
wx.getUserProfile({ lang: 'zh_CN', desc: '用户登录', success: (res) => { console.log("用户允许授权后获得的信息为:", res) }, fail: (err) => { console.log("用户拒绝授权") } })
③ 授权获取手机号及解密,文档(https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html)
步骤:
<1> 将 button 组件 open-type
的值设置为 getPhoneNumber
<2> 用户同意后,通过 bindgetphonenumber
事件回调获取到微信服务器返回的加密数据
<3> 获取session_key
<4> 向后台发送参数解密手机号
<5> 分段代码:
// html代码: // 原生微信小程序写法: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> // uniapp写法: <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号码</button> // js代码: // 获取手机号: getPhoneNumber(e) { if (e.detail.errMsg !== "getPhoneNumber:ok") return; // 如果用户不同意授权则返回并结束 console.log(e.detail.errMsg) console.log(e.detail.iv) console.log(e.detail.encryptedData) } // 检验登录是否过期: uni.checkSession({ success(res) { console.log("登录未过期,进行下一步操作") }, fail() { console.log("登录已过期,请重新登录") } }) // 获取session_key,以此作为参数进行解密手机号 wx.request({ url: "https://api.weixin.qq.com/sns/jscode2session", data: { 'appid': "小程序的appid", 'secret': "小程序密钥", 'js_code': "登录时获取到的code", 'grant_type': "authorization_code" // 默认填写authorization_code }, method: 'GET', header: { 'content-type': 'application/json' }, success: function(res) { console.log("成功获取到session_key", res) }, fail(err) { console.log("获取session_key失败", err) } }) // 解密手机号 wx.request({ url: '后台提供的接口地址', data: { 'encryptedData': e.detail.encryptedData, 'iv': e.detail.iv, 'session_key': "上一步获取到的session_key" }, method: 'GET', header: { 'content-type': 'application/json' }, success: function(data) { console.log("获取到的手机号是", data) }, fail: function(err) { console.log(err) } })
④ 微信登录流程的完整代码:
<template> <!-- #ifdef MP-WEIXIN --> <view v-if="!isCanUse"> <view> <view class='header'> <image src='你的网站logo等等'></image> </view> <view class='content'> <view>申请获取以下权限</view> <text>获得你的公开信息(昵称,头像、地区等)</text> </view> <button v-if="!flag" type="primary" class='btn' withCredentials="true" lang="zh_CN" open-type="getUserInfo" @tap="wxLogin"> 授权登录 </button> <button v-else type="primary" class='btn' open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号码</button> </view> </view> <!-- #endif --> </template> <script> export default { data() { return { SessionKey: '', OpenId: '', code: '', isCanUse: uni.getStorageSync('isCanUse'), //默认为true token: '', userInfo: { nickName: null, avatarUrl: null, }, flag: false, toWeb: false, webSrc: 'http://localhost:8080/#/?token=' + uni.getStorageSync('token'), } }, onLoad() { if (uni.getStorageSync('isCanUse') == '1') { console.log('已经授权登录过了,请直接跳转') } else { console.log('未授权登录,请点击登录') } }, methods: { wxLogin(e) { let p1 = this.wxSilentLogin() // 获取code let p2 = this.wxGetUserProfile() // 获取用户信息 p1.then((code) => { return code }) .then((code) => { this.code = code return new Promise((resolve, reject) => { p2.then((res) => { resolve({ code, iv: res.iv, encryptedData: res.encryptedData, }) }).catch((err) => { reject(err) }) }) }) .then((res) => { let _this = this // 请求服务器 wx.request({ url: '后台提供的接口', method: 'get', data: { code: res.code, encrypted_data: res.encryptedData, iv: res.iv, }, header: { 'content-type': 'application/json', // 默认值 }, success(res) { uni.setStorageSync('token', res.data.data) _this.token = res.data.data console.log('获取token', _this.token) }, }) }) .catch((err) => { console.log(err) }) }, wxGetUserProfile: function () { // 获取头像昵称等 let _this = this return new Promise((resolve, reject) => { wx.getUserProfile({ lang: 'zh_CN', desc: '用户登录', success: (res) => { resolve(res) console.log('获取到的用户信息', res.userInfo) _this.userInfo.nickName = res.userInfo.nickName _this.userInfo.avatarUrl = res.userInfo.avatarUrl uni.setStorageSync('isCanUse', '1') _this.flag = true // _this.updateUserInfo(); }, // 失败回调 fail: (err) => { reject(err) console.log('选择了拒绝') }, }) }) }, wxSilentLogin: function () { // 获取code return new Promise((resolve, reject) => { wx.login({ success(res) { resolve(res.code) console.log('获取得到的loginres', res) }, fail(err) { reject(err) }, }) }) }, getPhoneNumber: function (e) { var self = this if (e.detail.errMsg !== 'getPhoneNumber:ok') return wx.showLoading() uni.checkSession({ success(res) { if (self.code) { // 2.访问登录凭证校验接口获取session_key wx.request({ url: 'https://api.weixin.qq.com/sns/jscode2session', data: { appid: '小程序appid', secret: '小程序密钥', js_code: self.code, grant_type: 'authorization_code', }, method: 'GET', header: { 'content-type': 'application/json', }, success: function (data) { console.log('获取到session_key啦', data) if (data.statusCode == 200) { //3. 解密 wx.request({ url: '后台提供的接口', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, sessionKey: data.data.session_key, }, method: 'GET', header: { 'content-type': 'application/json', }, success: function (data) { wx.hideLoading() console.log('获取到的手机号是', data.data.phoneNumber) }, fail: function (err) { console.log(err) }, }) } }, fail: function (err) { console.log(err) }, }) } else { wx.showToast({ icon: 'none', title: '授权失败,请重新授权', }) self.flag = false } }, fail() { wx.showToast({ icon: 'none', title: '登录过期,请重新登录', }) }, }) }, updateUserInfo: function () { return new Promise((resolve, reject) => { console.log('发送给后台的用户信息', this.userInfo) wx.request({ url: '后台提供的接口', method: 'POST', data: { openId: '', password: '', telePhone: '', tenantId: 0, username: '', }, header: { 'content-type': 'application/json', // 默认值 }, success(res) { console.log(333, res.data) }, }) wx.login({ success(res) { resolve(res.code) }, fail(err) { reject(err) }, }) }) }, }, } </script> <style> .header { margin: 90rpx 0 90rpx 50rpx; border-bottom: 1px solid #ccc; text-align: center; width: 650rpx; height: 300rpx; line-height: 450rpx; } .header image { width: 240rpx; height: 240rpx; border-radius: 50%; } .content { margin-left: 50rpx; margin-bottom: 90rpx; } .content text { display: block; color: #9d9d9d; margin-top: 40rpx; } .btn { background: #1890FF !important; line-height: 80rpx !important; border-radius: 80rpx !important; margin: 70rpx 50rpx !important; font-size: 35rpx !important; } </style>
注:1. background: url 无法使用静态资源,可以使用线上资源或者数据流(base64等),在线图片库(https://www.superbed.cn/)
2.分包:文件资源过大时,可使用分包加载 (https://www.cnblogs.com/cisum/p/10190245.html)