GraphQL运用解析器生成结果

发布时间 2023-06-19 14:52:12作者: 泡泡糖公主

文章出处(https://developer.sjtu.edu.cn/graphql/overview.html)

GraphQL语法说明

#说明

GraphQL是一个用于API的查询语言,数据资源API服务通过接收GraphQL查询,并验证和执行。接收到的查询首先会被检查确保它只引用了已定义的类型和字段,然后运行指定的解析函数来生成结果。

#GraphQL语言结构

{ tm_class(first: 1, offset:10, filter:{name:{eq : "123"}}){ num name id  } }
 

以上面示例,GraphQL有三部分组成

  • 第一部分type类型,在数据资源API中其实际为接口的名称 //tm_class
  • 第二部分查询条件,在数据数据资源API中内设3个关键字(first、offset、filter) //(first: 1, offset:10, filter:{name:{eq : "123"}})
  • 第三部分查询字段,表示用户本次API查询需要获取的数据字段,查询字段是受权限保护,对于用户无权限访问的字段,一律返回空值;用户发送查询请求时,可去除未申请的字段;如果希望对返回的字段进行重命名可通过(重命名:字段名 username:name) //{ num name id }

#GraphQL查询条件

GraphQL语句中过滤条件内设三个关键字(first、offset、filter);在filter中配置查询条件,filter的value是一个map集合,可以是一个嵌套的key&value。

filter查询过滤

  • filter其本质数据结构是一个嵌套的键值对,基本语法: {字段名: {查询关键字: 查询值}}
  • 在一个键值对中不能出现相同的key,例如(name:{eq : "123", eq: "231"}),这是不合法的,可以直接通过in,not in的方式来实现
  • filter中不是所有字段都可作为查询参数,根据每个API接口提供的查询参数进行相应配置
  • 需要特别注意,在filter中如果查询值是字符串,需对相应的双引号转义
	//查询语句事例, filter中的双引号需转义
	public static String selectStatement = "{ tm_class(first: 1, offset:10, filter:{name:{eq : \"123\"}}){ num name id  } }";

	public static void main(String[] args) {
		
		Map<String, String> bodyMap = new HashMap<String, String>();
		bodyMap.put("query", selectStatement);
		String response = null;
		try {
			response = HttpUtils.get(apiEndPoint, new Gson().toJson(bodyMap), "");
		}catch (Exception e) {
			e.printStackTrace();
		}
	}

filter查询关键字

关键字符号解释案例
eq == 等号 {userid:{eq : "123"}}
ne != 不等于 {userid:{ne: "123"}}
ge >= 大于等于 {userid:{ge: "123"}}
gt > 大于 {userid:{gt: "123"}}
le <= 小于等于 {userid:{le: "123"}}
lt < 小于 {userid:{lt: "123"}}
like like sql中like语法 {userid:{like: "%123%"}}
nlike nlike sql中not like语法 {userid:{nlike: "%123%"}}
in in sql中的in语法
多个值之间用逗号隔开
查询值用[]包围
{userid:{in: ["123", "321"]}}
nin not in sql中的not in语法
多个值之间用逗号隔开
查询值用[]包围
{userid:{nin: ["123", "321"]}}
nil null sql中is null语法
查询值用”“
{userid:{nil: ""]}}
notnull not null sql中is not null语法
查询值用”“
{userid:{notnull: ""]}}

filter查询逻辑符(AND/OR)

  • filter中也支持AND和OR的逻辑符号,优先级和数据库sql查询一致。默认缺省值为AND。 通过嵌套逻辑符号,可以实现十分复杂的过滤查询。

下面通过一些常用的配置,基本可以了解filter的相应的写法

  • 缺省查询条件
# sql中查询 (name="123" and age > 1)转换成graphql的filter写法
filter:{name:{eq : \"123\"}, age:{gt : 1}}
# 上面的filter和下面filter语句的语义是一致的
filter:{and:{name:{eq : \"123\"}, age:{gt : 1}}}
  • 实现sql中between...and...的
# sql中查询 (name="123" and age between 1 and 10)转换成graphql的filter写法
filter:{and:{name:{eq : \"123\"}, age:{ge : 1, le : 10}}}
  • 简单的or查询
#sql中查询(name is null or name="123")转换成graphql的filter写法
filter:{or:{name:{nil : \"\", eq : \"123\"}}}

#sql中查询(name = "123" or age > 10)
filter:{or:{name : {eq : \"123\"}, age: {lt: 10}}}
  • AND语句中嵌套OR (复杂的条件语句不能使用缺省条件)
#sql中查询(age > 10 and (name is null or name like "王%"))转换成graphql的filter写法
filter:{and: {age: {gt : 10}, or:{name : {nil: \"\", like: \"王%\"}}}}
 
  • OR语句中嵌套And (复杂的条件语句不能使用缺省条件)
#sql中查询(age > 10 or (name is not null and name like "王%"))转换成graphql的filter写法
filter:{or: {age : {gt : 10}, and: {name : {notnull : \"\", like : \"王%\"}}}}
 

#分页查询关键字

过滤条件3大关键字(offset, first, filter), offset和first就是用来指定分页的参数。对于没有指定分页参数的,默认最多返回500条数据。

  • first:第一条数据开始的位置, first最小值为1
  • offset: 偏移量,一次查询获取最大多少数据
  • 假设一次查询10条数据,第一次为(first:1, offset: 10), 则第二次为(first:11, offset:10)。即第二次查询first= (前一次first + 前一次offset)
  • 分页查询实例
#第一次查询10条数据
{ tm_class(first: 1, offset:20){ num name id  } }

#则第二次查询10条数据
{ tm_class(first: 21, offset:20){ num name id  } }

#GraphQL构造示例


示例 graphql语句嵌入查询条件
public class GraphqlFilterHandler {
	
	public final static String FILTER_KEYWORD = "filter";
	public final static String PAGE_FIRST_KEYWORD = "first";
	public final static String PAGE_OFFSET_KEYWORD = "offset";

	/**
	 * @Title: GraphqlStatementAddFilterCriteria   
	 * @Description: 嵌入的过滤条件
	 * @param: @param graphqlStatement : graphql查询语句
	 * @param: @param criteriaEntities :查询条件
	 * @param: @return      
	 * @return: String      
	 * @throws
	 */
	public static String GraphqlStatementAddFilterCriteria(String graphqlStatement, CriteriaEntity...criteriaEntities ) {
		String filterStatement = buildCriteria(graphqlStatement, criteriaEntities);
		graphqlStatement = addFilterCriteria(graphqlStatement, filterStatement);
		return graphqlStatement;
	}
	
	/**
	 * 
	 * @Title: GraphqlStatementAddFilterCriteria   
	 * @Description: 附带 分页参数
	 * @param: @param graphqlStatement
	 * @param: @param pagePara
	 * @param: @return      
	 * @return: String      
	 * @throws
	 */
	public static String GraphqlStatementAddFilterCriteria(String graphqlStatement, int first,
														   int offset, CriteriaEntity...criteriaEntities) {
		String pageFirstStatement = PAGE_FIRST_KEYWORD +  " : " +first;
		String pageOffsetStatement = PAGE_OFFSET_KEYWORD + " : " + offset;
		String filterStatement = buildCriteria(graphqlStatement, criteriaEntities);
		if(StringUtils.isEmpty(filterStatement)) {
			filterStatement = "(" + pageFirstStatement + ", " + pageOffsetStatement + ")";
		}else {
			int pos = filterStatement.indexOf(")");
			String suffix = filterStatement.substring(0, pos);
			filterStatement = suffix + ", " + pageFirstStatement + ", " + pageOffsetStatement + ")";
		}
		graphqlStatement = addFilterCriteria(graphqlStatement, filterStatement);
		return graphqlStatement;
	}
	
	private static String buildCriteria(String graphqlStatement, CriteriaEntity...criteriaEntities) {
		String filterStatement = "";
		Map<String, Object> filterMap = new HashMap<String, Object>();
		for(CriteriaEntity criteriaEntity : criteriaEntities) {
			QueryFilterEnum filterEnum = criteriaEntity.getQueryFilterEnum();
			Map<String, Object> map = filterEnum.criteriaStatement(criteriaEntity);
			filterMap.put(criteriaEntity.getFieldName(), map);
		}
		if(filterMap.size() == 0) {
			if(graphqlStatement.indexOf(FILTER_KEYWORD) != -1) {
				filterStatement = graphqlStatement.substring(graphqlStatement.indexOf("(") , graphqlStatement.indexOf(")") + 1);
				return filterStatement;
			}else {
				return filterStatement;
			}
		}
		Gson gson = new Gson();
		filterStatement = gson.toJson(filterMap);
		filterStatement = filterStatement.replaceAll("\"(\\w+)\"(\\s*:\\s*)", "$1$2"); 
		if(graphqlStatement.indexOf(FILTER_KEYWORD) != -1) {
			int pos = graphqlStatement.indexOf(FILTER_KEYWORD);
			int criteriaPos = graphqlStatement.indexOf("{", pos);
			filterStatement = filterStatement.substring(1, filterStatement.length()-1);
			String suffix = graphqlStatement.substring(graphqlStatement.indexOf("("), criteriaPos+1);
			String prefix = graphqlStatement.substring(criteriaPos+1, graphqlStatement.indexOf(")")+1);
			filterStatement = suffix + filterStatement + " " + prefix;
		}else {
			filterStatement = "( " + FILTER_KEYWORD + ":" + filterStatement + ")";
		}
		return filterStatement;
	}
	
	/**
	 * 过滤过滤条件添加到graphql语句中
	 * 前提条件:graphql语句本身不带过滤条件
	 * @Title: addFilterCriteria   
	 * @param: @param graphqlStatement
	 * @param: @param filter
	 * @param: @return      
	 * @return: String      
	 * @throws
	 */
	private static String addFilterCriteria(String graphqlStatement, String filter) {
		if(graphqlStatement.indexOf("(") != -1) {
			graphqlStatement = graphqlStatement.substring(0, graphqlStatement.indexOf("(")) + graphqlStatement.substring(graphqlStatement.indexOf(")") +1);
		}
		int postion = graphqlStatement.indexOf("{", graphqlStatement.indexOf("{") + 1);
		String prefix = graphqlStatement.substring(0, postion);
		graphqlStatement = prefix + filter + graphqlStatement.substring(postion, graphqlStatement.length());
		return graphqlStatement;
	}
}
 

CriteriaEntity构造类

public class CriteriaEntity implements Serializable {

	private static final long serialVersionUID = -300137530104056710L;
	
	public CriteriaEntity(String fieldName, Object argValue, QueryFilterEnum queryFilterEnum) {
		this.fieldName = fieldName;
		this.argValue = argValue;
		this.queryFilterEnum = queryFilterEnum;
	}
	private String fieldName;
	
	private Object argValue;
	
	private QueryFilterEnum queryFilterEnum;

	public String getFieldName() {
		return fieldName;
	}

	public Object getArgValue() {
		return argValue;
	}

	public QueryFilterEnum getQueryFilterEnum() {
		return queryFilterEnum;
	}
	
}
 

查询关键字枚举类

枚举类示例中,仅配置了部分枚举,供参考。

public enum QueryFilterEnum {

	GE("ge"){
		@Override
		public Map<String, Object> criteriaStatement(CriteriaEntity criteriaEntity) {
			Map<String, Object> map = new HashMap<String, Object>();
			map.put(getKey(), criteriaEntity.getArgValue());
			return map;
		}
	},
	
    IN("in") {
		@Override
		public Map<String, Object> criteriaStatement(CriteriaEntity criteriaEntity) {
			Object argVal = criteriaEntity.getArgValue();
			if(argVal instanceof List || argVal instanceof Set) {
				Map<String, Object> map = new HashMap<String, Object>();
				map.put(getKey(), argVal);
				return map;
			}else {
				throw new ClassCastException(String.format("filed name: %s, can not cast to List or Set", criteriaEntity.getFieldName()));
			}
		}
	};
	
	QueryFilterEnum(String key) {
        this.key = key;
    }
    
    private String key;
	
    public String getKey() {
        return key;
    }
	
	public abstract Map<String, Object> criteriaStatement(CriteriaEntity criteriaEntity);
}