企业微信自建应用之跳转H5

3,962 阅读4分钟

需求

在企业微信中开发一个自建应用,只有一个登录和资产显示页面,登录之后后续免登录...其实需求很简单,登录和显示页面和逻辑右手就行。但他是在企业微信中打开的,而且需要公司自己系统的账号登录,并绑定当前进入的微信号,这就有点操蛋了。

难点1:企业微信的前期配置

难点2:H5是单独部署的,如何知道是谁点击的应用

开发到现在其实难点就这两个,解决了其他的都很简单。

企业微信配置

企业微信后台链接

1. 创建应用:(左边 资产测试 就是我创建开发好的)

image.png

2. 填写应用信息:

image.png

3. 创建完成后进入应用:

AgentIdSecret后期写代码时有用,应用主页后面再具体介绍,此时你想验证是否创建成功,你可以输入 www.baidu.com 这样你从企业微信点击应用就是跳转到百度的页面了。如果你的应用不需要知道是谁点击进入的,或者说不涉及登录的,那基本上到这里就结束了,可以划走了。

image.png

4. 需要拿到当前用户的消息:

当需要到用户的唯一标识,userId(等同于小程序中的openId),这时候就开始繁琐了。微信为了安全等因素考虑,开发者是不能显式的去配置跳转时携带用户信息的,只能通过折中的方案:构造一个链接,将需要跳转的网址放进链接里面然后交给企业微信,企业微信去验证相关信息通过后,将code码拼接到你上面的跳转网址后面,然后重定向到需要跳转的网址,你再通过消费code来换取userId等用户信息

企业微信开发者中心-接口文档

image.png

image.png

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,点开可配置域名 image.png

步骤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>

结束啦