3

github:vue-wechat-auth

之前是自己太年轻,写什么【最终解决方案】。这一次的项目vue移动端电商项目,做了很多的优化。大家都知道授权需要每次都要发布到线上,本地的需要代理,这让我们很头疼。后面会介绍一个本地直接授权的方式,真的超级香。

时隔几年,第三次升级我的微信授权,每一次思路都更加清晰,当我的知识越来越广,越来越深,我相信会有第四次,第五次。。。

另外也优化:

  • 微信分享
  • keep-alive返回到上次浏览的位置
  • vue-router 所有页面携带参数
  • ...

后续会持续分享,接下来首先优化的就是授权逻辑的优化。

场景

整个项目无论什么页面进入都需要进行授权,一般微信公众号H5项目这一点都是需要做到

接下来我们开始吧,先克隆安装依赖,不要着急启动,先把准备工作做好。

// 克隆项目
git clone https://github.com/sunnie1992/vue-wechat-auth.git
//安装依赖
npm install 复制代码

实现本地微信授权

1.工具

实现本地开发授权,你需要使用微信开发者工具,网页是没有办法直接本地拿到授权的。

2.将auth.html部署到服务器上

这里我们用到了 GetWeixinCode ,使用的时候修复了一些bug

  • 携带的参数在授权完之后没能全部带回来。
  • hash回调url错误问题

部署auth.html(在github项目的根目录下)到你的微信授权回调域名的目录下。

  1. 前往微信公众平台->接口权限->网页授权获取用户基本信息->修改,填写授权回调页面域名,例如www.abc.com
  2. www.abc.com域名下部署auth.html,不一定是根目录,例如:https://www.abc.com/xxx/auth....
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>微信登录</title>
  </head>

  <body>
    <script>
      var GWC = {
        version: '1.2.0',
        urlParams: {},
        appendParams: function (url, params) {
                if (params) {
                    var baseWithSearch = url.split('#')[0];
                    var hash = window.location.hash.split('#')[1];
                    if (hash) {
                      baseWithSearch = baseWithSearch + '#' + hash;
                    } 
                    for (var key in params) {
                        var attrValue = params[key];
                        if (attrValue !== undefined) {
                            var newParam = key + "=" + attrValue;
                            if (baseWithSearch.indexOf('?') > 0) {
                                var oldParamReg = new RegExp('^' + key + '=[-%.!~*\'\(\)\\w]*', 'g');
                                if (oldParamReg.test(baseWithSearch)) {
                                    baseWithSearch = baseWithSearch.replace(oldParamReg, newParam);
                                } else {
                                    baseWithSearch += "&" + newParam;
                                }
                            } else {
                                baseWithSearch += "?" + newParam;
                            }
                        }
                    }
                }
               
                return baseWithSearch;
            
            },
        getUrlParams: function() {
          var pairs = location.search.substring(1).split('&')
          for (var i = 0; i < pairs.length; i++) {
            var pos = pairs[i].indexOf('=')
            if (pos === -1) {
              continue
            }
            GWC.urlParams[pairs[i].substring(0, pos)] = decodeURIComponent(pairs[i].substring(pos + 1))
          }
        },
        doRedirect: function() {
          var code = GWC.urlParams['code']
          var appId = GWC.urlParams['appid']
          var scope = GWC.urlParams['scope'] || 'snsapi_base'
          var state = GWC.urlParams['state']
          var isMp = GWC.urlParams['isMp'] //isMp为true时使用开放平台作授权登录,false为网页扫码登录
          var baseUrl
          var redirectUri

          if (!code) {
            baseUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize#wechat_redirect'
            if (scope == 'snsapi_login' && !isMp) {
              baseUrl = 'https://open.weixin.qq.com/connect/qrconnect'
            }

            //第一步,没有拿到code,跳转至微信授权页面获取code
            redirectUri = GWC.appendParams(baseUrl, {
              appid: appId,
              redirect_uri: encodeURIComponent(location.href),
              response_type: 'code',
              scope: scope,
              state: encodeURIComponent(state)
            })
        
          } else {
            const params = Object.assign({}, GWC.urlParams)
            delete params.backUrl
            //第二步,从微信授权页面跳转回来,已经获取到了code,再次跳转到实际所需页面
            redirectUri = GWC.appendParams(GWC.urlParams['backUrl'], params)
          }

          location.href = redirectUri
        }
      }

      GWC.getUrlParams()
      GWC.doRedirect()
    </script>
  </body>
</html>

3.实现代码逻辑

主要文件:src/plugins/wechatAuth.js

微信授权相关方法封装这里引用的是[vue-wechat-login],做了简单的修改,直接在路由钩子文件permission.js使用。

const qs = require('qs')
// 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
const SCOPES = ['snsapi_base', 'snsapi_userinfo']

class VueWechatAuthPlugin {
  constructor() {
    this.appid = null
    this.redirect_uri = null
    this.scope = SCOPES[1]
    this._code = null
    this._redirect_uri = null
  }

  static makeState() {
    return (
      Math.random()
        .toString(36)
        .substring(2, 15) +
      Math.random()
        .toString(36)
        .substring(2, 15)
    )
  }
  setAppId(appid) {
    this.appid = appid
  }
  set redirect_uri(redirect_uri) {
    this._redirect_uri = encodeURIComponent(redirect_uri)
  }
  get redirect_uri() {
    return this._redirect_uri
  }
  get state() {
    return localStorage.getItem('wechat_auth:state')
  }
  set state(state) {
    localStorage.setItem('wechat_auth:state', state)
  }
  get authUrl() {
    if (this.appid === null) {
      throw new Error('appid must not be null')
    }
    if (this.redirect_uri === null) {
      throw new Error('redirect uri must not be null')
    }
    this.state = VueWechatAuthPlugin.makeState()
    return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appid}&redirect_uri=${this.redirect_uri}&response_type=code&scope=${this.scope}&state=${this.state}#wechat_redirect`
  }
  returnFromWechat(redirect_uri) {
    const parsedUrl = qs.parse(redirect_uri.split('?')[1])

    if (process.env.NODE_ENV === 'development') {
      this.state = null
      this._code = parsedUrl.code
    } else {
      if (this.state === null) {
        throw new Error("You did't set state")
      }
      if (parsedUrl.state === this.state) {
        this.state = null
        this._code = parsedUrl.code
      } else {
        this.state = null
        throw new Error(`Wrong state: ${parsedUrl.state}`)
      }
    }
  }
  get code() {
    if (this._code === null) {
      throw new Error('Not get the code from wechat server!')
    }
    const code = this._code
    this._code = null
    // console.log('code: ' + code)
    return code
  }
}
const vueWechatAuthPlugin = new VueWechatAuthPlugin()

export default vueWechatAuthPlugin

4.设置相关变量

在开发之前你要首先在下面三个文件设置两个变量,如果你已经启动项目,设置后需要重启。

.env.development 

.env.staging 

 .env.production

VUE_APP_WECHAT_APPID是你的appid,在.env.[环境] 文件中设置

VUE_APP_WECHAT_AUTH_URL是你的auth.html 访问地址。在.env.[环境] 文件中设置

5.permission.js 路由守卫

设置授权白名单whiteList,授权失败,或者其他错误进入404页面。

 // 设置回调地址,本地和线上不同 
wechatAuth.redirect_uri = processUrl()  
await store.dispatch('user/setLoginStatus', 1)
// 跳转完整的授权地址
window.location.href = wechatAuth.authUrl复制代码

wechatAuth.authUrl 地址 https://open.weixin.qq.com/co..._uri 设置调用 processUrl方法,本地开发,回调设置本地路径会报redirect_uri错误,所以我们跳到中间页auth.html再携带code跳会到backUrl。

本地环境返回授权的回调地址:

`${process.env.VUE_APP_WECHAT_AUTH_URL}?backUrl=${window.location.href}`复制代码

 其中 process.env.VUE_APP_WECHAT_AUTH_URL 就是中间授权页面的网址。backUrl后面跟的是你本地开发的地址。https://www.abc.com/auth.html...://localhost:9018/#/

线上环境返回的是正常的微信授权地址:

https://open.weixin.qq.com/co...

redirect_uri是线上的地址,不用中间页跳转。

import qs from 'qs'
import router from '@/router'
import store from '@/store'
import wechatAuth from '@/plugins/wechatAuth'
// 设置APPID
wechatAuth.setAppId(process.env.VUE_APP_WECHAT_APPID)
const whiteList = ['/404']
router.beforeEach(async (to, from, next) => {
  // 在白名单,直接进入
  if (whiteList.indexOf(to.path) !== -1) {
    return next()
  }
  const {loginStatus} = store.getters
  switch (Number(loginStatus)) {
    case 0:
      // 获取跳转地址
      wechatAuth.redirect_uri = processUrl()
      await store.dispatch('user/setLoginStatus', 1)
      window.location.href = wechatAuth.authUrl
      break
    case 1:
      try {
        wechatAuth.returnFromWechat(to.fullPath)
        const code = wechatAuth.code
        console.log('code==', code)
        // 通过code换取token
        // await store.dispatch('user/loginWechatAuth', code)
        await store.dispatch('user/setLoginStatus', 2)
        next()
      } catch (err) {
        await store.dispatch('user/setLoginStatus', 0)
        next('/404')
      }
      break
    case 2:
      next()
      break
    default:
      break
  }
})

/**
 * 处理url链接
 * @returns {string}
 */
function processUrl() {
  // 本地环境换通过auth.html拿code
  if (process.env.NODE_ENV === 'development') {
    // 中间授权页地址
    return `${process.env.VUE_APP_WECHAT_AUTH_URL}?backUrl=${window.location.href}`
  }
  const url = window.location.href
  // 解决多次登录url添加重复的code与state问题
  const urlParams = qs.parse(url.split('?')[1])
  let redirectUrl = url
  if (urlParams.code && urlParams.state) {
    delete urlParams.code
    delete urlParams.state
    const query = qs.stringify(urlParams)
    if (query.length) {
      redirectUrl = `${url.split('?')[0]}?${query}`
    } else {
      redirectUrl = `${url.split('?')[0]}`
    }
  }
  return redirectUrl
}

当配置好参数,本地启动后,可以正常进入授权页面

// 启动
npm run serve 复制代码

WX20200223-114848@2x.png

同意之后就看到code值了
WX20200223-145454@2x.png

到此主要的流程就结束了。当授权成功后需要通过code换取token,因为并没有对接后台,所以这里我注释掉了

WX20200223-150114@2x.png

接下来操作在vuex中进行,用户根据需求对接后台接口即可。

/src/store/modules/user.js

  import {loginByCode} from '@/api/user'
import {
  saveToken,
  saveLoginStatus,
  saveUserInfo,
  removeToken,
  removeUserInfo,
  removeLoginStatus,
  loadLoginStatus,
  loadToken,
  loadUserInfo
} from '@/utils/cache'
const state = {
  loginStatus: loadLoginStatus(), // 登录状态
  token: loadToken(), // token
  userInfo: loadUserInfo() // 用户登录信息
}

const mutations = {
  SET_USERINFO: (state, userInfo) => {
    state.userInfo = userInfo
  },
  SET_LOGIN_STATUS: (state, loginStatus) => {
    state.loginStatus = loginStatus
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  }
}

const actions = {
  // 登录相关,通过code获取token和用户信息,用户根据自己的需求对接后台
  loginWechatAuth({commit}, code) {
    const data = {
      code: code
    }
    return new Promise((resolve, reject) => {
      loginByCode(data)
        .then(res => {
          // 存用户信息,token
          commit('SET_USERINFO', saveUserInfo(res.data.user))
          commit('SET_TOKEN', saveToken(res.data.token))
          resolve(res)
        })
        .catch(error => {
          reject(error)
        })
    })
  },
  // 设置状态
  setLoginStatus({commit}, query) {
    if (query === 0 || query === 1) {
      // 上线打开注释,本地调试注释掉,保持信息最新
      removeToken()
      removeUserInfo()
    }
    // 设置不同的登录状态
    commit('SET_LOGIN_STATUS', saveLoginStatus(query))
  },
  // 登出
  fedLogOut() {
    // 删除token,用户信息,登陆状态
    removeToken()
    removeUserInfo()
    removeLoginStatus()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

utils/cache.js文件用户来缓存数据

 import cookies from 'js-cookie'
import storage from 'good-storage'
const LoginStatusKey = 'Login-Status' // 登录态 0未授权未登录 1授权未登录 2 登陆成功
const TokenKey = 'Access-Token' // token
const UserInfoKey = 'User-Info' // 用户信息 {} {...}
// 获取登录状态
export function loadLoginStatus() {
  return cookies.get(LoginStatusKey) || 0
}
// 保持登录状态
export function saveLoginStatus(status) {
  cookies.set(LoginStatusKey, status, {expires: 7})
  return status
}
// 删除登录状态
export function removeLoginStatus() {
  cookies.remove(LoginStatusKey)
  return ''
}
// 获取token
export function loadToken() {
  return storage.get(TokenKey, '')
}
// 保存token
export function saveToken(token) {
  storage.set(TokenKey, token)
  return token
}
// 删除token
export function removeToken() {
  storage.remove(TokenKey)
  return ''
}
// 获取用户信息
export function loadUserInfo() {
  return storage.get(UserInfoKey, {})
}
// 保存用户信息
export function saveUserInfo(userInfo) {
  storage.set(UserInfoKey, userInfo)
  return userInfo
}
// 删除用户信息
export function removeUserInfo() {
  storage.remove(UserInfoKey)
  return {}
}

项目地址

github:vue-wechat-auth

另外,项目架构介绍请看[ vue-h5-template]基于vue-cli4.0+webpack 4+vant ui + sass+ rem适配方案+axios封装。如果你只需要授权逻辑,只要把涉及到的相关文件放到你的项目下就可以。

关于我

如果您遇到了问题可以给我提 issues 

您也可以扫描添加下方的微信并备注 Sol 加前端交流群,交流学习。

如果对你有帮助送我一颗小星星,你的star是我前进的动力(づ ̄3 ̄)づ╭❤~


程序媛花花
1.7k 声望172 粉丝

玩的转前端,搞的定产品,骑车能上三十五,努力跑个马拉松