vue的hash路由微信授权方法

最近在开发公众号网页, 所以对授权进行了探索
示例代码: klren0312/wechatVueHash (github.com)

1. 官方文档步骤

1 第一步:用户同意授权,获取code

2 第二步:通过code换取网页授权access_token

3 第三步:刷新access_token(如果需要)

4 第四步:拉取用户信息(需scope为 snsapi_userinfo)

5 附:检验授权凭证(access_token)是否有效

2. 问题

当使用vue的hash路由时, 微信授权重定向到前端时, 会把路由放到url最后, 例如

https://open.weixin.qq.com/connect/oauth2/authorize?appid=yourappid&redirect_uri=https%3A%2F%2Fxx.xx.xx%2Fwechat&response_type=code&scope=snsapi_base&state=wechat&connect_redirect=1#wechat_redirect
会变成
https://xx.xx.xx/wechat/?code=091v5v000CeBWM1bGz2005y2Sd3v5v0q&state=wechat#/codePage
hash路由问题

3. 处理方法

1) 方法一

在路由拦截器中截取#/后的路由, 重新拼接成正确url, 并使用location.href进行跳转
如果想带参, 可以直接放在路由后面或者放在state里面

带参

注意: redirect_uristate都得使用encodeURIComponent进行编码

当然我们得拿code 去后台请求openId等参数进行业务开发

路由拦截器中进行路由拼接与code获取请求接口例子(本例子页面参数是从state中获取)

router.beforeEach(async (to, from, next) => {
  const href = window.location.href
  if (href.indexOf('/?code') > -1) {
    const urlArr = href.split('/?')
    const leftUrl = urlArr[0] + '/#/'
    const rightUrlArr = urlArr[1].split('#/')
    const queryObj = {}
    // 获取code和state参数
    rightUrlArr[0]
      .split('&')
      .map((item) => {
        const splitStr = item.split('=')
        return {
          key: splitStr[0],
          value: splitStr[1],
        }
      })
      .forEach((item) => {
        queryObj[item.key] = item.value
      })
    // 使用微信code请求后台接口拿openId等你业务参数
    getOpenId(queryObj.code)
      .then((res) => res.json())
      .then((res) => {
        if (res.code === 0) {
          // 解码state参数
          const state = decodeURIComponent(queryObj.state)
          // 拼接url, 跳转
          location.href = `${leftUrl}${rightUrlArr[1]}?openid=${res.openid}&state=${state}`
        } else {
          location.href = leftUrl + 'login'
        }
      })
      .catch(() => {
        location.href = leftUrl + 'login'
      })
  } else {
    next()
  }
})

2) 方法二

授权回调后端接口, 后端获取微信的code重定向给前端, 前端拿url中的code参数再请求后端接口获取openId等

流程
# 设置为后台接口地址
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd5be0fe8e3c48877&redirect_uri=https%3A%2F%2Fxx.xx.xx%2Fapi%2FgetCode&response_type=code&scope=snsapi_base&state=wechat&connect_redirect=1#wechat_redirect

# 最后跳转地址
https://xx.xx.xx/wechat/#/codePage?code=001sMjFa1F7uhC0lncJa1jHXCs3sMjFa

后端nodejs示例代码

const got = require('got')
const express = require('express')
const bodyParser = require('body-parser')
const ioredis = require('ioredis')
const redis = new ioredis()
const app = express()
app.use('/static', express.static('public'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

const appID = ''
const appsecret = ''

const BASEURL = encodeURIComponent('https://xx.xx.xx/wechat')

const BASEURL2 = encodeURIComponent('https://xx.xx.xx/api/getCode')

//设置所有路由无限制访问,不需要跨域
app.all('*', function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
  res.header('Access-Control-Allow-Methods', '*')
  next()
})

const SERVERURL = '/api'
// 微信域名校验
app.get(SERVERURL + '/wechat', function(req, res) {
  const { signature, timestamp, nonce, echostr } = req.query
  console.log(req.query)
  const token = 'zzes'
  jsSHA = require('jssha')
  const arr = [token, timestamp, nonce].sort()
  shaObj = new jsSHA(arr.join(''), 'TEXT')
  const currentSign = shaObj.getHash('SHA-1', 'HEX')
  if (currentSign === signature) {
    res.send(echostr)
  } else {
    res.send('')
  }
})

// 获取用户openId
app.post(SERVERURL + '/getOpenId', function(req, res) {
  const { code } = req.body
  const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appID}&secret=${appsecret}&code=${code}&grant_type=authorization_code`
  got(url).then(data => {
    const result = JSON.parse(data.body)
    if (result?.openid) {
      console.log('openid:' + result.openid)
      res.send({
        code: 0,
        binding: true,
        openid: result.openid
      })
    } else {
      console.log('err', result)
      res.send({
        code: result.errcode,
        binding: false,
        openid: '',
        msg: result.errmsg
      })
    }
  }).catch(err => {
    res.send({
      code: -1,
      binding: false,
      openid: '',
      msg: err.message
    })
  })
})

// 后端拿code, 这里授权域名得配后台的域名
app.get(SERVERURL + '/getCode', async function(req, res) {
  const { code } = req.query
  console.log(req.query)
  res.redirect(`${decodeURIComponent(BASEURL)}/#/codePage?code=${code}`)
})

// 发送模板消息
app.get(SERVERURL + '/sendMsg', async function(req, res) {
  const { openid } = req.query
  const result = await sendTemplateMsg(openid)
  res.send(result)
})

//端口:18888
var server = app.listen(28848, function() {
  console.log("127.0.0.1:28848")
})

// 创建菜单
setWechatMenu()
async function setWechatMenu() {
  const url = encodeURIComponent(`/#/`)
  const menu = {
    button: [
      {
        name: '菜单',
        sub_button: [
          { 
            type:'view',
            name:'测试一',
            url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
          },
          { 
            type:'view',
            name:'测试二',
            url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat2&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
          },
          { 
            type:'view',
            name:'测试',
            url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL2}&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
          }
        ]
      }
    ]
  }
  let accessToken = await redis.get('access_token')
  if (!accessToken) {
    accessToken = await getAccessToken()
  }
  got({
    url: `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`,
    method: 'POST',
    body: JSON.stringify(menu)
  }).then(data => {
    const result = JSON.parse(data.body)
    console.log('菜单', result)
  })
}

/**
 * 发送模板消息
 */
async function sendTemplateMsg(openid) {
  let accessToken = await redis.get('access_token')
  if (!accessToken) {
    accessToken = await getAccessToken()
  }
  const state = encodeURIComponent(`wechat&id=${Math.floor(Math.random() * 100)}`)
  return got({
    url: `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${accessToken}`,
    method: 'POST',
    body: JSON.stringify({
      touser: openid,
      template_id: 'WfcomWPkkbQlvTJXJpzFVWGc14hOeyI23TXgHPST8-I',
      url: `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat1&response_type=code&scope=snsapi_base&state=${state}#wechat_redirect`,
      data: {
        time: {
          value: new Date().toLocaleString(),
          color: '#323232'
        },
        content: {
          value: '您有新的消息, 请点击查看',
          color: '#ff0000'
        }
      }
    })
  }).then(data => {
    const result = JSON.parse(data.body)
    return result
  })
}

/**
 * 获取access_token
 */
function getAccessToken() {
  return got(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`)
    .then(data => {
      console.log(data.body)
      const result = JSON.parse(data.body)
      if (result?.access_token) {
        redis.set('access_token', result.access_token, 'EX', result.expires_in - 60)
        return result.access_token
      } else {
        console.log('err', result)
        return ''
      }
    })
    .catch(err => {
      console.log(err)
      return ''
    })
}

示例测试公众号

简书不给发 也是牛 去github 的 readme中看吧

推荐阅读更多精彩内容