2021年2月24日,微信官方团队发布了一个调整通知:《小程序登录、用户信息相关接口调整说明》,公告明确从4月13日起,所有发布的小程序将无法使用 wx.getUserInfo 接口(JS)和 <button open-type="getUserInfo"/> 标签来获取用户信息了。主要信息如下:
实际时间从1个月前(4月2日)起,我们已经陆续接到开发者的反馈,在开发环境已经无法正常使用旧版本的功能,这也意味着从现在开始,要进行小程序的开发必须符合调整后接口的标准。
虽然文档看上去很复杂,经过实际测试,其实修改的地方还是比较简单的,步骤如下:
第一步:替换原有的 <button open-type="getUserInfo"/> 标签为普通标签,例如:
<button bindtap="getUserInfo"> 获取头像昵称 </button>
在页面的 .js 文件中创建一个对应的方法 getUserInfo(如果以前就有可以直接修改):
getUserInfo: function (e) { //... }
第二步:在 getUserInfo 代码中调用 wx.getUserProfile 接口:
getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: \'用于完善会员资料\', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { this.setData({ userInfo: res.userInfo, hasUserInfo: true }) } }) }
完成。
以下是新接口调用的效果:
未登录状态 | 授权 | 完成授权 |
最新的 Demo 已经更新至 Senparc.Weixin SDK 的开源项目库:https://github.com/JeffreySu/WeiXinMPSDK
小程序文件目录:\src\Senparc.Weixin.WxOpen\src\Senparc.Weixin.WxOpen.AppDemo
后台程序目录如下:
框架 | 解决方案 | 小程序 Controller 代码 | 学习新一代 .NET | |
.NET Framework 4.5 |
\Samples\net45-mvc\Senparc.Weixin.MP.Sample.sln |
Senparc.Weixin.Sample项目下 Controllers/WxOpenController.cs |
|
|
.NET Core 3.1 | \Samples\netcore3.0-mvc\Senparc.Weixin.Sample.NetCore3.vs2019.sln | 学习 .NET Core 3.1 | ||
.NET 6.0(兼容5.0) | \Samples\net6-mvc\Senparc.Weixin.Sample.Net6.sln | 学习 .NET 6.0 |
在线 Demo:
小程序二维码 |
注意点
1、建议将小程序基础库升级到最新,否则可能导致无法正确解密:
2、注意 wx.getUserProfile 接口和 wx.login 接口的调用次序,Demo 中为了方便演示各项接口能力,所以进行了如下的嵌套操作:
wx.getUserProfile({ desc: \'用于完善会员资料\', success: function (userInfoRes) { //... //调用 wx.login 登录接口 wx.login({ success: function (res) { //换取openid & session_key wx.request({ url: wx.getStorageSync(\'domainName\') + \'/WxOpen/OnLogin\', method: \'POST\', header: { \'content-type\': \'application/x-www-form-urlencoded\' }, data: { code: res.code }, success:function(json){ var result = json.data; if(result.success) { wx.setStorageSync(\'sessionId\', result.sessionId); //校验 wx.request({ url: wx.getStorageSync(\'domainName\') + \'/WxOpen/CheckWxOpenSignature\', method: \'POST\', header: { \'content-type\': \'application/x-www-form-urlencoded\' }, data: { sessionId: result.sessionId,//wx.getStorageSync(\'sessionId\'), rawData:userInfoRes.rawData, signature:userInfoRes.signature }, success:function(json){ console.log(json.data); } }); //解密数据(建议放到校验success回调函数中,此处仅为演示) wx.request({ url: wx.getStorageSync(\'domainName\') + \'/WxOpen/DecodeEncryptedData\', method: \'POST\', header: { \'content-type\': \'application/x-www-form-urlencoded\' }, data: { \'type\':"userInfo", sessionId: result.sessionId,//wx.getStorageSync(\'sessionId\'), encryptedData: userInfoRes.encryptedData, iv: userInfoRes.iv }, success:function(json){ console.log(\'数据解密:\', json.data); } }); }else{ console.log(\'储存session失败!\',json); } } }) } }) } });
相关后端代码,如果是新项目,只需要参考 Demo 提供的代码即可,旧项目不需要修改:
wx.login 成功后,调用 WxOpenController.cs 中的 OnLogin 方法:
1 /// <summary> 2 /// wx.login登陆成功之后发送的请求 3 /// </summary> 4 /// <param name="code"></param> 5 /// <returns></returns> 6 [HttpPost] 7 public ActionResult OnLogin(string code) 8 { 9 try 10 { 11 var jsonResult = SnsApi.JsCode2Json(WxOpenAppId, WxOpenAppSecret, code); 12 if (jsonResult.errcode == ReturnCode.请求成功) 13 { 14 //Session["WxOpenUser"] = jsonResult;//使用Session保存登陆信息(不推荐) 15 //使用SessionContainer管理登录信息(推荐) 16 var unionId = ""; 17 var sessionBag = SessionContainer.UpdateSession(null, jsonResult.openid, jsonResult.session_key, unionId); 18 19 //注意:生产环境下SessionKey属于敏感信息,不能进行传输! 20 return Json(new { success = true, msg = "OK", sessionId = sessionBag.Key, sessionKey = sessionBag.SessionKey }); 21 } 22 else 23 { 24 return Json(new { success = false, msg = jsonResult.errmsg }); 25 } 26 } 27 catch (Exception ex) 28 { 29 return Json(new { success = false, msg = ex.Message }); 30 } 31 }
OnLogin 方法调用成功后进行签名校验:
1 /// <summary> 2 /// 检查签名 3 /// </summary> 4 /// <param name="sessionId"></param> 5 /// <param name="rawData"></param> 6 /// <param name="signature"></param> 7 /// <returns></returns> 8 [HttpPost] 9 public ActionResult CheckWxOpenSignature(string sessionId, string rawData, string signature) 10 { 11 try 12 { 13 var checkSuccess = Senparc.Weixin.WxOpen.Helpers.EncryptHelper.CheckSignature(sessionId, rawData, signature); 14 return Json(new { success = checkSuccess, msg = checkSuccess ? "签名校验成功" : "签名校验失败" }); 15 } 16 catch (Exception ex) 17 { 18 return Json(new { success = false, msg = ex.Message }); 19 } 20 }
同时进行用户信息解密及水印校验:
1 /// <summary> 2 /// 数据解密并进行水印校验 3 /// </summary> 4 /// <param name="type"></param> 5 /// <param name="sessionId"></param> 6 /// <param name="encryptedData"></param> 7 /// <param name="iv"></param> 8 /// <returns></returns> 9 [HttpPost] 10 public async Task<IActionResult> DecodeEncryptedData(string type, string sessionId, string encryptedData, string iv) 11 { 12 DecodeEntityBase decodedEntity = null; 13 14 try 15 { 16 switch (type.ToUpper()) 17 { 18 case "USERINFO"://wx.getUserInfo() 19 decodedEntity = EncryptHelper.DecodeUserInfoBySessionId( 20 sessionId, 21 encryptedData, iv); 22 break; 23 default: 24 break; 25 } 26 } 27 catch (Exception ex) 28 { 29 WeixinTrace.SendCustomLog("EncryptHelper.DecodeUserInfoBySessionId 方法出错", 30 $@"sessionId: {sessionId} 31 encryptedData: {encryptedData} 32 iv: {iv} 33 sessionKey: { (await SessionContainer.CheckRegisteredAsync(sessionId) 34 ? (await SessionContainer.GetSessionAsync(sessionId)).SessionKey 35 : "未保存sessionId")} 36 37 异常信息: 38 {ex.ToString()} 39 "); 40 } 41 42 //检验水印 43 var checkWatermark = false; 44 if (decodedEntity != null) 45 { 46 checkWatermark = decodedEntity.CheckWatermark(WxOpenAppId); 47 48 //保存用户信息(可选) 49 if (checkWatermark && decodedEntity is DecodedUserInfo decodedUserInfo) 50 { 51 var sessionBag = await SessionContainer.GetSessionAsync(sessionId); 52 if (sessionBag != null) 53 { 54 await SessionContainer.AddDecodedUserInfoAsync(sessionBag, decodedUserInfo); 55 } 56 } 57 } 58 59 //注意:此处仅为演示,敏感信息请勿传递到客户端! 60 return Json(new 61 { 62 success = checkWatermark, 63 //decodedEntity = decodedEntity, 64 msg = $"水印验证:{(checkWatermark ? "通过" : "不通过")}" 65 }); 66 }
上述方法中标红的接口调用、SessionKey管理和解密方法都已经封装在 Senparc.Weixin SDK 中(更多教程),只需要按照 Demo 演示的调用即可。
调试窗口结果:
注意:
1、不能在调用 wx.login 等过程的回调函数中,自动调用 wx.getUserProfile 来触发授权行为,因为 wx.getUserProfile 只能由用户手动触发。否则,系统会抛出异常:
error msg: getUserProfile:fail can only be invoked by user TAP gesture
关于这两个方法的同时使用问题也可以参考:《wx.getUserProfile不能和wx.login一起使用?》(PS:并非所有都是正解)
2、虽然官方提示需要使用2.10.4以上基础库,但是实测发现,2.10.4及之后的几个版本,虽然可以使用wx.getUserProfile,但在用户信息解密(wx.login)上面都有缺陷,导致无法正常解密,因此,建议直接升级到最新的版本(见上图)。升级基础库后,后端代码不需要修改。
下一篇我们将介绍微信公众号模板消息下线后,如何使用“订阅消息”进行开发。