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==' } 
  • 在main.js中导入、挂载project.config.js
 // 导入js文件 import ProjectConfig from './project.config'  // 挂载 Vue.prototype.$ProjectConfig = ProjectConfig 
  • 在项目中引用
this.$ProjectConfig.APP_API_BASE_URL 
  • 如果是在APP挂在前引用,那么使用以下方法引用
import ProjectConfig from './project.config' 
2、打开HBuilderX终端命令窗口,用于执行yarn安装命令

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

  • 按快捷键Ctrl+Alt+T打开终端窗口
  • 菜单栏中,选择 视图 > 显示终端(C)
3、执行安装axios(http请求拦截)和 axios-auth-refresh(强大的token刷新)组件命令
yarn add axios yarn add axios-auth-refresh 
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 } 
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、新建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   }) } 

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

<!-- 蓝色简洁登录页面 --> <template> 	<view class="login-bg"> 		<br /><br /><br /><br /><br /><br /><br /> 		<view class="t-login"> 			<form class="cl"> 				<view class="t-a"> 					<image src="@/static/login/user.png"></image> 					<input type="text" name="username" placeholder="请输入手机号码" maxlength="11" v-model="username" /> 				</view> 				<view class="t-a"> 					<image src="@/static/login/pwd.png"></image> 					<input type="password" name="password" maxlength="100" placeholder="请输入密码" v-model="password" /> 				</view> 				<button @tap="login()">登 录</button> 				<view class="t-c"> 					<text class="t-c-txt" @tap="reg()">注册账号</text> 					<text @tap="forgotPwd()">忘记密码</text> 				</view> 			</form> 			<view class="t-f"><text>—————— 其他登录方式 ——————</text></view> 			<view class="t-e cl"> 				<view class="t-g" @tap="wxLogin()"><image src="@/static/login/wx2.png"></image></view> 				<view class="t-g" @tap="zfbLogin()"><image src="@/static/login/qq2.png"></image></view> 				<view class="t-g" @tap="zfbLogin()"><image src="@/static/login/wb.png"></image></view> 			</view> 		</view> 		<image class="img-a" src="@/static/login/bg1.png"></image> 	</view> </template> <script> import md5 from '@/common/md5.min.js'; import { mapActions } from 'vuex' import { ACCESS_TOKEN, REFRESH_ACCESS_TOKEN } from '@/store/mutation-types' export default { 	data() { 		return { 			username: '', 			password: '', 			grant_type: 'password' 		}; 	}, 	onLoad() {}, 	methods: { 		...mapActions(['Login', 'Logout']), 		login() { 			var that = this; 			if (!that.username) { 				uni.showToast({ title: '请输入手机号', icon: 'none' }); 				return; 			} 			if (!/^[1][3,4,5,7,8,9][0-9]{9}$/.test(that.username)) { 				uni.showToast({ title: '请输入正确手机号', icon: 'none' }); 				return; 			} 			if (!that.password) { 				uni.showToast({ title: '请输入密码', icon: 'none' }); 				return; 			} 			const loginParams = {} 			loginParams.username = that.username 			loginParams.grant_type = 'password' 			loginParams.password = md5(that.password) 			that.Login(loginParams) 			  .then((res) => this.loginSuccess(res)) 			  .catch(err => this.requestFailed(err)) 			  .finally(() => {  			}) 		}, 		loginSuccess (res) { 		  // 判断是否记住密码 		  uni.showToast({ title: '登录成功!', icon: 'none' }); 		  uni.switchTab({ 		  	url: '/pages/tabBar/component/component', 			fail(err) { 				console.log(err) 			} 		  }) 		}, 		requestFailed (res) { 		 // 判断是否记住密码 		  uni.showToast({ title: '登录失败:' + res.msg, icon: 'none' }); 		}, 		//忘记密码 		forgotPwd() { 			uni.showToast({ title: '忘记密码', icon: 'none' }); 		}, 		//立刻注册 		reg() { 			uni.showToast({ title: '注册账号', icon: 'none' }); 		} 	} }; </script> <style> .img-a { 	width: 100%; 	position: absolute; 	bottom: 0; } .login-bg { 	height: 100vh; 	background-image: url(/static/login/bg3.png); } .t-login { 	width: 580rpx; 	padding: 55rpx; 	margin: 0 auto; 	font-size: 28rpx; 	background-color: #ffffff; 	border-radius: 20rpx; 	box-shadow: 0 5px 7px 0 rgba(0, 0, 0, 0.15); 	z-index: 9; } .t-login button { 	font-size: 28rpx; 	background: linear-gradient(to right, #ff8f77, #fe519f); 	color: #fff; 	height: 90rpx; 	line-height: 90rpx; 	border-radius: 50rpx; }  .t-login input { 	padding: 0 20rpx 0 120rpx; 	height: 90rpx; 	line-height: 90rpx; 	margin-bottom: 50rpx; 	background: #f6f6f6; 	border: 1px solid #f6f6f6; 	font-size: 28rpx; 	border-radius: 50rpx; } .t-login .t-a { 	position: relative; } .t-login .t-a image { 	width: 40rpx; 	height: 40rpx; 	position: absolute; 	left: 40rpx; 	top: 28rpx; 	padding-right: 20rpx; } .t-login .t-b { 	text-align: left; 	font-size: 46rpx; 	color: #000; 	padding: 300rpx 0 120rpx 0; 	font-weight: bold; } .t-login .t-d { 	text-align: center; 	color: #999; 	margin: 80rpx 0; } .t-login .t-c { 	text-align: right; 	color: #666666; 	margin: 30rpx 30rpx 40rpx 0; } .t-login .t-c .t-c-txt { 	margin-right: 300rpx; } .t-login .t-e { 	text-align: center; 	width: 600rpx; 	margin: 40rpx auto 0; } .t-login .t-g { 	float: left; 	width: 33.33%; }  .t-login .t-e image { 	width: 70rpx; 	height: 70rpx; } .t-login .t-f { 	text-align: center; 	margin: 80rpx 0 0 0; 	color: #999; } .t-login .t-f text { 	margin-left: 20rpx; 	color: #b9b9b9; 	font-size: 27rpx; } .t-login .uni-input-placeholder { 	color: #aeaeae; } .cl { 	zoom: 1; } .cl:after { 	clear: both; 	display: block; 	visibility: hidden; 	height: 0; 	content: '20'; } </style> 
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('已登录'); 			} 

三、在手机模拟器中运行并预览登录界面

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

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

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

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

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

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

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

源码地址:

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

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

发表评论

评论已关闭。

相关文章