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中看吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容