Uniapp请求封装 —— 拦截器思想 | 青训营

1,604 阅读5分钟

写在前面

最近写了在改一些uniapp的项目代码,用到了很多请求,都没有封装。我在网上找了一些封装的方法都比较简单,后面询问了一下师兄,他跟我讲到一些封装思想和工具应该怎么写的一个思维,还引入了java中拦截器转换器的思想,学到了不少东西。

原生请求的实现

uni.request | uni-app官网

uni.request({
    url: 'https://www.example.com/request', //仅为示例,并非真实接口地址。
    data: {
        text: 'uni.request'
    },
    header: {
        'custom-header': 'hello' //自定义请求头信息
    },
    success: (res) => {
        console.log(res.data);
        this.text = 'request success';
    }


data 数据说明

最终发送给服务器的数据是 String 类型,如果传入的 data 不是 String 类型,会被转换成 String。转换规则如下:

  • 对于 GET 方法,会将数据转换为 query string。例如 { name: 'name', age: 18 } 转换后的结果是 name=name&age=18
  • 对于 POST 方法且 header['content-type'] 为 application/json 的数据,会进行 JSON 序列化。
  • 对于 POST 方法且 header['content-type'] 为 application/x-www-form-urlencoded 的数据,会将数据转换为 query string。

简单的promise封装 - 以get请求为例

这里可以用到根路径,但我当时以为项目好像不止一个端口,就直接改成了url,但是可以用直接拼接的。 第一步先将参数结构出来,创建一个Promise对象进行请求,请求成功则再success用resolve给客户端返回参数,失败则将错误信息reject出来。

// fetch.js

// const BaseUrl = 'http://47.115.32.14:8082/screenControl'; 
const get = ({
	url,
	method,
	data
}) => {
	console.log('get')
	return new Promise((resolve, reject) => {
		uni.request({
			url: url,
			method: method,
			data: data,
			success: res => {
				console.log(res);
				const {
					data,
					code,
					msg
				} = res.data;
				if (code === 0) {
					resolve(data);
					return;
				}
				reject({
					message: msg,
					code
				})
			},
			fail: err => {
				reject({
					message: err.msg,
					code: err.code
				})
			}
		})
	})
}
module.exports = {
	get,post
}

调用方式

fetch.get({
	url: '',
	method: 'GET',
	data: {
		token: '',
	}
}).then(res => {
	console.log(res);
});

BASE_URL定义

请求中,query参数是直接拼接再URL后面的。 所以我们首先要对URL进行处理。

const BASE_URL = 'http://47.115.32.14:8082/abc/';

拼接方式1 第一位不是‘/’ path: aaa/bbb/add

real: http://47.115.32.14:8082/abc/aaa/bbb/add

拼接方式2 第一位是‘/’ path: /ccc/ddd/eee

real: http://47.115.32.14:8082/ccc/ddd/eee

方法实现

// 拼接baseurl和query
this._handleUrl = function (path, query) {
        return this._getUrl(path) + Object.keys(query).reduce((curr, next, index, array) => {
            if (index !== 0) {
                curr += '&';
            }
            curr += `${next}=${query[next]}`;
            return curr;
        }, '?')
    }
// 如果拼接的第一个符号是/ 则在根目录进行赋值 则为拼接方式2
this._getUrl = function (path = '') {
        if (!path) {
            throw new Error(`Unexpected url: ${path}`);
        }

        return path.startsWith('/') ? `${this._getHost()}/${path}` : this._baseUrl + path;
    },
// 定义在第三个/的位置进行截断  
this._getHost = function () {
    return this._baseUrl.split('/').slice(0, 3).join('/');
}

拦截器思想

拦截器(Interceptor) Filter 过滤器一样,它俩都是面向切面编程—— AOP 的具体实现。(这个java的我也不是很了解)那我们直接看他们的作用是什么!

Interceptor 作用

  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  2. 权限检查:如登录检测,进入处理器检测是否登录;
  3. 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。
  4. 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。

我们后面的例子中,就将以第四个通用行为作为拦截器的案例。 那其实简单的话来说,拦截器就是在请求发送之前,将请求参数拦截下来,处理后发送请求。当请求响应之后,又将响应参数拦截下来,处理后再返回给客户端。

流程图实现基本思路

image.png

实现

拦截器定义

首先,我们已知拦截器是自动注册的,所以我们可以在调用GET和POST方法的时候就创建对应的拦截器,并把要用到的拦截器注册好。拦截器分为请求拦截器(requestInterceptor)和响应拦截器(responseInterceptor)两种。这里用数组的形式进行存储。

export const ScsService = new HttpClient({
    requestInterceptor: [],
    responseInterceptor: [],
});

HttpClient 参数

在HttpClient写对应的请求封装代码。由于JS中没有规定参数的b类型和可见范围。我们约定将不愿意给用户使用的方法前面加上一个下划线,eg:_baseUrl。同时对参数进行初始化nullundefined处理。

语法格式为a ?? b。其意义为:当a为 null 或者 undefined 时,返回 b 的值。

this._baseUrl = config.baseUrl ?? '';

this._requestInterceptor = config.requestInterceptor ?? [];

this._responseInterceptor = config.responseInterceptor ?? [];

拦截器例子

方法都放在intercept中。

TokenRequestInterceptor

大多数请求都需要带上身份认证token,所以我们可以默认在每一个请求都加上token参数。已知token是放在url进行拼接的,我们可以先判断当前路径是否存在参数来决定拼接的字符是还是&

const TokenRequestInterceptor = function () {
	
    this.intercept = function (params) {
		console.log("addToken");
		// if(params.url.indexOf('token')!==-1){
			params.url = params.url + (params.url.indexOf('?') > 0 ? '&' : '?') + 'token=' + uni.getStorageSync("user")?.token || '';
		// }
    }
}

JsonRequestInterceptor

请求参数传入是JSON格式的,所以我们可以将参数传进去转化成JSON格式再发送请求。

const JsonRequestInterceptor = function () {

    this.intercept = function (params) {
        params.data = JSON.stringify(params.data);
    }
}

LogRequestInterceptor

打印请求参数,用于调试。

const LogRequestInterceptor = function () {

    this.intercept = function (params) {
        console.log('请求参数:', params)
    }
}

LogResponseInterceptor

打印响应参数,用于调试。

const LogResponseInterceptor = function () {

    this.intercept = function (params) {
        console.log('响应数据::', params)
    }
}

CommonResponseInterceptor

将响应参数转化成对象的形式,并将data解构出来传给客户端。

const CommonResponseInterceptor = function () {

    this.intercept = function (response = {
        data: ''
    }) {
        response.data = JSON.parse(response.data)
        const {
            code,
            data,
            msg
        } = response.data;
        if (code === 0) {
            response.data = data;
            return;
        }

        throw response.data;
    }
}

前面我们定义了拦截器是数组,那么我们可以for循环链式调用里面的intercept方法。

this._requestInterceptor.forEach(interceptor => interceptor.intercept(config));
for (let intercepter of this._responseInterceptor) {
    try {
        intercepter.intercept(res);
    } catch (err) {
        reject({
            msg: err.msg,
            code: err.code,
            data: err.data,
        });
        return;
    }
}

以GET请求为例

this.get = function (params) {
        const filledParams = {
            header: {},
            query: {},
            ...params,
        }
        return new Promise((resolve, reject) => {
            const {
                path,
                header,
                query
            } = filledParams;

            const config = {
                url: this._handleUrl(path, query),
                header,
            };

            this._requestInterceptor.forEach(interceptor => interceptor.intercept(config));
            uni.request({
                method: "GET",
                dataType: 'xml',
                ...config,
                success: res => {
					console.log(666,res);
                    for (let intercepter of this._responseInterceptor) {
                        try {
                            intercepter.intercept(res);
                        } catch (err) {
                                reject({
                                msg: err.msg,
                                code: err.code,
                                data: err.data,
                            });
                            return;
                        }
                    }
					let result=this._dataConverterFactory.responseBodyConverter().convert(res.data);
                    resolve(result.data);
                },
                fail (err) {
                    reject({
                        msg: err.errMsg,
                    })
                },
            })
        })
    }

调用

ScsService.get({
	path: 'aaa/bbb',
	query: {
		name: ,
		password: 
	},
	header: {
		aaa:bbb
	}
}).then(res => {
	console.log(res);
}).catch(err => {
	console.log(err);
})

参考:

  1. Spring Boot拦截器(Interceptor)详解

完整代码:

  1. github.com/tiffany886/…