需求
在企业微信中开发一个自建应用,只有一个登录和资产显示页面,登录之后后续免登录...其实需求很简单,登录和显示页面和逻辑右手就行。但他是在企业微信中打开的,而且需要公司自己系统的账号登录,并绑定当前进入的微信号,这就有点操蛋了。
难点1:企业微信的前期配置
难点2:H5是单独部署的,如何知道是谁点击的应用
开发到现在其实难点就这两个,解决了其他的都很简单。
企业微信配置
1. 创建应用:(左边 资产测试 就是我创建开发好的)
2. 填写应用信息:
3. 创建完成后进入应用:
AgentId和Secret后期写代码时有用,应用主页后面再具体介绍,此时你想验证是否创建成功,你可以输入 www.baidu.com 这样你从企业微信点击应用就是跳转到百度的页面了。如果你的应用不需要知道是谁点击进入的,或者说不涉及登录的,那基本上到这里就结束了,可以划走了。
4. 需要拿到当前用户的消息:
当需要到用户的唯一标识,userId(等同于小程序中的openId),这时候就开始繁琐了。微信为了安全等因素考虑,开发者是不能显式的去配置跳转时携带用户信息的,只能通过折中的方案:构造一个
链接,将需要跳转的网址放进链接里面然后交给企业微信,企业微信去验证相关信息通过后,将code码拼接到你上面的跳转网址后面,然后重定向到需要跳转的网址,你再通过消费code来换取userId等用户信息。
5. 按上面的步骤完成:
步骤1:构造链接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=[替换成你的]&redirect_uri=[替换成你的]&response_type=code&scope=snsapi_base&state=[xxx]#wechat_redirect
| 参数 | 必须 | 说明 |
|---|---|---|
| appid | 是 | 企业的CorpID |
| redirect_uri | 是 | 授权后重定向的回调链接地址,请使用urlencode对链接进行处理 |
| response_type | 是 | 返回类型,此时固定为:code |
| scope | 是 | 应用授权作用域。 snsapi_base:静默授权,可获取成员的基础信息(UserId与DeviceId); snsapi_privateinfo:手动授权,可获取成员的详细信息,包含头像、二维码等敏感信息。 |
| state | 否 | 重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节 |
| agentid | 是 | 应用agentid,建议填上该参数(对于第三方应用和代开发自建应用,在填写该参数的情况下或者在工作台、聊天工具栏、应用会话内发起oauth2请求的场景中,会触发接口许可的自动激活)。snsapi_privateinfo时必填否则报错; |
| #wechat_redirect | 是 | 终端使用此参数判断是否需要带上身份信息 |
这一步的难点在于:首先要进行企业微信的认证,其次要进行域名的认证,重定向的地址微信校验通过后才会帮你重定向,而且这两个认证必须是同一个公司主体,否则就进行不下去了
应用当中往下滑,有一个网页授权及JS-SDK,点开可配置域名
步骤2:写代码咯
tip:import.meta.env.VITE_APP_REPLACE_URL就是上面的redirect_uri,因为code只能消费一次,这个项目中要实现免登录,所以第一次进入会将code码发送给后端,后端消费之后得到是否登录绑定过,如果未登录绑定过就需要重新获取code码,所以只能重新来一遍。跳转的都是同一个页面怎么分辨是需要登录呢,就利用上面的state,它为login时就是登录,为其它时就不是登录。这个可以自己定的,这不是微信官方的哈,只是自己利用的!!以下代码做个参考吧,其实没什么技术含量的。。。剩下获取用户信息只能后端来做了!
<!--
🚀 Tip : 登录页
🥇 Author: XiaoQi
✍ Data : 2023-07-19 09:14:59
-->
<template>
<Overlay :show="show" @click="show = false">
<div class="wrapper" @click.stop>
<div class="block">
<img src="../assets/loading.gif" alt="">
</div>
</div>
</Overlay>
<div class="box" v-if="!show">
<!-- msg 为测试专用 充当控制台 -->
<!-- <div class="msg">{{msg}}</div> -->
<div class="img">
<img src="../assets/loginTop.png" alt="">
</div>
<div class="main">
<div class="username">
<img src="../assets/user.png" alt="">
<input type="text" v-model="username" @blur="toTop" placeholder="请输入账号">
</div>
<div class="password">
<img src="../assets/lock.png" class="imgA">
<input :type="isActive?'text':'password'" v-model="password" @blur="toTop" placeholder="请输入密码">
<img v-if="isActive" src="../assets/eyeoff.png" @click="changeActive" class="imgB">
<img v-else src="../assets/eye.png" @click="changeActive" class="imgB">
</div>
<div class="login" @click="login">登陆</div>
</div>
<div class="logo">
<img src="/xinsec.png" alt="">
</div>
</div>
</template>
<script setup>
import { ref, getCurrentInstance, onMounted, onUnmounted } from 'vue'
import { showToast, Overlay } from 'vant';
import md5 from 'js-md5';
import Cookies from 'js-cookie'
import { useRouter } from 'vue-router'
const { proxy } = getCurrentInstance() // 1.获取proxy实例
const $router = useRouter()
const username = ref('')
const password = ref('')
const isActive = ref(false)
const LYcode = ref('')
const show = ref(true)
// 充当 log
const msg = ref('')
onMounted(async () => {
document.body.style.overflow = 'hidden'
init()
})
onUnmounted(() => {
document.body.style.overflow = 'auto'
})
// 初始化
const init = () => {
const flag = getQueryCode('state')
LYcode.value = getQueryCode('code')
// 调用后台接口,授权
flag === 'login' && (show.value = false)
flag === 'xinsec' && getServeToken()
}
// 获取 url 指定字段内容
const getQueryCode = (variable) => {
const query = window.location.search.substring(1);
const vars = query.split("&")
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split("=");
if (pair[0] == variable) {
localStorage.setItem('code', pair[1])
return pair[1];
}
}
return false;
}
// 请求 token
const getServeToken = async () => {
try {
const res = await proxy.$api.login({ code: LYcode.value })
msg.value = res
if (res.code === 0) {
Cookies.set('token', res.data.token)
// 成功 跳转到 index
$router.push('index')
} else {
location.replace(import.meta.env.VITE_APP_REPLACE_URL)
}
} catch (error) {
showToast(`error:${error}`)
}
}
// 切换密码显示状态
const changeActive = () => {
const temp = !isActive.value
isActive.value = temp
}
// 回到顶部
const toTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
// 登录
const login = async () => {
if (!username.value || !password.value) {
showToast(`请完整填写登录信息!`)
return
}
try {
const res = await proxy.$api.login({
loginId: username.value,
loginPassword: md5(password.value),
code: LYcode.value
})
msg.value = res
if (res.code === 0) {
Cookies.set('token', res.data.token)
$router.push('index')
} else {
if (res.code === 40029 || res.code === 317022) {
showToast(`code为空或超时!自动获取code中...`)
} else {
showToast(`登录失败,请重试!`)
}
setTimeout(() => {
location.replace(import.meta.env.VITE_APP_REPLACE_URL)
}, 1000);
}
} catch (error) {
showToast(`error:${error}`)
}
}
</script>