微信小程序登录功能的实现以及坑点 —— 前端实现

5,517 阅读4分钟

前两天发了一篇关于微信小程序项目开发前准备的文章,没什么人看。辛辛苦苦码了三千字,我太难了。也罢,自己达到复盘的效果就好

刚兴趣的朋友可以看一下:微信小程序开发那些事 —— 项目前期准备篇

废话少讲,干!就完事。

小程序中,同一个用户只有一个 openid ,且不变的。所以往数据库里添加一个用户,要带上openId,这个就是用户的唯一标识。至于怎么把用户的昵称、头像储存下来,后端弄一个更新用户信息的API就好了。

大致登录流程:

  • 前端调用 wx.login 获取 code
  • 调用登录接口,把 code 传给后端,后端用 appid + app_secret + code 取到 openid 和 session_key ,并根据 openid 创建一个用户并生成一个 token ,把用户信息和token,一并返回给前端。前端存储起来
  • 前端判断用户名、手机是否为空,接着调用更新用户信息的接口 ,后端把信息存入数据库

文档:小程序登录 | 微信开放文档

先看一下前端实现的流程图。

微信登录流程

代码实现:

首先把处理登录逻辑都放在authLogin.js,接着自定义一个授权登录的组件——authorize 方便多次调用。

注意:文中调用的接口,需要后端实现。根据实际情况进行调整

authLogin.js —— 处理登录逻辑

  import {
    CACHE_USERINFO,
    CACHE_TOKEN,
    APP_ID,
    CACHE_CODE,
    CACHE_CODE_TIME,
    CACHE_TOKEN_TIME,
    CODE_EFFECTIVE_TIME,
    TOKEN_EFFECTIVE_TIME,
  } from "../config"

  const {
    login
  } = require("../api/user");

  export async function authLogin(instance) {
    const cacheUserInfo = wx.getStorageSync(CACHE_USERINFO)
    let userInfo = cacheUserInfo ? JSON.parse(cacheUserInfo) : {}
    getApp().globalData.userInfo = userInfo

    //有token时,不需要重新登录
    const token = wx.getStorageSync(CACHE_TOKEN)
    const tokenTime = wx.getStorageSync(CACHE_TOKEN_TIME)
    if (token && (tokenTime + TOKEN_EFFECTIVE_TIME > (new Date()).getTime())) {
      return authMain(instance, userInfo)
    }
    // 防止第一次经入小程序时,反复调用 wx.login,超出频率规范
    const code = wx.getStorageSync(CACHE_CODE)
    const codeTime = wx.getStorageSync(CACHE_CODE_TIME)
    if (code && (codeTime + CODE_EFFECTIVE_TIME > (new Date()).getTime())) {
      const loginRes = await loginMain(res.code)
      return authMain(instance, loginRes.result)
    }
    wx.login({
      success: async (res) => {
        if (res.code) {
          const loginRes = await loginMain(res.code)
          authMain(instance, loginRes.result)
        } else {
          console.log('登录失败!' + res.errMsg)
        }
      }
    })
  }
  //调用 token 和 储存用户token等信息
  async function loginMain(code) {
    const loginRes = await login(APP_ID, code)
    wx.setStorageSync(CACHE_TOKEN, loginRes.result.token)
    wx.setStorageSync(CACHE_TOKEN_TIME, (new Date()).getTime())
    wx.setStorageSync(CACHE_CODE, code)
    wx.setStorageSync(CACHE_CODE_TIME, (new Date()).getTime())
    wx.setStorageSync(CACHE_USERINFO, JSON.stringify(loginRes.result))
    const globalData = getApp().globalData
    globalData.userInfo = loginRes.result || {}
    return loginRes
  }

  async function authMain(instance, loginRes) {
    //判断用户名是否为空,弹出授权窗口
    if (!loginRes.wxNickname) {
      instance.setData({
        authType: "UserInfo",
        isShowAuth: true
      })
      return
    }
    //判断用户名是否为空,弹出授权窗口
    if (!loginRes.wxMobile) {
      instance.setData({
        authType: "PhoneNumber",
        isShowAuth: true,
      })
      return
    }
    //用户名和手机号码都不为空证明已经授权过了
    //这里可以调用首页需要的 api等等信息
 }

authorize 组件—— 用户信息授权、手机用户授权的自定义组件

// components/authorize/index.js
import {
  CACHE_USERINFO,
} from "../../config"
import {
  updateUserInfo,
  updatePhone
} from "../../api/user"
Component({
  properties: {
    type: {
      type: String,
      value: "UserInfo"
    },
    showAuth: {
      type: Boolean,
      value: false
    },
  },

  data: {
    loading: false,
  },
  /**
   * 组件的方法列表
   */
  lifetimes: {},
  methods: {
    getUserProfile() {
      wx.getUserProfile({
        lang: 'zh_CN',
        desc: "微信授权登录",
        success: async (res) => {
          //传给后端的参数
          let param = {
            encryptedData: {
              encryptedData: res.encryptedData,
              iv: res.iv
            },
            rawData: res.rawData,
            signature: res.signature,
            userInfo: res.userInfo
          }
          //调用更新接口
          let updateRes = await this.update(updateUserInfo, param)
          if (!updateRes) return
          // 弹出手机号码授权窗口
          this.setData({
            showAuth: "PhoneNumber",
            type: true,
          });
        },
        fail(res) {
          console.log(res)
        }
      })
    },
    async getPhoneNumber(e) {
      if (e.detail.errMsg === "getPhoneNumber:ok") {
        //传给后端的参数
        const param = {
          encryptedData: {
            encryptedData: e.detail.encryptedData,
            iv: e.detail.iv
          },
        }
        //调用接口
        let updateRes = await this.update(updatePhone, param)
        if (!updateRes) return
        //关闭隐藏接口
        this.hide()
      }
    },
    onCancel() {
      this.hide()
    },
    hide() {
      this.setData({
        showAuth: false
      })
    },
    //更新用户信息
    async update(updateApi, param) {
      this.setData({
        loading: true
      })
      let updateRes = await updateApi(param)
      this.setData({
        loading: false
      })
      if (updateRes.code !== 200) {
        wx.showToast({
          icon: "error",
          title: "更新失败",
        })
        return false
      }
      //更新本地保存 userInfo 数据
      getApp().globalData.userInfo = updateRes.result || {}
      wx.setStorageSync(CACHE_USERINFO, JSON.stringify(updateRes.result))
      return updateRes
    }
  }
})
<!-- components/authorize/index.wxml -->
<van-dialog use-slot show="{{ showAuth }}" showConfirmButton="{{false}}" zIndex="99999">
  <view class="tips-content flex_c_hv">
    <van-button plain icon="wechat" type="primary" custom-class="auth-btn" loading="{{loading}}"
      bind:click="getUserProfile" wx:if="{{type==='UserInfo'}}">
      微信授权登录
    </van-button>
    <van-button plain icon="phone-o" type="info" custom-class="auth-btn" loading="{{loading}}"
      open-type="getPhoneNumber" bind:getphonenumber="getPhoneNumber" wx:else>获取手机号进行绑定
    </van-button>
    <van-button plain custom-class="auth-btn" bind:click="onCancel">取消
    </van-button>
  </view>
  <view class="tips">用户信息仅用于同步售后产品信息</view>
</van-dialog>

组件和处理逻辑都准备好之后,就是怎么使用了。你需要在某个页面使用,就把它们引入就好

//index.js
import { authLogin } from "../../utils/authLogin.js"
Page({
    data:{
     authType: "getPhoneNumber", // "UserInfo" 为用户基本信息授权、"getPhoneNumber" 为手机号码授权
     showAuth: true
    },
    onLond(){
       //在需要登录校验的地方调用 authLogin
       authLogin(this)
    },
    auth(){
      authLogin(this)
    }
})

//index.json
 "usingComponents": 
   "authorize": "/components/authorize/index"
 }
 
//index.wxml
<van-button bind:click="auth">点击授权登录</van-button>
<authorize type="{{authType}}" showAuth="{{showAuth}}" />

坑点一:个人小程序无法获取手机号码,且需要认证的小程序才能获取

坑点二:小程序 wx.login 、wx.getUserProfile等接口,存在调用频率的限制。使用时,需要注意。相关文档:调用频率规范

坑点三: 2021年4月28日起 无法通过wx.getUserInfo直接获取到用户个人信息,可以使用getUserProfile代替。相关文档:微信接口调整

坑点四:授权登录的窗口必须要可以关闭,不能强制用户授权。否则无法过审

坑点五:微信小程序是共用一套缓存的,也就说开发版、体验版、正式版用的缓存一样,需要注意正式环境的接口Token和测试环境Token不通用问题。

代码demo:wx_template