在REST中,资源的返回结构与返回数量是由服务端决定;在GraphQL,服务端只负责定义哪些资源是可用的,由客户端自己决定需要得到什么资源,所谓的api查询。我们的想法是在REST中服务端决定的资源,其实也是通过客户端所给予的信息来进行反馈(资源定位和http的动作),如果客户端和服务端之间有一套约定,这个约定框架下客户端给的信息足够多,那么也可以让服务端来满足客户端的查询需求,即按需请求按需返回,这个和GraphQL的思想是一致的。
至于为什么不统一使用GraphQL,每个公司的大背景下会有每种不同的实际情况,满足多端的需求又尽快的让服务端和客户端每个人都熟悉GraphQL,成本比较大;当然在有条件的额情况下统一使用GraphQL是最好的。所以我们就想用中间层和传统的REST来改造现有数据接口,又符合GraphQL的思想,在客户端的查询条件下,满足合并模块数据、过滤筛选冗余数据,选取特定数据的需求。

查询附加符号定义

符号 定义
$& 与,默认,独立逻辑符号,不与任何字符连接使用
$| 或,条件满足其一即可,merge专用,独立逻辑符号,不与任何字符连接使用
$^ 非,剔除此条件下的当前数据,独立逻辑符号,不与任何字符连接使用
$. 定位深度,类似于this. 只适用于mergeRule中
$< 小于,适用于数值或者数组的长度
$> 大于,适用于数值或者数组的长度
$<= 小于等于,适用于数值或者数组的长度
$>= 大于等于,适用于数值或者数组的长度
$= 等于,默认等于条件,不必写
$!= 不等于,不等于条件成立
$<> 区间,适用于数值或者数组的长度
$$ 正则匹配符号,表示当前条件为正则表达式

$& 查询组里条目之间的关系默认是与的关系,当B且C满足条件,A才满足条件。

1
2
3
4
5
6
{
"A": {
"B": "conditionB",
"C": "conditionC"
},
}

等价于B & C

$| 查询组里条目之间的是或的关系,当B或C且D满足条件,A才被提取出来,注意只有在merge规则里使用或逻辑,filter规则里不必使用或关系。

1
2
3
4
5
6
7
8
9
{
"A": {
"B": "conditionB",
"$|": {
"C": "conditionC",
"D": "conditionC"
}
}
}

等价于 B | ( C & D )

$^ 满足B的情况下过滤掉A字段中的C和D字段,再提取出A,$^的value为数组,数组中的值为需要排除的字段。

1
2
3
4
5
6
{
"A": {
"B": "conditionB",
"$^": ["C","D"]
}
}

过滤A字段中的C和D字段

1
2
3
4
5
{
"A": {
"$^": ["C","D"]
}
}

$< $<= $> $>= $< $<> 适用于数值或者数组长度的比较

1
2
3
4
5
6
{
"A": {
"$>=B": 100,
"$<>C": [1,100],
}
}

假设B为数值,C为数组,那么A字段中的B大于等于100且C的长度在1到100区间,就提取A字段数据。

$!= 不等于符号

1
2
3
4
5
6
{
"A": {
"B": "conditionB",
"$!=C": "conditionC"
}
}

如果B等于conditionB,且C不等于conditionC,那么提取A段数据。

$$ 正则匹配符号,后面的筛选条件为正则表达式数组,数组第一项为正则表达式,第二项为标志;原数据中的数据会和正则表达式进行match匹配。
语法:

[pattern[,flag]]```
1
2
3
4
5
6
7
```json
{
"A": {
"B": "conditionB",
"$$C": ["/condition(?:C|D)/","g"]
}
}

如果B等于conditionB,且C中含有conditionC或者conditionD,那么提取A段数据

$.mergeRule中定位深度,类似于this.,则只适用于mergeRule中进行查找筛选然后合并数据,不使用的该符号默认为原数据结构的根目录查找起点。

1
2
3
4
5
6
7
{
"mergeGroupA": {
"$.prarent.child"{
"B": "conditionB",
}
}
}

$.prarent.child表示,在根目录下查找parent
1.parent对象则继续查找parent下的child
2.parent数组则继续查找parent里有child的子项
3.如果整个链式查找出错或者找不到期望的筛选深度,则废弃该合并字段,找不到parent或者child,mergeGroupA就会被废弃,返回数据中不会存在此字段
4.root > prarent > child 中满足B等于conditionBchild会被合并到mergeGroupA

注意:

  1. 逻辑符号 表达逻辑关系时候,符号$| $^ $&都需要作为单独的Key值写,,以便于分组,不可和字段连在一起写。
  2. 逻辑查询,为了兼顾性能,只适用于简单类型的条件比较,字符串,数值,布尔,undefined,null等,不适用于复杂类型的判断。例如以上的condition条件可以为字符串,数值,布尔值,空等,不可以为对象,函数,数组。

查询参数

url [string]

接口地址,必传。中间层会代理请求当前地址,按照规则将处理的数据返回,必要时需要传params作为透传参数。

1
2
3
{
"url": "https://api.xx.com/client.action1",
}

params [object]

接口相应的参数,默认为空。

1
2
3
4
5
6
7
8
{
"url": "https://api.xx.com/client.action1",
"params":{
"client": "wh5",
"functionId": "list",
"clientVersion": "10.0.0",
}
}

filterWithoutMerge [boolean]

输出的过滤模块(filter)中是否要排除掉整合模块中(merge)的数据,默认值true
例如,所有数据为A,过滤数据为F,整合数据为M,我们会在A-M的基础上再去得到F,也就是说FM为空。

1
2
3
{
"filterWithoutMerge": true,
}

mergeRule [object]

需要整合的数据规则,不需要整合则不用传该参数
规则为:

1
2
3
4
5
{
merge:{
mergeModuleName: conditions
}
}

mergeModuleName [string]
分组的名称,例如你可能需要把所有的标题整合到titles的分组中,以便于你接收数据时候可以从res.merge.titles里取出你期望整合过的数据。
conditions [array | object]
分组整合查询条件,以原始数据的根节点为查询源点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"mergeRule": {
"titles": {
"$.result.list":{
"id": ["665,666","667,668"],
"$!=content": "img"
}
},
"skucards": {
"$.result.des":{
"type": "5"
}
},
"scrollers": {
"$.result.skus":{
"img": "xx.gov",
"$|":{
"$>=leftStock": "100"
}
}
}
}
}

假设返回的原始数据设为res
titles分组
整合res.result.list自身或者子项中id为’665,666’或者’667,668’且content不为’img’的集合。
skucards分组
整合res.result.des自身或者子项中type为5的集合。
scrollers分组
整合res.result.skus自身或者子项中’img’为’xx.gov’,或自身或者子项的’leftStocks’的数目大于100的集合。

filterRule [object]

过滤模块,基于原始接口数据进行筛选,满足条件的集合会被过滤掉。可在filterWithoutMerge中配置是否要在原始数据中排除已经整合出来的数据。
语法为:

1
2
3
4
5
6
7
{
filterRule:{
path:{
filterName: conditions
}
}
}

filterName [string]
原始数据中存在的子项字段名称,对该子项进项处理。
conditions [object | boolean]
filter的对象为数组类型,conditions的目标是对数组内的子项进行处理。
filter的对象为非数组类型,conditions的目标是对该filter的对象自身进行处理。
简而言之,过滤对象本身为数组,则过滤条件适用于子项过滤,过滤对象为数值、字符串、布尔值、对象等则针对自身做过滤。

注意:

  1. 过滤规则内不支持$|’或’规则,因为没有必要且不符合认知
  2. 过滤规则支持数组内子项过滤,但不支持数组内子项的子项过滤, $.a.b.c 即c才能是数组,如其他需求配合merge使用

filter过滤规则分为条件过滤非条件过滤
条件过滤: 只针对于数组,对象类型,满足Key,Value相互匹配的条件产生过滤处理。

1
2
3
'$.result.list': {
'floorAppearance': ['articleDetailFloor_1', 'similarArticleFloor_2']
}

非条件过滤: 对象中字段,没有任何条件判断,找到次过滤字段即过滤。

1
2
3
'$.result.config': {
'$^': ['head'] // 删除config中footer字段数据,无论其值是什么
},

过滤list中有floorApearrence的字段,且值等于articleDetailFloor_1和等于similarArticleFloor_2的子项。

1
2
3
4
5
6
{
// **** list是数组类型,其后条件是针对子项进项筛选 ****
'$.result.list': {
'floorAppearance': ['articleDetailFloor_1', 'similarArticleFloor_2']
},
}

过滤list中有floorApearrence的字段,且值不等于articleDetailFloor_2都过滤掉。

1
2
3
4
5
6
{
// **** list是数组类型,其后条件是针对子项进项筛选 ****
'$.result.list': {
'$!=floorAppearance': 'similarArticleFloor_2'
},
}

过滤list中没有description的子项

1
2
3
4
5
6
{
// **** list是数组类型,其后条件是针对子项进项筛选 ****
'$.result.list': {
'description': 'undefined'
}
}

过滤list中含有floorApearrence字段的所有子项

1
2
3
4
5
6
{
// **** list是数组类型,其后条件是针对子项进项筛选 ****
'$.result.list': {
'$^': ['floorApearrence']
}
}

过滤configfooterheader字段数据,无论其值是什么

1
2
3
4
5
6
{
// **** config 是对象类型,其后条件对自身进行筛选 ****
'$.result.config': {
'$^': ['footer','header']
}
}

完整单接口请求:

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
module.exports = {
'queryKey': 'discoveryFanAreaList',
'url': 'https://api.m.jd.com/client.action',
'params': {
'client': 'wh5',
'functionId': 'discoveryFanAreaList',
'clientVersion': '10.0.0'
},
'filterWithoutMerge': true,
'mergeRule': {
'skusOrImgs': {
'$.result.list.description': {
'type': '3',
'$|': {
'type': '2'
}
}
},
'authorDetailFloors': {
'$.result.list': {
'$$floorAppearance': ['authorDetailFloor', 'g']
}
}
},
'filterRule': {
'$.result.list': {
'floorAppearance': ['articleDetailFloor_1', 'similarArticleFloor_2']
},
'$.result.config': {
'$^': ['head']
},
'$.result': {
'$^': ['pageView', 'pageViewStr']
}
}
}

多接口查询

多接口查询的时候,需要增加每个接口的查询的关键字,以便于返回合并数据后的读取。

queryKey [string]

查询关键字,用于多接口查询返回使用,单接口可选,多接口必传。
完整多接口请求示例:

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
module.exports = [{
'queryKey': 'discoveryFanAreaList',
'url': 'https://api.m.jd.com/client.action',
'params': {
'client': 'wh5',
'functionId': 'discoveryFanAreaList',
'clientVersion': '10.0.0'
},
'filterWithoutMerge': true,
'mergeRule': {
},
'filterRule': {
}
}, {
'queryKey': 'discoveryGuessLike',
'url': 'https://api.m.jd.com/client.action',
'params': {
'client': 'wh5',
'functionId': 'discoveryGuessLike',
'clientVersion': '10.0.0'
},
'filterWithoutMerge': false,
'mergeRule': {
},
'filterRule': {
}
}, {
'queryKey': 'discoveryAuthorHome',
'url': 'https://api.m.jd.com/client.action',
'params': {
'client': 'wh5',
'functionId': 'discoveryAuthorHome',
'clientVersion': '10.0.0'
},
'filterWithoutMerge': false,
'mergeRule': {
},
'filterRule': {
}
}]

原创内容,欢迎交流转载请注明出处