[重要更新]微信小程序登錄、用戶信息相關接口調整:使用 wx.getUserProfile 取代 wx.getUserInfo

  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 的開源項目庫://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);
              }
            }
          })
        }
      })
    }
});    

 

  相關後端代碼,如果是新項目,只需要按照之前的實現方式,舊項目不需要修改:

  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)上面都有缺陷,導致無法正常解密,因此,建議直接升級到最新的版本(見上圖)。升級基礎庫後,後端代碼不需要修改。

 

  下一篇我們將介紹微信公眾號模板消息下線後,如何使用「訂閱消息」進行開發。