diff --git a/yudao-ui-app/api/auth.js b/yudao-ui-app/api/auth.js index 476a04740..54799eb9c 100644 --- a/yudao-ui-app/api/auth.js +++ b/yudao-ui-app/api/auth.js @@ -3,11 +3,13 @@ const { http } = uni.$u //使用手机 + 密码登录 export const passwordLogin = data => http.post('/member/auth/login', data) -//退出登录 -export const logout = data => http.post('/member/auth/logout', data) //发送手机验证码 export const sendSmsCode = data => http.post('/member/auth/send-sms-code', data) //使用手机 + 验证码登录 export const smsLogin = data => http.post('/member/auth/sms-login', data) -//社交登录,使用 (手机号授权)code + 用户信息 -export const socialLogin = data => http.post('/member/auth/social-login', data) +//微信小程序的一键登录 +export const weixinMiniAppLogin = data => http.post('/member/auth/weixin-mini-app-login', data) +//刷新令牌 +export const refreshToken = data => http.post('/member/auth/refresh-token', data) +//退出登录 +export const logout = data => http.post('/member/auth/logout', data) diff --git a/yudao-ui-app/common/config.js b/yudao-ui-app/common/config.js index fceb0a814..fed69625b 100644 --- a/yudao-ui-app/common/config.js +++ b/yudao-ui-app/common/config.js @@ -1,6 +1,10 @@ module.exports = { //后端接口地址 baseUrl: 'http://127.0.0.1:48080/app-api', + // 超时 + timeout: 30000, + // 禁用 Cookie 等信息 + withCredentials: false, header: { //租户ID 'tenant-id': 1 diff --git a/yudao-ui-app/main.js b/yudao-ui-app/main.js index d662ffd07..942875b87 100644 --- a/yudao-ui-app/main.js +++ b/yudao-ui-app/main.js @@ -21,6 +21,6 @@ const app = new Vue({ }) // 引入请求封装 -require('./util/request/index')(app) +require('./utils/request/index')(app) app.$mount() diff --git a/yudao-ui-app/pages/login/mobile.vue b/yudao-ui-app/pages/login/mobile.vue index 6a9525040..e58e6527d 100644 --- a/yudao-ui-app/pages/login/mobile.vue +++ b/yudao-ui-app/pages/login/mobile.vue @@ -139,15 +139,30 @@ export default { }, handleSubmit() { this.$refs.form.validate().then(res => { - this.$store.dispatch('Login', { type: this.currentModeIndex, data: this.formData }).then(res => { - uni.$u.toast('登录成功') - setTimeout(() => { - uni.switchTab({ - url: '/pages/user/user' - }) - }, 300) + uni.login({ + provider: 'weixin', + success: res => { + let data = this.formData + data.socialType = 34 //WECHAT_MINI_APP 先指定固定值 + data.socialCode = res.code + data.socialState = Math.random() // 该参数没有实际意义暂时传随机数 + this.mobileLogin(data) + }, + fail: res => { + this.mobileLogin(this.formData) + } }) }) + }, + mobileLogin(data){ + this.$store.dispatch('Login', { type: this.currentModeIndex, data: data }).then(res => { + uni.$u.toast('登录成功') + setTimeout(() => { + uni.switchTab({ + url: '/pages/user/user' + }) + }, 300) + }) } } } diff --git a/yudao-ui-app/pages/login/social.vue b/yudao-ui-app/pages/login/social.vue index 502393fa6..ac09a095c 100644 --- a/yudao-ui-app/pages/login/social.vue +++ b/yudao-ui-app/pages/login/social.vue @@ -33,7 +33,7 @@ export default { onReady() {}, methods: { getPhoneNumber(e) { - let code = e.detail.code + let phoneCode = e.detail.code if (!e.detail.code) { uni.showModal({ title: '授权失败', @@ -50,10 +50,10 @@ export default { } }) } else { - uni.getUserInfo({ + uni.login({ provider: 'weixin', success: res => { - this.$store.dispatch('Login', { type: 2, data: { code: code, userData: res } }).then(res => { + this.$store.dispatch('Login', { type: 2, data: { phoneCode: phoneCode, loginCode: res.code } }).then(res => { uni.$u.toast('登录成功') setTimeout(() => { uni.switchTab({ diff --git a/yudao-ui-app/store/index.js b/yudao-ui-app/store/index.js index 5ca02bbd4..9eed31c90 100644 --- a/yudao-ui-app/store/index.js +++ b/yudao-ui-app/store/index.js @@ -2,24 +2,25 @@ import Vue from 'vue' import Vuex from 'vuex' import { logout } from '@/api/auth' import { getUserInfo } from '@/api/user' -import { passwordLogin, smsLogin, socialLogin } from '@/api/auth' +import { passwordLogin, smsLogin, weixinMiniAppLogin } from '@/api/auth' -const TokenKey = 'App-Token' +const AccessTokenKey = 'ACCESS_TOKEN' +const RefreshTokenKey = 'REFRESH_TOKEN' Vue.use(Vuex) // vue的插件机制 // Vuex.Store 构造器选项 const store = new Vuex.Store({ state: { - openExamine: false, // 是否开启审核状态。用于小程序、App 等审核时,关闭部分功能。TODO 芋艿:暂时没找到刷新的地方 - token: uni.getStorageSync(TokenKey), // 用户身份 Token - userInfo: {}, // 用户基本信息 - timerIdent: false // 全局 1s 定时器,只在全局开启一个,所有需要定时执行的任务监听该值即可,无需额外开启 TODO 芋艿:需要看看 + accessToken: uni.getStorageSync(AccessTokenKey), // 访问令牌 + refreshToken: uni.getStorageSync(RefreshTokenKey), // 刷新令牌 + userInfo: {} }, getters: { - token: state => state.token, + accessToken: state => state.accessToken, + refreshToken: state => state.refreshToken, userInfo: state => state.userInfo, - hasLogin: state => !!state.token + hasLogin: state => !!state.accessToken }, mutations: { // 更新 state 的通用方法 @@ -32,12 +33,14 @@ const store = new Vuex.Store({ state[param.key] = param.val } }, - // 更新token + // 更新令牌 SET_TOKEN(state, data) { - // 设置 Token - const { token } = data - state.token = token - uni.setStorageSync(TokenKey, token) + // 设置令牌 + const { accessToken, refreshToken } = data + state.accessToken = accessToken + state.refreshToken = refreshToken + uni.setStorageSync(AccessTokenKey, accessToken) + uni.setStorageSync(RefreshTokenKey, refreshToken) // 加载用户信息 this.dispatch('ObtainUserInfo') @@ -46,10 +49,12 @@ const store = new Vuex.Store({ SET_USER_INFO(state, data) { state.userInfo = data }, - // 清空 Token 和 用户信息 + // 清空令牌 和 用户信息 CLEAR_LOGIN_INFO(state) { - uni.removeStorageSync(TokenKey) - state.token = '' + uni.removeStorageSync(AccessTokenKey) + uni.removeStorageSync(RefreshTokenKey) + state.accessToken = '' + state.refreshToken = '' state.userInfo = {} } }, @@ -65,7 +70,7 @@ const store = new Vuex.Store({ commit('SET_TOKEN', res.data) }) } else { - return socialLogin(data).then(res => { + return weixinMiniAppLogin(data).then(res => { commit('SET_TOKEN', res.data) }) } diff --git a/yudao-ui-app/util/request/responseInterceptors.js b/yudao-ui-app/util/request/responseInterceptors.js deleted file mode 100644 index ca858313e..000000000 --- a/yudao-ui-app/util/request/responseInterceptors.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 响应拦截 - * @param {Object} http - */ -module.exports = vm => { - uni.$u.http.interceptors.response.use( - res => { - //对响应成功做点什么 可使用async await 做异步操作 - //可以根据业务情况做相应的处理 - if (res.data.code === 0) { - return res.data - } else if(res.data.code === 401) { - //用户未登录或登录token已过期 - vm.$store.commit('CLEAR_LOGIN_INFO') - } else { - console.log(res) - //其他错误信息统一处理 - uni.$u.toast(res.data.msg) - return Promise.reject(res) - } - }, - err => { - //对响应错误做点什么 (statusCode !== 200) - console.log(err) - uni.$u.toast('响应错误' + err.statusCode) - return Promise.reject(err) - } - ) -} diff --git a/yudao-ui-app/utils/request/errorCode.js b/yudao-ui-app/utils/request/errorCode.js new file mode 100644 index 000000000..d2111ee10 --- /dev/null +++ b/yudao-ui-app/utils/request/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/yudao-ui-app/utils/request/index.js b/yudao-ui-app/utils/request/index.js new file mode 100644 index 000000000..a27fb424d --- /dev/null +++ b/yudao-ui-app/utils/request/index.js @@ -0,0 +1,14 @@ +// 引入配置 +import config from '@/common/config' +// 初始化请求配置 +uni.$u.http.setConfig((defaultConfig) => { + /* defaultConfig 为默认全局配置 */ + defaultConfig.baseURL = config.baseUrl /* 根域名 */ + defaultConfig.header = config.header + return defaultConfig +}) + +module.exports = (vm) => { + require('./requestInterceptors')(vm) + require('./responseInterceptors')(vm) +} diff --git a/yudao-ui-app/util/request/requestInterceptors.js b/yudao-ui-app/utils/request/requestInterceptors.js similarity index 85% rename from yudao-ui-app/util/request/requestInterceptors.js rename to yudao-ui-app/utils/request/requestInterceptors.js index ec4224f9d..afd1137d0 100644 --- a/yudao-ui-app/util/request/requestInterceptors.js +++ b/yudao-ui-app/utils/request/requestInterceptors.js @@ -9,7 +9,7 @@ module.exports = vm => { // 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{} config.data = config.data || {} if (vm.$store.getters.hasLogin) { - config.header.authorization = 'Bearer ' + vm.$store.getters.token + config.header.Authorization = 'Bearer ' + vm.$store.getters.accessToken } return config }, diff --git a/yudao-ui-app/utils/request/responseInterceptors.js b/yudao-ui-app/utils/request/responseInterceptors.js new file mode 100644 index 000000000..32b91875d --- /dev/null +++ b/yudao-ui-app/utils/request/responseInterceptors.js @@ -0,0 +1,98 @@ +import errorCode from '@/utils/request/errorCode' +import { refreshToken } from '@/api/auth' + +// 需要忽略的提示。忽略后,自动 Promise.reject('error') +const ignoreMsgs = [ + '无效的刷新令牌', // 刷新令牌被删除时,不用提示 + '刷新令牌已过期' // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面 +] + +// 请求队列 +let requestList = [] +// 是否正在刷新中 +let isRefreshToken = false + +/** + * 响应拦截 + * @param {Object} http + */ +module.exports = vm => { + uni.$u.http.interceptors.response.use( + async res => { + const code = res.data.code || 0 + const msg = res.data.msg || errorCode[code] || errorCode['default'] + + if (ignoreMsgs.indexOf(msg) !== -1) { + // 如果是忽略的错误码,直接返回 msg 异常 + return Promise.reject(msg) + } else if (code === 401) { + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + if (!isRefreshToken) { + isRefreshToken = true + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + if (!vm.$store.getters.refreshToken()) { + vm.$store.commit('CLEAR_LOGIN_INFO') + return Promise.reject(res) + } + // 2. 进行刷新访问令牌 + try { + const refreshTokenRes = await refreshToken() + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + vm.$store.commit('SET_TOKEN', refreshTokenRes.data) + requestList.forEach(cb => cb()) + return uni.$u.http.request(res.config) + } catch (e) { + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 + // 2.2 刷新失败,只回放队列的请求 + requestList.forEach(cb => cb()) + // 登出。即不回放当前请求!不然会形成递归 + vm.$store.commit('CLEAR_LOGIN_INFO') + return Promise.reject(res) + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise(resolve => { + requestList.push(() => { + res.config.header.Authorization = 'Bearer ' + vm.$store.getters.accessToken // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(uni.$u.http.request(res.config)) + }) + }) + } + } else if (code === 500) { + uni.$u.toast(msg) + return Promise.reject(res) + } else if (code === 901) { + uni.$u.toast('演示模式,无法进行写操作') + return Promise.reject(res) + } else if (code !== 0) { + if (msg === '无效的刷新令牌') { + // hard coding:忽略这个提示,直接登出 + console.log(msg) + } else { + uni.$u.toast(msg) + } + return Promise.reject(res) + } else { + return res.data + } + }, + err => { + console.log(err) + let { message } = err + if (!message) { + message = '系统发生未知错误' + }else if (message === 'Network Error') { + message = '后端接口连接异常' + } else if (message.includes('timeout')) { + message = '系统接口请求超时' + } else if (message.includes('Request failed with status code')) { + message = '系统接口' + message.substring(message.length - 3) + '异常' + } + uni.$u.toast(message) + return Promise.reject(err) + } + ) +}