个人网站:aijianli.site/ 可以免费在线制作简历,提供PDF下载,方便快捷。
vqa (view query api) 是一个后端查询组件,方便写 sql 查询,能减少在后端代码中增加业务代码。前端调用接口的方式比较复杂。因此使用ES6的反射和代理对调用方式进行改造,实现方便调用。
一、原始的vqa接口调用方式
js
function findStatsTable() {
let params = {
method: "stats-service-stats-table.findTable",
datas: {
st_id: "1234567890",
}
};
this.axios.post("/stats/vqa-query", params).then(res => {
console.log(res)
})
}
存在的问题:
- 参数params太复杂,并且同一个模块有很多vqa,它们的 params.method 中的前缀(stats-service-stats-table)是相同的,只有方法名不同,每次查询都要复制相同的内容,后期如有改变很难维护
- 同一个服务的接口地址(/stats/vqa-query)相同,每次都要写相同的字符串,太多重复。
二、改进的vqa调用方式
js
function findStatsTable() {
let params = {
st_id: "1234567890",
};
this.$statsTable.findTable(params).then(res => {
console.log(res)
})
}
改进后的优点:
- 调用简单,只需要关注请求参数、
- 易维护,不会重复在不同的地方出现stats-service-stats-table,/stats/vqa-query等字符串
三、实现代码
3.1 类图结构设计
3.2 具体实现
其中,ParentInterface中的 _sendVqaRequest 方法内容如下,这里就是封装的原始的vqa的方法
js
class ParentInterface {
/*......*/
/**
* 项vqa接口发送请求, 注意vqa的请求都是 post 请求
* @param {Object} params 对应查询的 method 的 datas 中需要的参数
* @param {String} method 查询的 vqa 方法,不需要方法前缀,只需要方法名
* @returns
*/
_sendVqaRequest(params, method) {
params = this._getParams(params, method);
return this[axiosSym].post(this[vqaUrlSym], params).then(res => {
return res.data.content;
})
}
/*......*/
}
为了实现调用类中不存在的方法时都统一调用 _sendVqaRequest 方法,需要使用到ES6的反射和代理,这才是实现此功能的关键。具体实现如下:
js
/**
* 初始化所有接口
* @param {*} app Vue
* @param {*} axios 请求实例
*/
function initInterfaces(app, axios) {
const statsTable = createInterfaceProxy(new StatsTable(axios));
const interfaces = {
statsTable
};
for (let interfac in interfaces) {
app.prototype[`$${interfac}`] = interfaces[interfac];
}
}
核心代码
js
/**
* 创建接口代理,处理vqa的查询,调用vqa方式时,获取调用方法的名称调用后台vqa
* @param {Object} target 被代理的对象,这里是接口的实例
* @returns 返回一个代理对象
*/
function createInterfaceProxy(target) {
let hander = {
get(target, key) {
let vqaMethod = Reflect.get(target, key);
if (vqaMethod) {
return vqaMethod;
}
vqaMethod = Reflect.get(target, "_sendVqaRequest"); //写死,所有vqa的查询调用的都是 _sendVqaRequest 方法
let vqaProxy = new Proxy(vqaMethod, {
apply(funtarget, funthis, args) {
let params = [...args, key]
return Reflect.apply(funtarget, funthis, params)
}
})
return vqaProxy;
}
}
return new Proxy(target, hander)
}
如下代码,通过ES6的代理和反射,实现对target对象的代理。
通过代理target的get方法,当调用target调用自身方法时,如果调用的方法在target中原本就存在,则直接返回方法。若不存在,则返回一个代理对象,这个代理对象代理的是target中的一个公用的方法。由此实现当调用一个对象中不存在的方法时,都会统一调用到一个特定的公用方法。
3.3 部分代码展示
3.3.1 ParentInterface类
js
/**
* 所有接口类的父类,提供所有接口公用的方法
* 注意:每个继承此父接口的具体类必须提在config文件中config对象中添加如下配置
*
* 类型: {
* interfacePrefix: xxx, //后台接口的前缀
* }
*
* 例如 快照的 接口类配置如下
* StatsSnapshot: {
interfacePrefix: "/stats/snapshots",
}
*
* @author hebing
* @date 2021/03/09
*/
import config from "./config.js";
//使用symbol来作为key,避免外部使用时更改这些属性,导致后续的请求出错
let axiosSym = Symbol("axios");
let vqaUrlSym = Symbol("vqaUrl");
let vqaPrefixSym = Symbol("vqaPrefix");
let interfacePrefixSym = Symbol("interfacePrefix");
class ParentInterface {
constructor(className, axios) {
//实现类的类名,根据创建的类的实例对象来从配置文件中取出具体的类的 vqaUrl, vqaPrefix 和 interfacePrefix
this[axiosSym] = axios;
let implClass = className;
let configs = config[implClass];
if (!configs) throw new Error(`接口类【${className}】缺少配置文件!`)
this[vqaUrlSym] = configs.vqaUrl;
this[vqaPrefixSym] = configs.vqaPrefix;
this[interfacePrefixSym] = configs.interfacePrefix;
}
/**
* 修正查询vqa的参数
* @param {Object} params 查询vqa所需要的参数
*/
_fixParams(params) {
params.method = this[vqaPrefixSym] + params.method;
}
/**
* 获取请求参数
* @param {Object} datas 请求参数
* @param {String} method 请求方法
* @returns 返回查询vqa需要的参数
*/
_getParams(datas, method) {
let params = {
method: method,
datas: datas
}
this._fixParams(params);
return params;
}
/**
* 项vqa接口发送请求, 注意vqa的请求都是 post 请求
* @param {Object} params 对应查询的 method 的 datas 中需要的参数
* @param {String} method 查询的 vqa 方法,不需要方法前缀,只需要方法名
* @returns
*/
_sendVqaRequest(params, method) {
params = this._getParams(params, method);
return this[axiosSym].post(this[vqaUrlSym], params).then(res => {
return res.data.content;
})
}
/**
* 修正请求路径, 后台接口时使用
* @param {*} method 除config配置文件中的前缀后的部分
* @param {*} fullUrl 指定 method 是否是全路径
* @returns
*/
_fixUrl(method, fullUrl) {
let realUrl = this[interfacePrefixSym];
if (method) {
if (fullUrl) {
realUrl = method;
} else if (method.startsWith("/")) {
realUrl += method;
} else {
realUrl += ("/" + method);
}
}
return realUrl;
}
/**
* 向后台接口发送请求
* @param {Object} params 对应后台接口的参数
* @param {String} method 除config配置文件中的前缀后的部分
* @param {Boolean} fullUrl 指定 method 是否是全路径
* @returns
*/
_sendPostRequest(params, method, fullUrl) {
let realUrl = this._fixUrl(method, fullUrl);
return this[axiosSym].post(realUrl, params, {
loading: params.loading
}).then(res => {
return res.data.content;
})
}
/**
* 向后台接口发送请求
* @param {Object} params 对应后台接口的参数
* @param {String} method 除config配置文件中的前缀后的部分
* @param {Boolean} fullUrl 指定 method 是否是全路径
* @returns
*/
_sendGetRequest(params, method, fullUrl) {
let realUrl = this._fixUrl(method, fullUrl);
return this[axiosSym].get(realUrl, {
params
}).then(res => {
return res.data.content;
})
}
}
export default ParentInterface;
3.3.2 index.js
js
import StatsTable from "./stats/StatsTable.js";
/**
* 创建接口代理,处理vqa的查询,调用vqa方式时,获取调用方法的名称调用后台vqa
* @param {Object} target 被代理的对象,这里是接口的实例
* @returns 返回一个代理对象
*/
function createInterfaceProxy(target) {
let hander = {
get(target, key) {
let vqaMethod = Reflect.get(target, key);
if (vqaMethod) {
return vqaMethod;
}
//写死,所有vqa的查询调用的都是 _sendVqaRequest 方法
vqaMethod = Reflect.get(target, "_sendVqaRequest");
let vqaProxy = new Proxy(vqaMethod, {
apply(funtarget, funthis, args) {
let params = [...args, key]
return Reflect.apply(funtarget, funthis, params)
}
})
return vqaProxy;
}
}
return new Proxy(target, hander)
}
/**
* 初始化所有接口
* @param {*} app Vue
* @param {*} axios 请求实例
*/
function initInterfaces(app, axios) {
const statsTable = createInterfaceProxy(new StatsTable(axios));
const interfaces = {
statsTable
};
for (let interfac in interfaces) {
app.prototype[`$${interfac}`] = interfaces[interfac];
}
}
export default initInterfaces;