(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白名单,只有在白名单中的机器才会被微信服务器接收,否则都被认为是非法请求而拒绝。后者需要配置你的服务器地址,并进行验证,这样当有和你的公众号相关的事件发生,就会通过这个配置的服务器地址进行推送。
更具体地,需要做的准备工作有:
- 权限:认证的服务号,订阅号和未认证的服务号都没有获取带参二维码接口的权限,因此无法实现扫码登录。
- 开通微信公众号开发者(在微信公众号后台的基本配置中开启)
- 配置公众号服务地址并验证(在微信公众号后台的基本配置中配置并验证),这个地址也是后面接收扫码事件的。
- 配置公众号的ip白名单(serverless实例运行时候ip不固定,可以通过代理方式保证固定ip(例如阿里云文档给出的解决方案:help.aliyun.com/document_de…),也可以把微信后端接口放在云服务器上)。
- 实现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()
}