BUG 描述

登录页面,选择微信扫码登录,如果是没有绑定企业的微信账号,在微信扫码页面跳转回来的时候,登录页请求接口会返回 400 状态码,然后刷新页面再次请求接口返回 400 状态码,无限循环。

代码分析

前端所有的 http 请求全部封装在 request.js 内,每次请求都会走一遍这里面的代码,这里根据不同的 状态码 做了一些处理,经过长时间多人的反复修改,里面穿插了很多 业务处理, 这个 BUG 就是由这部分代码所引起的。

// … 注释部分为与本次 BUG 分析无关的上下文相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// request.js 修复前代码
if (status === 401) {
// ...
} else if (status === 400) {
if (error.response.data.error.message === '当前账户没有绑定企业') {
Notification.error({
title: '当前账户没有绑定企业, 请重新登录',
position: 'bottom-right',
duration: 1000,
onClose: () => {
store.dispatch('authority/LogOut', true, { root: true }) // 退出登录
router.go('/login') // 去往登录页
}
})
} else if // ...

上面这段代码的大概意思是,所有的 http 请求返回 状态码 400, 就让路由跳转到登录页,然后看下登录页的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// login/index.vue
mounted() {
// ...
this.getQRToken() // 在进入页面的时候,都会请求一次 getQRToken() 方法
// ...
},
methods: {
// 获取回调参数
getQRToken() {
const query = this.$route.query
if (query && query.weixin_token) {
// 返回微信 token 跳转 绑定账号页面 传递token
this.$router.push({path: '/binding', query})
} else if (query && query.user_token) {
// 返回用户token 请求第三方登陆获取令牌
this.loading = true
this['authority/getAccessTokenByQR']({user_token: query.user_token}).then(res => {
this.loading = false
if (res.status < 400 && res.data.access_token) {
this['authority/GetUserInfo']({})
.then((response) => {
this.$notify.success({
title: '登录成功',
position: 'right-bottom'
})
this['authority/setUserLoginState'](true)
this.$disconnect()
this.$connect()
if (navigator.userAgent.indexOf('Firefox') >= 0) {
window.location.href = getMainHost(ENV, null, true)
} else {
if (this.hasRoles) {
this.$router.push({path: '/'})
} else {
this.$router.push({path: '/mySupplier'})
}
}
}).catch((errorRes) => {
this['authority/LogOut']()
})
}
}).catch(e => {
console.log(e)
})
}
}
}, // ...
  • 在登录页,每次刷新页面或者进入页面,都会进入到 mounted 生命周期,执行里面代码,然后调用了 getQRToken 方法,在这个方法内部判断路由的参数(query)来得知是否是微信登录,如果是的话执行相应的处理。

  • 而当我们用没有绑定微信企业的账号扫码登录,微信跳转回来的路径是 https://86yqy.com/login?user_token=4e411cf59c1ea7a9c04fdd9329492c3c , 所以在 getQRToken 方法中,将会执行带有 user_token 的判断分支里。

  • user_token 的判断分支里请求接口,由于微信账号没有绑定企业返回了 400, 而每个请求都会走 request.js, 返回 400 的话 request.js又会让路由走回登录页。

目前的情况是,request.js 让路由走回登录页,但是没有清除掉 query 参数,所以导致从微信扫码登录页面跳转回来的路径是 https://86yqy.com/login?user_token=4e411cf59c1ea7a9c04fdd9329492c3c, 而 request.js400状态码 让路由跳转回登录页,完整路径还是 https://86yqy.com/login?user_token=4e411cf59c1ea7a9c04fdd9329492c3c, 没有变,所以又会从生命周期开始重新走一遍,一直循环。

解决方案

request.js400状态码 的处理,跳转回首页是对的,但是要把完整路由里面的 query 参数给清除掉,所以在上述第一段代码中使用 router.go('/login') 去往登录页就不合适了,因为它本身就在 /login 路由下,只是多了参数,所以要使用 router.replace('/login') 来将完整的路由 /login?user_token=4e411cf59c1ea7a9c04fdd9329492c3c 替换成 /login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// request.js 修复后代码
if (status === 401) {
// ...
} else if (status === 400) {
if (error.response.data.error.message === '当前账户没有绑定企业') {
MessageBox.alert('当前账户没有绑定企业, 请重新登录', {
confirmButtonText: '确认',
type: 'warning',
callback: action => {
store.dispatch('authority/LogOut', true, { root: true }) // 退出登录
router.replace('/login') // 去往登录页
}
})
} else if // ...

额外优化

  • 除了更换 router 的方法使用解决死循环 BUG, 这里还将第 Notification 组件 更换成了 MessageBox 组件,与其他状态码的表现保持统一

  • 整个 request.js 多处使用了 router.gorouter.pushrouter.replace, 也就是说上面的问题,其实在很多地方都是有问题的,只是没有触发,基于代码的严谨性、统一性做了相应的更改。

后续待优化

request.js 本意是封装 http 请求, 最初也是这样做的,但是由于长时间的多人修改,穿插了业务代码,导致现状维护难度高,可读性差,逻辑混乱难以梳理,所以应该重新封装 request.js,将 业务代码 抽取出来,单独维护, request.js 只负责提供 http 请求相关功能。