前端微信扫码登录

682 阅读5分钟

(1)扫码直接登录

步骤一:用户进入登录页,在登录页选择微信登陆,然后就会在内嵌二维码容器中显示出二维码。

步骤二:用户用微信扫码,当授权成功后,就会自动重定向到中转页。其中,中转页可以是注册页,也可以是当前登录页。

步骤三:前端在中转页获取当前路由对象的query信息,并通过请求接口将query信息中的code参数传给后端。

步骤四:后端就会通过code参数获取到access_token、openid,进而获取用户信息,当前端拿到这些参数后就会登录成功,跳转到首页,并保存到本地存储中。

1.加载wxLogin.js

// index.html
  <!-- 加载微信登录 -->
  <script>
    let script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = document.location.protocol + '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
    document.getElementsByTagName('head')[0].appendChild(script)
  </script>

2.封装微信登录组件wxWebsite

<template>
  <div id="wxcode" class="wxcode"></div>
  <div style="text-align: center">微信扫码登录</div>
</template>
<script setup>
const props = defineProps({
  redirectUri: {
    type: String,
    default: 'https://xxx/xxx/#/home'
  }
})
onMounted(() => {
  sessionStorage.setItem('loginStatus', '1')
  new WxLogin({
    id: 'wxcode', //wx组建元素
    appid: 'xxxxxxxxx', //微信平台开放id
    scope: 'snsapi_login',
    redirect_uri: encodeURIComponent(props.redirectUri), //回调地址 encodeURIComponent编码
    state: '',
    style: 'black', //黑白样式
    href: 'data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDE4MHB4O21hcmlnbi10b3A6LThweH0KLmltcG93ZXJCb3ggLnRpdGxlIHtkaXNwbGF5OiBub25lO30KLmltcG93ZXJCb3ggLmluZm8ge2Rpc3BsYXk6IG5vbmU7fQ==' //通过href base64加密css可以微调样式
  })
})
</script>
<style scoped>
.wxcode {
  margin-top: -20px;
  height: 200px;
  overflow: hidden;
  display: flex;
  justify-content: center;
}
</style>

3.页面中使用

redirect_uri的回调地址页面中

watch(
  () => route.query.code,
  () => {
    if (route.query.code && sessionStorage.getItem('loginStatus') == 1) {
      getWxOpenUser({ code: route.query.code })
        .then(res => {
          logInfo.wxUnionId = res.unionId
          logInfo.openid = res.openid
          logInfo.loginType = 'wx'
          sessionStorage.setItem('loginStatus', '2')
          handleLogin()
        })
        .catch(() => {
          sessionStorage.setItem('loginStatus', '0')
        })
    }
  }
)

const handleLogin = () => {
  let data = {}
  if (logInfo.loginType === 'wx') {
    data = logInfo.wxUnionId
  } else {
    let password = window.btoa(logInfo.password)
    data = { userName: logInfo.userName, password }
  }

  userLogin(data, logInfo.loginType)
    .then(() => {
      dialogVisible.value = false
      ElNotification({
        title: 'xxxxx',
        message: `欢迎登录 `,
        type: 'success'
      })
      //清空Code参数
      logInfo.loginType = 'account'
      router.replace({ query: {} })
    })
    .catch(() => {
      if (logInfo.loginType === 'wx') {
      // 跳转微信绑定页面
        router.push('/verifyBind?openId=' + logInfo.openid + '&wxUnionId=' + logInfo.wxUnionId) 
        return
      } else {
        verifyRef.value.closeVerify()
      }
    })
}


    userLogin(formData: any, flag: string) {
      return new Promise(async (resolve, reject) => {
        try {
          let res
          if (flag == 'wx') {
            res = await wxLogin(formData) // wxLogin后台登录接口
          } else {
            res = await login(formData)
          }
          //@ts-ignore
          const { access_token, userName, userId, realName } = res
          this.setInfo({ access_token, userName, userId, realName })
          resolve(true)
        } catch (error) {
          reject()
        }
      })
    },

(2)扫码关注公众号登录(由后端生成带参二维码)

核心原理:带参二维码,EasyWeChat二维码文档

接入微信扫码登录的准备工作

接入微信主要就是要让业务服务器可以和微信服务器能够正常通信,但是微信服务器并不是随便可以通信的,需要进行配置,这样微信服务器才能认为和你的业务服务器通信是安全合法的。

通信主要有两个方面:一个是业务服务器请求微信服务器,另一个是微信服务器向业务服务器发送事件。

前者需要配置ip白名单,只有在白名单中的机器才会被微信服务器接收,否则都被认为是非法请求而拒绝。后者需要配置你的服务器地址,并进行验证,这样当有和你的公众号相关的事件发生,就会通过这个配置的服务器地址进行推送。

更具体地,需要做的准备工作有:

  1. 权限:认证的服务号,订阅号和未认证的服务号都没有获取带参二维码接口的权限,因此无法实现扫码登录。
  2. 开通微信公众号开发者(在微信公众号后台的基本配置中开启)
  3. 配置公众号服务地址并验证(在微信公众号后台的基本配置中配置并验证),这个地址也是后面接收扫码事件的。
  4. 配置公众号的ip白名单(serverless实例运行时候ip不固定,可以通过代理方式保证固定ip(例如阿里云文档给出的解决方案:help.aliyun.com/document_de…),也可以把微信后端接口放在云服务器上)。
  5. 实现access_token刷新模块,调用微信的任何接口都需要access_token:获取access_token
具体步骤

扫描二维码,如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值(自定义值)关注事件推送给开发者。

扫描二维码,如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值(自定义值)扫码事件推送给开发者

  • 后端生成二维码的时候将前后端约定好的参数到添加到二维码中。
  • 前端调用后端接口获取二维码地址与前后端约定好的参数
  • 前端页面根据这个参数轮询用户登录状态(也可使用 socket)。
  • 用户扫码关注后会推送一个关注事件到服务端,也会把自定义参数带入到事件中。
  • 后端根据 openid 创建用户后,然后在 Redis 中存储 Key 为场景值(自定义参数) Value 为用户创建后的 id。
  • 前端轮询方法中如果在 Redis 中获取到 Id 后,Auth 登陆,页面再重载一下,流程完毕。

封装微信登录组件WxPublic

<template>
  <div class="wx_box">
    <div class="wx_img">
      <img :src="wxQrCode" alt="" />
    </div>
    <div class="cover" @click="getWXQrCode" v-if="showCover">
      <el-icon size="50"><RefreshRight /></el-icon>
      <div class="mt-10">二维码已失效,刷新</div>
    </div>
    <div class="wx_text">
      <div>微信扫码登录</div>
      <el-icon @click="getWXQrCode" size="16" class="icon"><RefreshRight /> </el-icon>
    </div>
  </div>
</template>
<script setup>
import { getQrCode, getUserBySceneId } from '@/api/wx'
onMounted(() => {
  getWXQrCode()
})
onUnmounted(() => {
  //停止查询扫码状态
  resetTimer()
})

const resetTimer = () => {
  //停止查询扫码状态
  if (qrTimer.value) clearInterval(qrTimer.value)
}
//暴露子组件方法供父组件调用
defineExpose({
  resetTimer
})
let wxQrCode = ref('')
let wxSceneId = ref('')
let qrTimer = ref('')
let showCover = ref(false)
let countdown = ref(150)
let countdownTimer = ref('')
//二维码失效
const countdownFc = () => {
  if (countdown.value === 0) {
    showCover.value = true
    //停止查询扫码状态
    resetTimer()
    return false
  } else {
    countdown.value--
  }
  //设置一个定时器,每秒执行一次
  countdownTimer.value = setTimeout(() => {
    countdownFc()
  }, 1000)
}
//调用后端接口获取二维码
const getWXQrCode = () => {
  showCover.value = false
  //停止查询扫码状态
  resetTimer()
  getQrCode().then(res => {
    wxQrCode.value = res.url
    wxSceneId.value = res.sceneId // sceneId 同后端约定好的key
    qrTimer.value = setInterval(() => {
      wxLogining()
    }, 1500)
    //重置二维码失效时间
    countdown.value = 150
    if (countdownTimer.value) clearTimeout(countdownTimer.value)
    countdownFc()
  })
}
//自定义函数,父组件可以触发
const em = defineEmits(['handleLogin'])

//微信监听登录状态
const wxLogining = () => {
// 调用后端接口传入key
  getUserBySceneId({ sceneId: wxSceneId.value }).then(res => {
    if (res.hasExist) {
      //停止查询扫码状态
      resetTimer()
      em('handleLogin', res.wxUnionId)
    }
  })
}
</script>
<style scoped>
.wx_box {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
}
.wx_text {
  display: flex;
  align-items: center;
  margin-top: 10px;
}
.wx_text .icon {
  margin: 5px;
  cursor: pointer;
}
.cover {
  position: absolute;
  width: 160px;
  height: 160px;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: #e0e0e0;
  cursor: pointer;
}

.wx_img {
  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
  padding: 3px;
  border-radius: 5px;
  width: 160px;
  height: 160px;
}
.wx_img img {
  width: 100%;
}
</style>

在页面中使用

<WxPublic v-if="wxAuth === 'open'" ref="wxRef" @handleLogin="wxLogin"></WxPublic>




const wxLogin = (wxUnionId) => {
    logInfo.wxUnionId = wxUnionId
    handleLogin()
}