一、简介:
微信小程序登录这个概念有些广,有调用官方登录接口和调用开发者第三方服务器登录两种。为什么要登录?因为小程序用户从微信打开时,就已经登录,但是登录后,对于开发者来说还是未登录状态,没有一个唯一固定的标识,无法保存用户的信息,用户第二次进入小程序不能保持上次登录时的数据、状态。
关于登录流程,我这边从网上拿的一张图,虽然不是很认同里面的说法,但流程大致如此:
其中,第三方服务器就是开发者自己的服务器了,比如我的:https://wx.pukuimin.top (测试服务器,暂时只购买一年)
二、登录功能开发
虽然功能看起来简单,但从无到有做出来,还是要花些时间,重要的没做出来之前感觉不太好理解,下面着手开发登录功能,小程序服务端用的是.Net WEB API开发。
1、小程序客户端
1.1 在app.json 添加页面
"pages/test/test"
保存后,在pages下面,会生成test目录,然后下面有 test.js,test.json,test.wxml,test.wxss四个文件
1.2 修改config.js
var host = 'https://wx.pukuimin.top'; var config = { // 下面的地址配合云端 Demo 工作 service: { host, // 登录地址,用于建立会话 getUserInfoUrl: `${host}/WeiXin/GetUserInfo`, } };
1.3 引用的腾讯云demo代码
/vendor/wafer2-client-sdk/lib/login.js 我稍微改了一下,导出 getWxLoginResult 方法,在外部要调用
var utils = require('./utils'); var constants = require('./constants'); var Session = require('./session'); /*** * @class * 表示登录过程中发生的异常 */ var LoginError = (function () { function LoginError(type, message) { Error.call(this, message); this.type = type; this.message = message; } LoginError.prototype = new Error(); LoginError.prototype.constructor = LoginError; return LoginError; })(); /** * 微信登录,获取 code 和 encryptData */ var getWxLoginResult = function getLoginCode(callback) { wx.login({ success: function (loginResult) { console.log("loginResult:"); console.log(loginResult); wx.getUserInfo({ success: function (userResult) { console.log("userResult:"); console.log(userResult); callback(null, { code: loginResult.code, encryptedData: userResult.encryptedData, iv: userResult.iv, userInfo: userResult.userInfo, }); }, fail: function (userError) { var error = new LoginError(constants.ERR_WX_GET_USER_INFO, '获取微信用户信息失败,请检查网络状态'); error.detail = userError; callback(error, null); }, }); }, fail: function (loginError) { var error = new LoginError(constants.ERR_WX_LOGIN_FAILED, '微信登录失败,请检查网络状态'); error.detail = loginError; callback(error, null); }, }); }; var noop = function noop() {}; var defaultOptions = { method: 'GET', success: noop, fail: noop, loginUrl: null, }; /** * @method * 进行服务器登录,以获得登录会话 * * @param {Object} options 登录配置 * @param {string} options.loginUrl 登录使用的 URL,服务器应该在这个 URL 上处理登录请求 * @param {string} [options.method] 请求使用的 HTTP 方法,默认为 "GET" * @param {Function} options.success(userInfo) 登录成功后的回调函数,参数 userInfo 微信用户信息 * @param {Function} options.fail(error) 登录失败后的回调函数,参数 error 错误信息 */ var login = function login(options) { options = utils.extend({}, defaultOptions, options); if (!defaultOptions.loginUrl) { options.fail(new LoginError(constants.ERR_INVALID_PARAMS, '登录错误:缺少登录地址,请通过 setLoginUrl() 方法设置登录地址')); return; } var doLogin = () => getWxLoginResult(function (wxLoginError, wxLoginResult) { if (wxLoginError) { options.fail(wxLoginError); return; } var userInfo = wxLoginResult.userInfo; // 构造请求头,包含 code、encryptedData 和 iv var code = wxLoginResult.code; var encryptedData = wxLoginResult.encryptedData; var iv = wxLoginResult.iv; var header = {}; header[constants.WX_HEADER_CODE] = code; header[constants.WX_HEADER_ENCRYPTED_DATA] = encryptedData; header[constants.WX_HEADER_IV] = iv; // 请求服务器登录地址,获得会话信息 wx.request({ url: options.loginUrl, header: header, method: options.method, data: options.data, success: function (result) { var data = result.data; // 成功地响应会话信息 if (data && data.code === 0 && data.data.skey) { var res = data.data if (res.userinfo) { Session.set(res.skey); options.success(userInfo); } else { var errorMessage = '登录失败(' + data.error + '):' + (data.message || '未知错误'); var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage); options.fail(noSessionError); } // 没有正确响应会话信息 } else { var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, JSON.stringify(data)); options.fail(noSessionError); } }, // 响应错误 fail: function (loginResponseError) { var error = new LoginError(constants.ERR_LOGIN_FAILED, '登录失败,可能是网络错误或者服务器发生异常'); options.fail(error); }, }); }); var session = Session.get(); if (session) { wx.checkSession({ success: function () { options.success(session.userInfo); }, fail: function () { Session.clear(); doLogin(); }, }); } else { doLogin(); } }; var setLoginUrl = function (loginUrl) { defaultOptions.loginUrl = loginUrl; }; module.exports = { LoginError: LoginError, login: login, setLoginUrl: setLoginUrl, getWxLoginResult: getWxLoginResult };
/utils/util.js
const formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } // 显示繁忙提示 var showBusy = text => wx.showToast({ title: text, icon: 'loading', duration: 10000 }) // 显示成功提示 var showSuccess = text => wx.showToast({ title: text, icon: 'success' }) // 显示失败提示 var showModel = (title, content) => { wx.hideToast(); wx.showModal({ title, content: JSON.stringify(content), showCancel: false }) } module.exports = { formatTime, showBusy, showSuccess, showModel }
1.4 test.wxml
<!--pages/test/test.wxml--> <view class="container"> <!-- 用户登录测试 --> <view class="userinfo" catchtap="catch_logout"> <image class="userinfo-avatar" src="{{logged ? userInfo.avatarUrl : '../index/user-unlogin.png'}}" background-size="cover"></image> <view> <text bindtap="login" id="login" data-testinfo="logintest" class="userinfo-nickname">{{logged ? userInfo.nickName : '点击登录'}}</text> <text class="userinfo-nickname" wx:if="{{logged}}">{{userInfo.lastLoginTime}}</text> </view> <button id="logout" class="{{logged ?'show':'hide'}} userinfo-logout" bindtap="logout">退出</button> </view> </view>
1.5 test.wxss
/* pages/test/test.wxss */ page { background: #F6F6F6; display: flex; flex-direction: column; justify-content: flex-start; } .userinfo, .uploader, .tunnel { margin-top: 40rpx; height: 140rpx; width: 100%; background: #FFF; border: 1px solid rgba(0, 0, 0, .1); border-left: none; border-right: none; display: flex; flex-direction: row; align-items: center; transition: all 300ms ease; } .userinfo-avatar { width: 100rpx; height: 100rpx; margin: 20rpx; border-radius: 50%; } .userinfo-nickname { font-size: 32rpx; color: #007AFF; } .hide{ display:none; } .show{ display:block; } .userinfo-logout{ font-size: 32rpx; width: 130rpx; line-height: 60rpx; }
1.6 test.js
// pages/test/test.js var login = require('../../vendor/wafer2-client-sdk/lib/login.js') var config = require('../../config.js') var util = require('../../utils/util.js') // This is our data. var helloData = { name: 'WeChat', userInfo: {}, logged: false, } Page({ /** * 页面的初始数据 */ data: helloData, changeName: function (e) { // sent data change to view this.setData({ name: 'MINA' }); }, login:function(e){ if (this.data.logged) return; console.log('begin login'); var context = this; var doLogin = () => login.getWxLoginResult(function (wxLoginError, wxLoginResult) { console.log(wxLoginError); console.log(wxLoginResult); if (wxLoginError && wxLoginError.detail) { console.log("login fail," + wxLoginError.detail.errMsg); wx.openSetting({ success: (res) => { console.log(res); } });//登录时,如果拒绝,短时间内不会弹出登录窗口,也一直登录不上,要调用此接口重新设置权限才行 return; } console.log('begin getuserinfo'); //options = utils.extend({}, defaultOptions, options); var noop = function noop() { }; var options = { method: 'post', success: noop, fail: noop, getUserInfoUrl: config.service.getUserInfoUrl, data: wxLoginResult }; //util.showBusy('正在登录') // 请求服务器登录地址,获得会话信息 wx.request({ url: options.getUserInfoUrl, //header: header, method: options.method, data: options.data, success: function (result) { var data = result.data; console.log("request result"); console.log(result); util.showSuccess('登录成功') // 成功地响应会话信息 if (data && data.errcode === '0' && data.data.skey) { context.setData({ userInfo: data.data, logged: true }); wx.setStorageSync('userInfo', data.data);//保存到本地缓存 //Session.set(res.skey); } }, // 响应错误 fail: function (loginResponseError) { //var error = new login.LoginError(constants.ERR_LOGIN_FAILED, '登录失败,可能是网络错误或者服务器发生异常'); //options.fail(error); console.log(loginResponseError); util.showModel('请求失败', loginResponseError) }, }); console.log('end getuserinfo'); }); doLogin(); console.log('end login'); }, logout:function(e){ if (this.data.logged){ this.setData({ userInfo: null, logged: false }); wx.removeStorageSync("userInfo"); } }, catch_logout:function(e){ console.log("catch_logout"); //console.log(e); }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var info = wx.getStorageSync('userInfo');//获取本地缓存 if(info&& info!=undefined){//判断登录信息有没有存在,有没有过期等逻辑 //没有过期直接登录,不需要再次登录 this.setData({ userInfo: info, logged: true }); console.log(info); console.log("exist logininfo") } else{ console.log("no logininfo"); } }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
1.7 test.json 设置小程序页面标题
{ "navigationBarTitleText": "小程序测试" }
2、小程序服务端
2.1 新建.net web api应用程序
由于是新项目,所以新建web应用程序,要简单配置下
App_Start/WebApiConfig.cs 修改默认路由配置
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
添加log4net包(版本 2.0.8)然后配置,这里不是重点。
2.2 新建控制器、登录action逻辑
在Controllers里,新建控制器 WeiXinController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web; using System.Web.Http; using ypsuit.api.WXHelper; using ypsuit.common; using ypsuit.model.WeiXinModel; namespace ypsuit.api.Controllers { public class WeiXinController : ApiController { [HttpPost] public ReturnModel<GetUserInfoOutputModel> GetUserInfo(GetUserInfoInputModel input) { Log4Logger.Info(new LogContent() { Url = HttpContext.Current.Request.RawUrl, LogIp = DataHelper.GetIP(), LogDescription = "参数:" + JsonUtility.ToJson(input) }); var temp = new WeiXinUsersHelper().GetUserData(input); return temp; } } }
ReturnModel.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ypsuit.model.WeiXinModel { public class ReturnModel { public ReturnModel() { errcode = "0"; errmsg = ""; } public string errcode { get; set; } public string errmsg { get; set; } } public class ReturnModel<T> { public ReturnModel() { errcode = "0"; errmsg = ""; } public string errcode { get; set; } public string errmsg { get; set; } public T data { get; set; } } }
GetUserInfoInputModel.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ypsuit.model.WeiXinModel { public class GetUserInfoInputModel { /// <summary> /// wx.login客户端接口能得到的信息 /// </summary> public string code { get; set; } /// <summary> /// wx.getUserInfo客户端接口能得到的信息 /// </summary> public string encryptedData { get; set; } /// <summary> /// wx.getUserInfo客户端接口能得到的信息 /// </summary> public string iv { get; set; } } }
GetUserInfoOutputModel.cs
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ypsuit.model.WeiXinModel { public class GetUserInfoOutputModel { public GetUserInfoOutputModel() { ExpTime = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd HH:mm:ss");//如果不指定,默认1天有效期 } /// <summary> /// 用户固定的openID /// </summary> [JsonIgnore] public string openid { get; set; } /// <summary> /// 用户数据解密的AesKey,每次都可能不同 /// </summary> [JsonIgnore] public string session_key { get; set; } /// <summary> /// md5(openid) /// </summary> public string skey { get; set; } /// <summary> /// 过期时间 /// </summary> public string ExpTime { get; set; } /// <summary> /// md5(skey + ExpTime),验证合法性 /// </summary> public string skeyCheckCode { get; set; } /// <summary> /// 用户昵称 /// </summary> public string nickName { get; set; } /// <summary> /// 头像url /// </summary> public string avatarUrl { get; set; } } }
WeiXinUsersHelper.cs
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Web; using ypsuit.common; using ypsuit.model.WeiXinModel; namespace ypsuit.api.WXHelper { public class WeiXinUsersHelper { #region 获取Get请求链接返回的html数据 /// <summary> /// 获取Get请求链接返回的html数据 /// </summary> /// <param name="Url">链接</param> /// <param name="type">请求编码</param> /// <returns></returns> public string GetUrltoHtml(string Url, string type = "utf-8") { try { System.Net.WebRequest wReq = System.Net.WebRequest.Create(Url); // Get the response instance. System.Net.WebResponse wResp = wReq.GetResponse(); System.IO.Stream respStream = wResp.GetResponseStream(); // Dim reader As StreamReader = New StreamReader(respStream) using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding(type))) { return reader.ReadToEnd(); } } catch (System.Exception ex) { return ex.Message; } } #endregion #region 微信小程序用户数据解密 public static string AesKey; public static string AesIV; /// <summary> /// AES解密 /// </summary> /// <param name="inputdata">输入的数据encryptedData</param> /// <param name="AesKey">key</param> /// <param name="AesIV">向量128</param> /// <returns name="result">解密后的字符串</returns> public string AESDecrypt(string inputdata) { try { AesIV = AesIV.Replace(" ", "+"); AesKey = AesKey.Replace(" ", "+"); inputdata = inputdata.Replace(" ", "+"); byte[] encryptedData = Convert.FromBase64String(inputdata); RijndaelManaged rijndaelCipher = new RijndaelManaged(); rijndaelCipher.Key = Convert.FromBase64String(AesKey); // Encoding.UTF8.GetBytes(AesKey); rijndaelCipher.IV = Convert.FromBase64String(AesIV);// Encoding.UTF8.GetBytes(AesIV); rijndaelCipher.Mode = CipherMode.CBC; rijndaelCipher.Padding = PaddingMode.PKCS7; ICryptoTransform transform = rijndaelCipher.CreateDecryptor(); byte[] plainText = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length); string result = Encoding.UTF8.GetString(plainText); return result; } catch (Exception) { return null; } } #endregion #region 通过客户端相关信息获取用户唯一标识(md5自定义后的) /// <summary> /// 通过客户端相关信息获取用户唯一标识(md5自定义后的) /// </summary> /// <param name="input"></param> /// <returns></returns> public ReturnModel<GetUserInfoOutputModel> GetUserData(GetUserInfoInputModel input) { string code = input.code; string iv = input.iv; string encryptedData = input.encryptedData; string Appid = CommonFun.GetConfigData("WeApp", "Appid"); string Secret = CommonFun.GetConfigData("WeApp", "Secret"); string grant_type = "authorization_code"; //向微信服务端 使用登录凭证 code 获取 session_key 和 openid string url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + Appid + "&secret=" + Secret + "&js_code=" + code + "&grant_type=" + grant_type; string type = "utf-8"; string j = GetUrltoHtml(url, type);//获取微信服务器返回字符串 //将字符串转换为json格式 JObject jo = (JObject)JsonConvert.DeserializeObject(j); var res = new ReturnModel<GetUserInfoOutputModel>() { data = new GetUserInfoOutputModel() }; try { //微信服务器验证成功 res.data.openid = jo["openid"].ToString(); res.data.session_key = jo["session_key"].ToString(); /* skey:作为用户客户端标识,openid太重要不能暴露在客户端. 思路就是把skey,openid,用户昵称、头像等等信息存入数据库,skey在客户端、后端接口间流转作为标识,可以用来识别、存储、查询用户信息 */ res.data.skey = EncryptHelper.MD5(res.data.openid);//作为用户客户端标识,openid太重要不能暴露在客户端 res.data.skeyCheckCode = EncryptHelper.MD5(res.data.skey + res.data.ExpTime);//用来二次验证登录是否伪造 } catch (Exception) { //微信服务器验证失败 res.errcode = jo["errcode"].ToString(); res.errmsg = jo["errmsg"].ToString(); } if (!string.IsNullOrEmpty(res.data.openid)) { //用户数据解密 AesIV = iv; AesKey = res.data.session_key; string result = AESDecrypt(encryptedData); //存储用户数据 JObject _usrInfo = (JObject)JsonConvert.DeserializeObject(result); WeiXinUserInfo userInfo = new WeiXinUserInfo(); userInfo.openId = _usrInfo["openId"].ToString(); try //部分验证返回值中没有unionId,这里是小程序关联的微信公众号ID { userInfo.unionId = _usrInfo["unionId"].ToString(); } catch (Exception) { userInfo.unionId = "unionId"; } userInfo.nickName = _usrInfo["nickName"].ToString(); userInfo.gender = _usrInfo["gender"].ToString(); userInfo.city = _usrInfo["city"].ToString(); userInfo.province = _usrInfo["province"].ToString(); userInfo.country = _usrInfo["country"].ToString(); userInfo.avatarUrl = _usrInfo["avatarUrl"].ToString(); object watermark = _usrInfo["watermark"].ToString(); object appid = _usrInfo["watermark"]["appid"].ToString(); object timestamp = _usrInfo["watermark"]["timestamp"].ToString(); res.data.nickName = userInfo.nickName; res.data.avatarUrl = userInfo.avatarUrl; Log4Logger.Info(new LogContent() { Url = HttpContext.Current.Request.RawUrl, LogIp = DataHelper.GetIP(), LogDescription = "userInfo:" + JsonUtility.ToJson(userInfo) + " res:" + JsonUtility.ToJson(res) }); //可以执行存数据库操作 //返回用户标识数据 return res; } return null; } #endregion } }
md5方法
/// <summary> /// MD5 hash加密+自定义 /// </summary> /// <param name="str"></param> /// <returns></returns> public static string MD5(string str, bool isCustomed = true) { if (str == null) { throw new ArgumentException("加密内容为NULL", "str"); } str = str.Replace(" ", ""); using (var md5Provider = new MD5CryptoServiceProvider()) { var md5 = BitConverter.ToString(md5Provider.ComputeHash(Encoding.UTF8.GetBytes(str.Trim()))); if (isCustomed)//自定义md5 { string left = "", right = ""; left += (char.IsDigit(md5, 6) ? md5.Substring(5, 1) : md5.Substring(7, 1)); left += (char.IsDigit(md5, 13) ? md5.Substring(12, 1) : md5.Substring(14, 1)); right += (char.IsDigit(md5, 20) ? md5.Substring(19, 1) : md5.Substring(21, 1)); right += (char.IsDigit(md5, 27) ? md5.Substring(26, 1) : md5.Substring(28, 1)); string middle = (char.IsDigit(md5, 14) ? md5.Substring(13, 1) : md5.Substring(15, 1)); md5 = (left + md5 + right).Insert(17, middle); } return md5; } }
2.3 读取配置文件
GetConfigData相关方法
#region 文件 /Uload/Data.config private static string _DataConfigFile = string.Empty; /// <summary> /// 文件/Uload/Data.config /// </summary> public static string DataConfigFile { get { if (_DataConfigFile == string.Empty) _DataConfigFile = DataHelper.GetAbsolutePath("/Upload/Data.config"); return _DataConfigFile; } } #endregion #region 通过相对路径获取文件夹绝对路径 /// <summary> /// 通过相对路径获取文件夹绝对路径 /// </summary> /// <param name="relativePath"></param> /// <returns></returns> public static string GetAbsolutePath(string relativePath) { string AbsolutePath = ""; if (HttpContext.Current != null) { AbsolutePath = HttpContext.Current.Server.MapPath(relativePath); } else { //多线程执行这里 relativePath = relativePath.Replace("/", "\\"); if (relativePath.StartsWith("\\"))//确定 String 实例的开头是否与指定的字符串匹配。为下边的合并字符串做准备 { relativePath = relativePath.TrimStart('\\');//从此实例的开始位置移除数组中指定的一组字符的所有匹配项。为下边的合并字符串做准备 } AbsolutePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath); } return AbsolutePath; } #endregion #region 获取/Upload/Data.config中的配置信息 /// <summary> /// 获取/Upload/Data.config中的配置信息 /// </summary> /// <param name="nodeName">Root下的节点名称</param> /// <param name="attrName">要获取的节点的属性名称 /// 如果为""则获取节点的InnerText /// 否则获取节点的Attribute /// </param> /// <returns></returns> public static string GetConfigData(string nodeName, string attrName = "") { return GetConfigSettings(DataCache.DataConfigFile, nodeName, attrName); } #endregion #region 获取其他config文件中的节点信息 /// <summary> /// 获取其他config文件中的节点信息 /// </summary> /// <param name="path">文件路径</param> /// <param name="NodeName">Root下的节点名称</param> /// <param name="AttrName">要获取的节点的属性名称 /// 如果为""则获取节点的InnerText /// 否则获取节点的Attribute /// <returns></returns> private static string GetConfigSettings(string path, string nodeName, string attrName = "") { string Ret = ""; XmlDocument doc = null; object obj_doc = DataCache.Instance.Get<object>(DataCacheKey.DataConfigFile.ToString(), DataCache.DataConfigFile);//依赖文件的缓存机制,测试时,可以不要 if (obj_doc == null) { if (File.Exists(path)) { doc = new XmlDocument(); doc.Load(path); DataCache.Instance.Set<object>(DataCacheKey.DataConfigFile.ToString(), doc, DataCache.DataConfigFile); } } else { doc = (XmlDocument)obj_doc; } if (doc != null) { XmlElement Node = (XmlElement)doc.SelectSingleNode("/Root/" + nodeName); if (Node != null) { if (attrName != "") Ret = Node.GetAttribute(attrName); else Ret = Node.InnerText; } } return Ret; } #endregion
Upload/Data.config
<?xml version="1.0" encoding="utf-8"?> <Root> <Log4net ISTEXT="1"></Log4net> <WeApp Appid="wxd16eafbd0ebdXXXX" Secret="44718d44XXXX1ebdabc0b60844e8XXXX"></WeApp> </Root>
3、效果图
登录日志
四:小结
这里实现的是自定义登录,并且登录有效期由自己管理,小程序官方还提供一个API,登录有效期由微信维护,具体可以看接口文档:
wx.checkSession(OBJECT):https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html#wxchecksessionobject
通过上述接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。开发者只需要调用wx.checkSession接口检测当前用户登录态是否有效。登录态过期后开发者可以再调用wx.login获取新的用户登录态。
可以使用自己自定义的方法结合官方过期机制,来处理登录有效期问题,要不要这样做主要是看个人需求。
评论列表: