山东大学项目实训(二)—— 请求封装和双TOKEN机制

前后端分离 —— 微信小程序请求封装

  • 工欲善其事必先利其器
  • 请求封装
  • 请求封装后的使用方式
  • 效果展示
  • 总结(双TOKEN机制)

工欲善其事必先利其器

在实现前端页面和功能之前, 首先需要确保我们(小程序前端)向服务器发起的网络请求能够得到确认和响应,不然一切都是徒劳。 因此,通过 uniapp 官方文档可以看到 —— 官方向我们提供了网络请求的API 。见下图:
山东大学项目实训(二)—— 请求封装和双TOKEN机制_第1张图片经测试(为了体现测试的真实性,并没有在本机上进行测试,而是购买了一台服务器,将后端测试代码和数据库中的数据部署在购买的服务器中,访问的BASE_URL是服务器的外网IP:端口号)
使用官方文档提供的API能够得到我们服务器的确认和响应。

请求封装

但是,官方文档提供的API并不能满足我们的需求 —— 基于前期的需求分析,为了保证用户数据的安全性和用户体验的良好性,团队决定采取并使用 “双TOKEN” 的网络请求机制。这一机制决定了我们需要对请求进行拦截 —— 在请求头上添加TOKEN。 故需要对官方文档提供的API进行再次封装以满足我们的需求。经结合相关资料,对uniapp网络请求进行了封装。详细介绍在代码中的注释上。

/**
 * 通用uni-app网络请求
 * 基于 Promise 对象实现更简单的 request 使用方式,支持请求和响应拦截
 */

export default {
	// 请求配置中心
	config: {
	    // 服务器url: "http://外网IP地址:端口号/"  
	    // 本机url: "http://localhost:8080/"
		baseUrl: "http://xxx.xxx.xxx.xxx:xxx/", 
		// 请求头
		header: {
			'Content-Type': 'application/json;charset=UTF-8',
			'Content-Type': 'application/x-www-form-urlencoded'
		},
		// 请求携带的数据
		data: {},
		// 请求默认方法为GET请求
		method: "GET",
		// 数据类型为JSON
		dataType: "json",
		/* 如设为json,会对返回的数据做一次 JSON.parse */
		responseType: "text",
		// 请求成功后调用的方法
		success() {},
		// 请求失败后调用的方法
		fail() {},
		// 请求完成时调用的方法
		complete() {}
	},
	// 请求和响应拦截器
	interceptor: {
		request: null,
		response: null
	},
	// 封装官方文档提供的API:uni.request
	request(options) {
		if (!options) {
			options = {}
		}
		// 请求配置参数
		options.baseUrl = options.baseUrl || this.config.baseUrl
		options.dataType = options.dataType || this.config.dataType
		options.url = options.baseUrl + options.url
		options.data = options.data || {}
		options.method = options.method || this.config.method
		
		// 返回Promise
		return new Promise((resolve, reject) => {
			let _config = null
			// 设置响应拦截器
			options.complete = (response) => {
				let statusCode = response.statusCode
				response.config = _config		
				if (this.interceptor.response) {
					let newResponse = this.interceptor.response(response)
					if (newResponse) {
						response = newResponse
					}
				}
				// 请求成功
				if (statusCode === 200) { 
					resolve(response);
				} else { // 请求失败
					uni.showToast({
						title: "未知错误",
						icon: 'error',
						duration: 2000,
						mask: true
					})
					reject(response)
				}
			}
			// 请求配置
			_config = Object.assign({}, this.config, options)
			_config.requestId = new Date().getTime()
			// 设置请求拦截器
			if (this.interceptor.request) {
				this.interceptor.request(_config)
			}
			// 调用官方文档提供的API,发起请求
			uni.request(_config);
		});
	},
	// 以下请求可以单独调用
	// GET 请求
	get(url, data, options) {
		if (!options) {
			options = {}
		}
		options.url = url
		options.data = data
		options.method = 'GET'
		return this.request(options)
	},
	// POST 请求
	post(url, data, options) {
		if (!options) {
			options = {}
		}
		options.url = url
		options.data = data
		options.method = 'POST'
		return this.request(options)
	},
	// PUT 请求
	put(url, data, options) {
		if (!options) {
			options = {}
		}
		options.url = url
		options.data = data
		options.method = 'PUT'
		return this.request(options)
	},
	// DELETE 请求
	delete(url, data, options) {
		if (!options) {
			options = {}
		}
		options.url = url
		options.data = data
		options.method = 'DELETE'
		return this.request(options)
	}
}

请求封装后的使用方式

/**
 * 将业务所有接口统一起来便于维护
 */
import http from './interface'

export const test = (data) => {
	// 设置请求拦截器
	http.interceptor.request = (config) => {
		console.log('请求拦截', config)
		config.header = {
			// 在发起请求之前,让请求的header携带TOKEN
 			"token": uni.getStorageSync('access_token')
 		}
	}
	// 设置响应拦截器
	http.interceptor.response = (response) => {
		if (response.code === 200) {
			// 请求成功的执行逻辑
			console.log('响应拦截', response)			
		} else { // 请求失败的执行逻辑
			uni.showToast({
				title: response.msg,
				icon: 'none',
				duration: 2000,
				mask: true
			})
		}				
		return response;
	}
	// 发起test请求
	return http.request({
		baseUrl: 'http://localhost:8080/',
		url: 'test',
		method: 'POST'
		data: data
	})
}

/** 
 *  登录业务
 */
export const login = (username, password) => {
	// 设置响应拦截器
	http.interceptor.response = (response) => {
		// 登录成功,则将两个 token 存在用户手机磁盘上,并跳转到首页
		if (response.code == 200) {
			uni.setStorageSync('access_token', response.data.access_token)
			uni.setStorageSync('refresh_token', response.data.refresh_token)
			uni.switchTab({
				url: '/pages/index/index'
			})
		} else { // 登录失败则弹窗提示相应消息给用户
			uni.showToast({
				title: response.msg,
				icon: 'none',
				duration: 2000,
				mask: true
			})
		}
		return response;
	}
	// 发起登录请求
	return http.request({
		url: 'xxx/' + username +'/' + password,
		method: 'POST',
	})
}

// 默认全部导出  import api from '@/common/http/api'
export default {
	test,
	login
}

效果展示

首先,在登录之前是没有双TOKEN字段的
山东大学项目实训(二)—— 请求封装和双TOKEN机制_第2张图片发起登录请求成功后,根据业务逻辑,设置双TOKEN到磁盘上,同时跳转到首页。 可以看到响应拦截打印出来的请求配置参数,由于后端的代码是测试代码,因此没有严格按照POST请求进行登录验证,而采用了GET
山东大学项目实训(二)—— 请求封装和双TOKEN机制_第3张图片

总结(双TOKEN机制)

可以看到,我们的“武器还是很锋利的” —— 微信小程序的请求封装还是很成功的。 说明我们不仅能够向服务器发起请求,还能对所有的请求和响应进行拦截,从而实现请求和响应方面相应的业务逻辑。因此,现在只需要专注于页面业务逻辑的实现即可。

为什么现在只需要专注页面业务逻辑即可呢?
比如:“双TOKEN” 网络请求机制的业务逻辑 —— 用户发起带ACCESS_TOKEN的请求,成功了没啥要说的。
主要是ACCESS_TOKEN过期后的逻辑: ACCESS_TOKEN过期后,会从服务器返回的响应中接收到相应的状态码(比如405),前端收到405后,会发起携带REFRESH_TOKEN的请求,让服务器重新发俩新的TOKEN回来。正常情况下服务器会返回俩新的TOKEN,但是假如REFRESH_TOKEN也过期了,那没办法,用户只能重新登录了。这请求和响应方面的逻辑在后期都可以进行修改,并不影响页面业务逻辑。

既然说到了 “双TOKEN” 网络请求机制, 那就再讲一下我们团队为什么选择这样一个机制:

众所周知,携带TOKEN的优势:能够减少敏感数据在网络上进行传递,从而保证用户数据的安全性 —— “你”登录后,“你”每次发起的请求都是携带TOKEN的,不仅很少有其他敏感数据在网上进行传递,因为携带TOKEN还能让服务器知道“你”是谁。同时在一定程度上可用保证用户体验 —— 比如自动登录,只需要存在TOKEN并且TOKEN不过期,那么用户可以直接登录,不需要每次进来都重新输入用户名和密码登录,也保证了一定的安全性。

但是 “单TOKEN机制并不完美” —— 假如发起的请求携带TOKEN过期了,那么会强制用户重新登录,假如用户用几个小时编写完文档,美滋滋的发起提交请求,然后因为TOKEN过期了,你居然让用户重新登录,并且文档数据还没有保存,那不是裂开了吗? 而双TOKEN机制就可以尽可能的避免这种情况的发生,从而提供良好的用户体验。

你可能感兴趣的:(智慧医院不良事件精细化管理平台,前端框架,vue,js,html5)