likes
comments
collection
share

koa+uniapp 微信小程序登录流程实现

作者站长头像
站长
· 阅读数 16

koa+uniapp 微信小程序登录流程实现



前言

在做个人商城项目时碰到授权登录小程序时碰到小程序和其他平台账号账号同步问题,记录一下。 个人开发者无法申请微信认证,不能获取用户手机号,流程略有改动


提示:以下是本篇文章正文内容,下面案例可供参考

流程

1、获取小程序appid以及appSecret(小程序密钥)

登录微信公众平台,进入开发管理 =》开发设置,获取小程序appid以及appSecret(小程序密钥) koa+uniapp 微信小程序登录流程实现

2、前端调用uni.getUserProfile和uni.login

前端调用uni.getUserProfile出现授权对话框,这里推荐使用uni.getUserProfile, 微信基础库2.10.4版本对用户信息相关接口进行了调整,使用 uni.getUserInfo 获取得到的 userInfo 为匿名数据,建议使用 uni.getUserProfile 获取用户信息。详情见小程序与小游戏获取用户信息接口调整 把接口分为两种情况: 第一种数据库已有openid,匹配成功后直接登录返回token 第二种数据库未存在openid,则前端需要输入绑定手机号后再次调用接口

          <u-button type="success" text="微信用户一键登录" @click="wxLogin" />
          <!-- 弹出层 -->
          <u-popup
		    :show="loginPopup.visible"
			mode="center"
			closeable
			:closeOnClickOverlay="false"
			round="10rpx"
			@close="closeLoginPopup"
		  >
          <view class="loginPopup">
           <view class="flexCenter fs16">首次微信登录,请绑定手机号</view>
		   <view>
			  <u--input placeholder="请输入手机号" border="surround" v-model="loginPopup.phone" />
			</view>
		    <view>
			  <u-button type="success" text="授权登录" @click="getUserProfile" />
		    </view>
        </view>
		</u-popup>
		
		--------------js代码-----------------------
			// 输入手机号后获取用户信息再次一并发送到后端
			// 获取用户信息。每次请求都会弹出授权窗口,用户同意后返回 userInfo
			getUserProfile() {
				if (!isPhone(this.loginPopup.phone)) {
					this.showToast('请输入正确的手机号')
					return
				}
				uni.getUserProfile({
					desc: '用户登录',
					lang: 'zh_CN',
					success: ({ errMsg, encryptedData, iv }) => {
						if (errMsg === 'getUserProfile:ok') {
							this.wxLogin({ encryptedData, iv, phone: this.loginPopup.phone })
						}
					}
				})
			},
			// 调用登录api,把获取的code调用接口上传至服务器
			async wxLogin({ encryptedData, iv, phone }) {
				const code = await this.$store.dispatch('user/wxLogin', { encryptedData, iv, phone })
				if (code === 0) { // 登录成功
					this.showSuccess('登陆成功')
					this.$store.dispatch('user/GetInfo') // 获取用户信息
					uni.$emit('createWebSocket') // 开启通信
					uni.switchTab({
						url: "/pages/index/index"
					})
				} else { // 首次微信登录需绑定手机号,个人开发者无法申请获取手机号api
				  this.loginPopup.visible = true
				}
			},
			-----------------vuex代码----------------
		// 微信登录
		wxLogin({ commit }, { encryptedData, iv, phone }) {
			return new Promise(async(resolve) => {
				uni.login({
					provider: 'weixin',
					success: async ({ code, errMsg }) => {
						if (errMsg === 'login:ok') {
							// 调用接口传入code,返回token
							const { data: token } = await Vue.prototype.$user.userWxLogin({	
								code,
								encryptedData: encryptedData ? encryptedData : undefined,
								iv: iv ? iv : undefined,
								phone: phone ? phone : undefined
							})
							if (token) { // 返回了token
								resolve(0)
								commit('SET_TOKEN', token)
							} else { // 未返回
								resolve(-1)
							}
						}
					}
				})
			})
		}
			

3、后端koa接收参数,换取openid、session_key

后端接收参数后通过api.weixin.qq.com/sns/jscode2…

拿到openid后进行数据库查询,如果已经存在,则生成token返回,登陆成功 如果不存在则返回空值给前端,再给用户跳转新的操作填写手机号,进行首次登陆绑定后再次提交接口

后端不对session_key进行操作,由前端调用uni.checkSession检查登录态是否过期。过期后再调用uni.login方法登陆,重新生成token

// 微信授权登录
exports.wxLogin = async(ctx) => {
  const jwt = require('jsonwebtoken')
  const { code, iv, encryptedData, phone } = ctx.request.body
  console.log(ctx.request.body, 'ctx.request.body')
  if (!code) {
    ctx.fail('参数错误', -1)
    return
  }
  // 换取微信 openid(用户唯一标识) 和 session_key(会话密钥)
  function getOpenId() {
    return new Promise(async(resolve) => {
      const appid = 'wxxxxxxxxx' // 小程序appId
      const secret = 'xxxxxxxxxxxxxxxxxxxxx' // 小程序密钥
      const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`
      const httpRequest = require('koa2-request')
      const res = await httpRequest(url) // http请求
      const { session_key, openid } = JSON.parse(res.body)
      resolve({ session_key, openid })
    })
  }
  // 校验是否为第一次登录(只传了code)
  function verifyLogin() {
    return new Promise(async(resolve) => {
      const { session_key, openid } = await getOpenId() // 获取appid
      // 数据库查询
      const userInfo = await User.findOne({ where: { openid }})
      if (userInfo) { // 用户存在返回token
        const token = jwt.sign({
          id: userInfo.userId,
          phone: userInfo.phone,
          openid: userInfo.openid
        }, 'user_token', { expiresIn: process.env.NODE_ENV != 'development' ? '7d' : '365d' }) // 线下环境365天
        resolve(token)
      } else { // 用户不存在返回空
        resolve(false)
      }
    })
  }
   // 传参齐全 为首次登陆绑定手机号信息使用
  function bindLogin() {
    return new Promise(async(resolve) => {
      const { session_key, openid } = await getOpenId() // 获取appid
      // 查找数据库中是否有该手机号
      const userInfo = await User.findOne({ where: { phone }})
      console.log(userInfo, 'userInfo')
      // 数据库中存在手机号直接把用户appid进行绑定
      if (userInfo) {
        const res = await User.update({ openid }, { where: { phone }})
        if (res[0] === 1) { // 成功,返回token
          const token = jwt.sign({
            id: userInfo.userId,
            phone: userInfo.phone,
            openid: userInfo.openid
          }, 'user_token', { expiresIn: process.env.NODE_ENV != 'development' ? '7d' : '365d' }) // 线下环境365天
          resolve(token)
        }
      } else { // 数据库未存在该账号则创建账号
        const appid = 'wxxxxxxxxxxxxxx' // 小程序appId
        const WXBizDataCrypt = require('../util/WXBizDataCrypt') // 引入微信加密数据解密算法https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#method-decode
        const pc = new WXBizDataCrypt(appid, session_key)
        const data = pc.decryptData(encryptedData, iv) // 获取解密后的用户信息

        // 数据库创建账号
        const res = await User.create({ 
          phone: phone, 
          openid: openid,
          userName: data.nickName,
          avatar: data.avatarUrl,
          sex: data.gender === 1 ? 'M' : data.gender === 2 ? 'W' : 'M' // 性别
        })
        // 创建账号 后返回token
        if (res) {
          const token = jwt.sign({
            id: res.userId,
            phone: res.phone,
            openid: res.openid
          }, 'user_token', { expiresIn: process.env.NODE_ENV != 'development' ? '7d' : '365d' }) // 线下环境365天
          resolve(token)
        }
      }
    })
  }

  let res
  // 传参齐全 为首次登陆绑定手机号信息使用
  if (code && iv && encryptedData && phone) {
    res = await bindLogin()
  } else { // 校验是否为第一次登录(只传了code)
    res = await verifyLogin()
  }
  if (res !== -1) {
    ctx.success(res, '成功')
  } 
}

4、前端检查登录是否过期

前端可以在接口请求拦截器中设置时间,当用户操作超出一定时间检查是否登录

总结

以上就是今天要讲的内容,不足之处请指出