• SpringCloud微服务实战——搭建企业级开发框架(四十七):【移动开发】整合uni-app搭建移动端快速开发框架-添加Axios并实现登录功能


      uni-app自带uni.request用于网络请求,因为我们需要自定义拦截器等功能,也是为了和我们后台管理保持统一,这里我们使用比较流行且功能更强大的axios来实现网络请求。

      Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。

    Axios特性:
    • 从浏览器创建 XMLHttpRequests
    • 从 node.js 创建 http 请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据
    • 取消请求
    • 自动转换JSON数据
    • 客户端支持防御XSRF
    一、安装axios和axios-auth-refresh组件
    1、新增uni-app自定义常量配置文件

      HBuilderX有针对于uni-app的通用配置,很多通用常量可以直接配置在manifest.json文件中,且HBuilderX提供图形化配置界面。但我们有许多业务系统相关的常量配置,那么就需要一个自定义常量配置文件。
      uni-app中定义全局常量有多种方式,在vue.js框架中也可以使用App.vue里面的globalData,根据业务需求,自定义常量可能会很多,不便于和官方配置融合在一起,所以这里使用新增配置project.config.js文件并挂载Vue.prototype的方式来实现常量配置。

    • 在工程的根目录下新增project.config.js
     module.exports = {
            # 配置请求后台地址
    	APP_API_BASE_URL: 'http://127.0.0.1:8080',
    	# 多租户项目,这里是默认的租户id
    	APP_TENANT_ID: '0',
    	# OAuth2授权的用户名密码
    	APP_CLIENT_ID: 'gitegg-admin',
    	# client_id:client_secret加密后的值,直接传,不需要再进行BASE64加密
    	APP_CLIENT_SECRET: 'Z2l0ZWdnLWFkbWluOjEyMzQ1Ng=='
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 在main.js中导入、挂载project.config.js
     // 导入js文件
    import ProjectConfig from './project.config'
     // 挂载
    Vue.prototype.$ProjectConfig = ProjectConfig
    
    • 1
    • 2
    • 3
    • 4
    • 在项目中引用
    this.$ProjectConfig.APP_API_BASE_URL
    
    • 1
    • 如果是在APP挂在前引用,那么使用以下方法引用
    import ProjectConfig from './project.config'
    
    • 1
    2、打开HBuilderX终端命令窗口,用于执行yarn安装命令

    HBuilderX默认没有开启终端命令窗口,选中项目,有两种方式打开命令窗口:

    • 按快捷键Ctrl+Alt+T打开终端窗口
    • 菜单栏中,选择 视图 > 显示终端©
    3、执行安装axios(http请求拦截)和 axios-auth-refresh(强大的token刷新)组件命令
    yarn add axios
    yarn add axios-auth-refresh
    
    • 1
    • 2
    4、在目录/common/utils新建axios.js,创建Axios 实例
    const VueAxios = {
      vm: {},
      // eslint-disable-next-line no-unused-vars
      install (Vue, instance) {
        if (this.installed) {
          return
        }
        this.installed = true
        if (!instance) {
          // eslint-disable-next-line no-console
          console.error('You have to install axios')
          return
        }
        Vue.axios = instance
        Object.defineProperties(Vue.prototype, {
          axios: {
            get: function get () {
              return instance
            }
          },
          $http: {
            get: function get () {
              return instance
            }
          }
        })
      }
    }
    export {
      VueAxios
    }
    
    • 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
    5、在目录/common/utils新建request.js,自定义Axios拦截器

    在这里定义拦截器主要用于:自动设置token、token过期刷新、统一异常提示、返回数据处理等功能。

    import axios from 'axios'
    import createAuthRefreshInterceptor from 'axios-auth-refresh'
    import store from '@/store'
    import { serialize } from '@/common/util'
    import { VueAxios } from './axios'
    import { ACCESS_TOKEN, REFRESH_ACCESS_TOKEN } from '@/store/mutation-types'
     // 导入js文件
    import ProjectConfig from '@/project.config.js'
    
    // uni-app适配
    axios.defaults.adapter = function(config) {
      return new Promise((resolve, reject) => {
          var settle = require('axios/lib/core/settle');
          var buildURL = require('axios/lib/helpers/buildURL');
          uni.request({
              method: config.method.toUpperCase(),
              url: config.baseURL + buildURL(config.url, config.params, config.paramsSerializer),
              header: config.headers,
              data: config.data,
              dataType: config.dataType,
              responseType: config.responseType,
              sslVerify: config.sslVerify,
              complete: function complete(response) {
                  response = {
                      data: response.data,
                      status: response.statusCode,
                      errMsg: response.errMsg,
                      header: response.header,
                      config: config
                  };
                  settle(resolve, reject, response);
              }
          })
      })
    }
    
    // 创建 axios 实例
    const request = axios.create({
      // API 请求的默认前缀
      baseURL: ProjectConfig.APP_API_BASE_URL,
      timeout: 30000 // 请求超时时间
    })
    
    // 当token失效时,需要调用的刷新token的方法
    const refreshAuthLogic = failedRequest =>
      axios.post(ProjectConfig.APP_API_BASE_URL + '/oauth/token',
      serialize({
          grant_type: 'refresh_token',
          refresh_token: uni.getStorageSync(REFRESH_ACCESS_TOKEN)
        }),
        {
          headers: { 'TenantId': ProjectConfig.APP_TENANT_ID, 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + ProjectConfig.APP_CLIENT_SECRET },
          skipAuthRefresh: true // 刷新token请求过期,不再进行刷新
        }
        ).then(tokenRefreshResponse => {
          if (tokenRefreshResponse.status === 200 && tokenRefreshResponse.data && tokenRefreshResponse.data.success) {
            const result = tokenRefreshResponse.data.data
            uni.setStorageSync(ACCESS_TOKEN, result.tokenHead + result.token, result.expiresIn * 1000)
            uni.setStorageSync(REFRESH_ACCESS_TOKEN, result.refreshToken, result.refreshExpiresIn * 1000)
            failedRequest.response.config.headers['Authorization'] = result.tokenHead + result.token
          } else if (tokenRefreshResponse.status === 200 && tokenRefreshResponse.data &&
            !tokenRefreshResponse.data.success && tokenRefreshResponse.data.code === 401) {
              store.dispatch('Timeout').then(async () => {
    			uni.navigateTo({
    				url: '/pages/login/login'
    			})
            })
          }
          return Promise.resolve()
    })
    
    // 初始化刷新token拦截器
    createAuthRefreshInterceptor(request, refreshAuthLogic, {
      pauseInstanceWhileRefreshing: true // 当刷新token执行时,暂停其他请求
    })
    
    // 异常拦截处理器
    const errorHandler = (error) => {
      if (error.response) {
        const data = error.response.data
        if (error.response.status === 403) {
    	  uni.showToast({
    	  	title: '您没有权限访问此接口',
    	  	icon:'error',
    		duration: 2000
    	  });
        } else if (error.response.status === 401 && !(data.result && data.result.isLogin)) {
           // 当刷新token超时,则调到登录页面
    	   uni.showModal({
    		title: '登录超时',
    	   	content: '由于您长时间未操作, 为确保安全, 请重新登录系统进行后续操作 !',
    		confirmText: '重新登录',
    	   	showCancel: false,
    		success: (res) => {
    			if(res.confirm) {  
    				store.dispatch('Timeout').then(() => {
    				    uni.navigateTo({
    				    	url: '/pages/login/login'
    				    })
    				})
    			} 
    		} 
    	   })
        }
      }
      return Promise.reject(error)
    }
    
    // request interceptor
    request.interceptors.request.use(config => {
      const token = uni.getStorageSync(ACCESS_TOKEN)
      // 如果 token 存在
      // 让每个请求携带自定义 token 请根据实际情况自行修改
      if (token && config.authenticationScheme !== 'Basic') {
        config.headers['Authorization'] = token
      }
      config.headers['TenantId'] = ProjectConfig.APP_TENANT_ID
      return config
    }, errorHandler)
    
    // response interceptor
    request.interceptors.response.use((response) => {
      const res = response.data
      if (res && res.code) {
        if (res.code !== 200) {
    	  uni.showToast({
    	  	title: '操作失败: ' + res.msg,
    	  	icon:'error',
    	  	duration: 2000
    	  });
          return Promise.reject(res || 'Error')
        } else {
          return response.data
        }
      } else {
        return response
      }
    }, errorHandler)
    
    const installer = {
      vm: {},
      install (Vue) {
        Vue.use(VueAxios, request)
      }
    }
    
    export default request
    
    export {
      installer as VueAxios,
      request as axios
    }
    
    
    • 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
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    二、请求后台接口并实现登录功能
    1、新建api目录,用于存放所有后台请求的接口,在api目录下新建login目录,存放用于登录的相关接口
    2、在/api/login目录下新增login.js
    import request from '@/common/utils/request'
    import ProjectConfig from '@/project.config.js'
    
    const loginApi = {
      // 登录
      Login: '/oauth/token',
      // 退出登录
      Logout: '/oauth/logout',
      // 获取系统配置的验证码类型
      CaptchaType: '/oauth/captcha/type',
      // 获取图片验证码
      ImageCaptcha: '/oauth/captcha/image',
      // 发送短信验证码
      SendSms: '/oauth/sms/captcha/send',
      // 获取用户信息
      UserInfo: '/system/account/user/info',
      // 第三方登录
      SocialLoginUrl: '/oauth/social/login/',
      // 第三方登录回调
      SocialLoginCallback: '/oauth/social/',
      // 第三方用户绑定---通过手机号验证码绑定
      SocialBindMobile: '/oauth/social/bind/mobile',
      // 第三方用户绑定---通过账号密码绑定
      SocialBindAccount: '/oauth/social/bind/account',
      // 发送短信验证码
      SmsSend: '/extension/sms/code/send',
      // 校验短信验证码
      SmsCheckPre: '/extension/sms/check/code/pre',
      // 校验短信验证码
      SmsCheck: '/extension/sms/check/code',
      // 发送注册短信
      SmsRegisterSend: '/system/account/register/sms/send',
      // 账户注册
      Register: '/system/account/register',
      // 校验用户是否存在
      CheckUserExist: '/system/account/register/check'
    }
    
    export default loginApi
    
    /**
     * OAuth2登录
     * @param parameter
     * @returns {*}
     */
    export function login (parameter) {
      return request({
        url: loginApi.Login,
        authenticationScheme: 'Basic',
        method: 'post',
        headers: { 'Authorization': 'Basic ' + ProjectConfig.APP_CLIENT_SECRET },
        skipAuthRefresh: true,
        data: parameter
      })
    }
    
    /**
     * OAuth2退出登录
     * @param parameter
     * @returns {*}
     */
    export function logout (parameter) {
      return request({
        url: loginApi.Logout,
        method: 'post',
        skipAuthRefresh: true,
        data: parameter
      })
    }
    
    /**
     * 获取验证码类型
     * @param parameter
     * @returns {*}
     */
    export function getCaptchaType () {
      return request({
        url: loginApi.CaptchaType,
        method: 'get'
      })
    }
    
    /**
     * 获取图片验证码
     * @param parameter
     * @returns {*}
     */
    export function getImageCaptcha () {
      return request({
        url: loginApi.ImageCaptcha,
        method: 'get'
      })
    }
    
    /**
     * 获取短信验证码
     * @param parameter
     * @returns {*}
     */
    export function getSmsCaptcha (parameter) {
      return request({
        url: loginApi.SendSms,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 获取用户信息
     * @param parameter
     * @returns {*}
     */
    export function getInfo () {
      return request({
        url: loginApi.UserInfo,
        method: 'get'
      })
    }
    
    /**
     * 获取第三方登录的URL
     * @param {Object} socialType
     */
    export function getSocialLoginUrl (socialType) {
      return request({
        url: loginApi.SocialLoginUrl + socialType,
        method: 'get'
      })
    }
    
    /**
     * 第三方登录回调地址
     * @param {Object} socialType
     * @param {Object} parameter
     */
    export function socialLoginCallback (socialType, parameter) {
      return request({
        url: loginApi.SocialLoginCallback + socialType + '/callback',
        method: 'get',
        params: parameter
      })
    }
    
    /**
     * 发送短信验证码
     * @param {Object} parameter
     */
    export function sendSmsCode (parameter) {
      return request({
        url: loginApi.SmsSend,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 校验短信验证码
     * @param {Object} parameter
     */
    export function checkSmsCode (parameter) {
      return request({
        url: loginApi.SmsCheckPre,
        method: 'get',
        params: parameter
      })
    }
    
    /**
     * 发送注册短信验证码
     * @param {Object} parameter
     */
    export function smsRegisterSend (parameter) {
      return request({
        url: loginApi.SmsRegisterSend,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 校验用户是否存在
     * @param {Object} parameter
     */
    export function checkUserExist (parameter) {
      return request({
        url: loginApi.CheckUserExist,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 用户注册
     * @param {Object} parameter
     */
    export function userRegister (parameter) {
      return request({
        url: loginApi.Register,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 第三方用户绑定---通过手机号验证码绑定
     * @param {Object} parameter
     */
    export function userBindMobile (parameter) {
      return request({
        url: loginApi.SocialBindMobile,
        method: 'post',
        data: parameter
      })
    }
    
    /**
     * 第三方用户绑定---通过账号密码绑定
     * @param {Object} parameter
     */
    export function userBindAccount (parameter) {
      return request({
        url: loginApi.SocialBindAccount,
        method: 'post',
        data: parameter
      })
    }
    
    • 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
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226

    3、在/pages目录下创建login目录,新增login.vue登录页面,用于登录。

    
    
    
    
    
    • 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
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    4、将页面中用到的图片,复制到/static/login目录下
    5、配置pages.json文件,将新增的login.vue文件目录加入到配置中。pages.json类似于vue.js工程下的路由页面配置
    6、在App.vue文件的onLaunch方法中新增判断,当token为空时,跳转到我们刚刚新建的登录界面。
    			const token = uni.getStorageSync(ACCESS_TOKEN)
    			if(!token || token === ''){
    				uni.navigateTo({
    					url: '/pages/login/login'
    				})
    			} else {
    				console.log('已登录');
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    三、在手机模拟器中运行并预览登录界面

      上文中介绍了如果配置HBuilderX连接手机模拟器,预览并调试uni-app项目,这里我们通过以上配置和编写,实现了登录界面,现在我们可以在手机模拟器中查看刚刚写的登录页面了。

    1、启动手机模拟器 > 双击桌面的nox_adb快捷方式
    2、在HBuilder X中依次点击 运行 -> 运行到手机或模拟器 -> 运行到Android App基座

    image.png

    3、弹出框会显示我们已连接的模拟器,点击运行,HBuilderX就可以自动打包app发布到模拟器中运行,并可以在HBuilderX控制台查看运行日志。

    image.png

    4、在手机模拟器展示的登录界面中,输入我们系统用户的手机号码 + 密码,登录成功后即可跳转到登录后的界面。

    image.png
    image.png

    GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地址:

    Gitee: https://gitee.com/wmz1930/GitEgg
    GitHub: https://github.com/wmz1930/GitEgg

    欢迎感兴趣的小伙伴Star支持一下。

  • 相关阅读:
    Hive DQL及优化
    Linux 文件系统(VFS、EXT、proc)
    2022年全国职业院校技能大赛:网络系统管理项目-模块B--Windows样题8
    Thinkpad T14升级Windows11ver22h2失败问题解决小记
    Redis客户端Lettuce深度分析介绍
    如何制作gif图片?
    无涯教程-JavaScript - ATAN函数
    【字符串】【字符串和字符数组的相互转化】Leetcode 541 反转字符串 II
    【二叉树的顺序结构:堆 && 堆排序 && TopK]
    java数组
  • 原文地址:https://blog.csdn.net/wmz1932/article/details/127550125