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项目的根目录下)到你的微信授权回调域名的目录下。
- 前往微信公众平台->接口权限->网页授权获取用户基本信息->修改,填写授权回调页面域名,例如
www.abc.com
- 在
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 复制代码
同意之后就看到code值了
到此主要的流程就结束了。当授权成功后需要通过code换取token,因为并没有对接后台,所以这里我注释掉了
接下来操作在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 ̄)づ╭❤~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。