"个人中心"小程序:用户登录

来自CloudWiki
跳转至: 导航搜索

案例分析

用户登录是小程序必不可少的环节。

一个完整的登录功能包括用户的信息获取、登录的状态判定等。

前导知识

用户登录流程

三个角色:

  • 小程序(客户端)
  • 开发者服务器
  • 微信接口服务

登录流程:

  • 小程序获取code
  • 小程序将code发送给开发者服务器
  • 开发者服务器通过微信接口服务校验登录凭证
需要注意的是,openid并不等同于微信用户id ,同一个微信用户在不同Appid小程序中的openid是不同的。
  • 开发者服务器自定义登录态
开发者服务器生成一个自定义token(令牌)给小程序,通过token可以查询到openid和session_key,下次再请求只要携带token就行。

数据缓存

在小程序中可以缓存一些数据,从而在小程序退出后再次打开时,可以从缓存中读取上次保存的数据。

小程序:缓存的增删查改与清除

搭建开发者服务器

本节使用Node.js搭建开发者服务器,

如果还没有安装node.js的可以参考 Windows 安装Node.js

安装成功后,创建一个空目录作为项目目录,然后打开命令提示符切换到该目录

1)初始化项目,将会自动创建package.json配置文件

npm init -y

2)安装Express框架和request模块

cnpm install express --save
cnpm install request --save

3) 安装nodemon监控文件修改(如果已经安装则跳过此步)

cnpm install nodemon -g

4)执行上述命令后,在项目目录下创建index.js文件:

// 加密解密
//const crypto = require('crypto');
//const WXBizDataCrypt = require('./WXBizDataCrypt')

const express = require('express')
const bodyParser = require('body-parser')
const request = require('request')
const app = express()
app.use(bodyParser.json())

const wx = {
    appid: '',//需要填写开发者的AppId
    secret: ''//需要填写开发者的AppSecret
}

var db = {
    session: {},
    user: {}
}

app.post('/login', (req, res) => {
    // 注意:小程序端的appid必须使用真实账号,如果使用测试账号,会出现login code错误
    console.log('login code: ' + req.body.code)
    var url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + wx.appid + '&secret=' + wx.secret + '&js_code=' + req.body.code + '&grant_type=authorization_code'
    request(url, (err, response, body) => {
        console.log('session: ' + body)
        var session = JSON.parse(body)
        if(session.openid) {
            var token = 'token_' + new Date().getTime()
            db.session[token] = session
            if(!db.user[session.openid]) {
                db.user[session.openid] = {
                    credit: 100
                }
            }
        }
        res.json({
            token: token
        })
    })
})

app.listen(3000, () => {
    console.log('server running at http://127.0.0.1:3000')
})

  • 第17-19行是校验登录凭证的微信接口URL地址,在接口参数中 js_code来自小程序提交的code
  • 第10-13行用于模拟数据库保存数据
  • 第24行用于生成token
  • 第25行用于保存openid和session_key,保存后,第31行将token响应给小程序。
  • 第26~29行在db.user中保存了用户记录,相当于在服务器中注册了一个新用户,每个新用户保存100积分

保存上述代码后,执行如下命令、启动开发者服务器:

nodemon index.js

[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
server running at http://127.0.0.1:3000

实现用户登录

在微信开发者工具中创建一个空白项目,

新建app.js和app.json文件以及pages/index/index页面

然后在app.js中编写如下代码,实现小程序启动时自动登录。

App({
  onLaunch: function () {   
      this.login()     
   
  },
  login: function () {
    wx.login({
      success: res => {
        console.log('login code: ' + res.code)
        wx.request({
          url: 'http://127.0.0.1:3000/login',
          method: 'post',
          data: { code: res.code },
          success: res => {
            console.log('token: ' + res.data.token)
            // 将token保存为公共数据(用于在多页面中访问)
            this.globalData.token = res.data.token
            // 将token保存到数据缓存(下次打开小程序无需重新获取token)
            wx.setStorage({ key: 'token', data: res.data.token })
          }
        })
      }
    })
  },
  
  globalData: {
    userInfo: null,
    token: null	// 保存token
  }
})

上述代码执行后,会在控制台中看到login code和token的值:

login code: 021HGy0w3M9zXW2E3p0w3d7ske4HGy0F
token: token_1629600794764

在开发者服务器的控制台中,可以看到login code 和session的值。

server running at http://127.0.0.1:3000
login code: 021HGy0w3M9zXW2E3p0w3d7ske4HGy0F
session: {"session_key":"354jtFe9AMkjPFUxhclH8A==","openid":"olaWZ5QrlbsK75dsU6DibtNzIQwE"}

可以看到token的值,说明小程序已经能收到服务器返回的token

测试

为了测试小程序能否使用token获取用户信息,

在服务器端index.js文件的app.listen()方法前面编写如下代码:

 
pp.get('/credit', (req, res) => {
    var session = db.session[req.query.token] //凭借token获取信息
    if(session && db.user[session.openid]) {
        res.json({
            credit: db.user[session.openid].credit
        })
    } else {
        res.json({
            err: '用户不存在,或未登录。'
        })
    }
})

在pages/index/index.wxml文件中编写"获取用户的积分"按钮,

 <button bindtap="credit">获取用户积分</button>

在pages/index/index.js文件中编写credit()函数,

 
credit: function () {
    wx.request({
      url: 'http://127.0.0.1:3000/credit',
      data: { token: app.globalData.token },
      success: res => {
        console.log(res.data)
      }
    })
  },

运行小程序,会从控制台中看到服务器返回的积分:

Wexin21082201.png

检查用户是否已经登录

上一节已经实现了用户登录,

下次启动小程序时,应该判断数据缓存中是否存在token,如果存在,直接取出这个token即可,不用再执行登录操作。

如果token过期,需要重新登录。

服务端

首先,在服务器端index.js文件中的app.listen()方法前面添加代码,检查token是否有效。

 
app.get('/checklogin', (req, res) => {
    var session = db.session[req.query.token]
    console.log('checklogin: ', session)
    // 将用户是否已经登录的布尔值返回给客户端
    res.json({
        is_login: session !== undefined
    })
})

上述代码中,第5行判断根据token取出的session是否为undfined,如果是,说明token已经过期,如果不是,说明token有效。

小程序端

然后在app.js中编写checkLogin()函数,用于判断token是否存在,如果token存在,则请求服务器,判断token是否有效。

 
  checkLogin: function (callback) {
      var token = this.globalData.token //先看看全局变量中有没有token
      if (!token) {
        // 如果没有,从数据缓存中获取token
        token = wx.getStorageSync('token')
        if (token) {
          this.globalData.token = token
        } else {
          callback({ is_login: false })
          return
        }
      }
      wx.request({
        url: 'http://127.0.0.1:3000/checklogin',
        data: { token: token },
        success: res => {
          callback({ is_login: res.data.is_login })
        }
      })
  },

接下来修改app.js中的onLaunch()函数的代码,用于在启动时检查用户是否已登录

如果没有登录,则执行登录操作。

 onLaunch: function () {
    this.checkLogin(res => {
      console.log('is_login: ', res.is_login)
      if (!res.is_login) {
        this.login()
      }
    })
  },

保存上述代码,运行程序,可以看到在token失效的情况下,小程序进行了重新登录。

is_login:  false
app.js? [sm]:13 login code: 051M6A100ClZiM1sK1000uIr3w3M6A1J
app.js? [sm]:19 token: token_1629709485623

获取用户信息

小程序中,获取用户信息有两种常用的方式,一种是<open-data>组件,不需要用户授权,就可以显示用户头像、昵称、性别等,适合只用来展示的情况。另一种方式是通过单机按钮,提示用户授权,获取到用户信息数据。

方法一:open-data组件

pages/index/index.wxml:

<!-- 用户头像 -->
<open-data type="userAvatarUrl"></open-data>
<!-- 用户昵称 -->
<open-data type="userNickName"></open-data>
<!-- 用户性别 -->
<open-data type="userGender" lang="zh_CN"></open-data>

方法二:单击按钮授权

接下来将代码改为单击按钮授权的方式,

<!--index.wxml-->
<view class="container">
  <view class="userinfo">
    <block wx:if="{{canIUseOpenData}}">
      <view class="userinfo-avatar" bindtap="bindViewTap">
        <open-data type="userAvatarUrl"></open-data>
      </view>
      <open-data type="userNickName"></open-data>
    </block>
    <block wx:elif="{{!hasUserInfo}}">
      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
      <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
      <view wx:else> 请使用1.4.4及以上版本基础库 </view>
    </block>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
     
      <!-- 用户昵称 -->
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>

    </block>
  </view>

</view>


这一版本中,如果没有用户信息,需用户主动点击按钮才会显示。

相应地,在index.js文件中增加data数据和getUserInfo()函数

 
// index.js
// 获取应用实例
const app = getApp()

Page({
  data: {
    motto: 'Hello World',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    canIUseGetUserProfile: false,
    canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') 
    && wx.canIUse('open-data.type.userNickName')  // 如需尝试获取用户信息可改为false
  },
  // 事件处理函数
  bindViewTap() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },
  onLoad() {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
  },
  getUserProfile(e) {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        console.log(res)
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
  },
  getUserInfo(e) {
    // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
    console.log(e)
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  }
})

  }

在pages/index/index.wxss文件中编写页面样式:

/**index.wxss**/
.userinfo {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: #aaa;
}

.userinfo-avatar {
  overflow: hidden;
  width: 128rpx;
  height: 128rpx;
  margin: 20rpx;
  border-radius: 50%;
}


Wexin21082401.png