2019独角兽企业重金招聘Python工程师标准>>>

#1 前言

本篇文章主要来说明下代码模块的设计。像我们这种菜鸟级别,只有平时多读读源码,多研究和探讨其中的设计才可能提升自己,写出高质量的代码。

没有最好的设计,只有更好的设计,所以在发表我自己的愚见的同时,希望小伙伴们相互探讨更好的设计,有探讨才有更大的进步。

#2 题目及分析

我们维护了一个数据中心,对外提供查询API,如何能让用户随意的添加查询条件,而不用修改后台的查询代码呢?用户如何配置查询条件,从而达到如下的sql效果呢?:

a.name='lg' or b.age>12
b.id in (12,34,45)
c.updateTime>'2015-3-28' and (b.id=2 or d.age<23)
e.age>f.age

##2.1 查询参数的传递方式

我们作为API设计者,该如何让用户方便的传递他们任意的查询需求呢?这是我们要思考的地方。

目前来看比较好的方式莫过于:用户通过json来表达他们的查询需求。

##2.2 查询的本质分析

从上面的查询来看,我们可以总结出来查询条件无非就是某个字段满足什么样的条件。这里有三个对象:

  • 查询的字段 如 b.age
  • 条件值 如 12
  • 怎样满足条件值 如 >

##2.3 查询的配置分析

这样我们就可以清晰明了了,一个查询条件无非就是三个内容,所以可以如下配置:

{"columns":"b.age","oper":">","value":12
}

很显然,上面的确很麻烦,我们无非是要表达这三个内容,所以就要简化配置:

{"b.age":12,"oper":">"
}

还是不够简化,如何把操作符 > 也放置进去呢?如下

{"b.age@>":12
}

这样我们就可以把三个对象表达清楚了,将查询的字段和操作符合并起来作为key,并使用分隔符@分割两者,条件值作为value。这样就做到了,非常简化的查询配置。

接下来又面临一个问题,如何表达查询条件之间的and or 关系呢?即如何表达下面的内容呢?

c.age>14 and (b.id=2 or d.age<23)

借鉴mongodb的查询方案,可以如下配置:

{"c.age@>":14,"$or":{"b.id@=":2,"d.age@<":23}
}

通过配置一个$or作为key表明里面的几个查询条件是or的关系,如果是$and表明里面的查询条件之间是and的关系,外层默认是and的关系。

同时我们再回顾下,mongodb所作出的查询设计,也是通过用户配置json形式来表达查询意图,但是我们来看下它是如何查询

a.age>12 对应的mongodb的查询方式为:
{a.age : {$gt : 22}
}我们的查询方式是{"a.age@>":22
}

虽然看似我们的更加简单,mongodb的更加繁琐,主要是mongodb认为对于一个字段,可以有多个查询条件的,为了支持更加复杂的查询,如下:

{  a.age : {$lt :24,$gt : 17}
}然而我们也可以对此进行拆分,如下,同样满足:{"a.age@>":17,"a.age@<":24,
}

各有各的好处和缺点,我就是我,颜色不一样的烟火。哈哈。。。

#3 代码设计与实现

##3.1 解析器接口的设计

对题目进行分析完了之后,就要考虑如何实现这样的json配置到sql的转化。实现起来不难,最重要的是如何做出一个高扩展性的实现?

再来看下下面的例子:

{"a.name@=":"lg","b.age@>":12,"c.id@in":[12,13,14],"d.time[@time](http://my.oschina.net/u/126678)>":"2015-3-1"
}

其实就是针对每个自定义的操作符进行相应的处理,所以就有了解析器接口:

public interface SqlParamsParser {//这里表示该解析器是否支持对应的操作符public boolean support(String oper);public String getParams(String key,Object value,String oper);public SqlParamsParseItemResult getParamsResult(String key,Object value,String oper);}

其中SqlParamsParseItemResult,则是把解析后的结果分别存储起来,而不是直接拼接成一个字符串,主要为了直接拼接字符串式的sql注入,它的内容如下:

public class SqlParamsParseItemResult {private String key;private String oper;private Object value;
}

上面的key oper value 则是解析后的内容。下面举例说明

以"b.age@>":12 为例,其中getParams方法中的 key就是b.age, value就是12, oper就是> 而这个方法的返回的字符串结果为:

b.age>12返回的SqlParamsParseItemResult存储的内容为分别为 key=b.age ; oper=> ; value=12

以"c.id@in":[12,13,14]为例,其中getParams方法中的 key就是c.id,value就是一个List集合,oper就是in ,这个方法的返回结果为:

c.id in (12,13,14)返回的SqlParamsParseItemResult存储的内容为分别为 key=c.id ; oper=in ; value=(12,13,14)

以"d.time@time>":"2015-3-1"为例,其中getParams方法中的 key就是c.id,value就是一个List集合,oper就是in,这个方法的返回结果为:

unix_timestamp(d.time) > 1425139200     (2015-3-1对应的秒数)返回的SqlParamsParseItemResult存储的内容为分别为 key=unix_timestamp(d.time) ; oper=> ; value=1425139200

##3.2 解析器接口的抽象类

解析器有很多相同的地方,这就需要我们进行抽象,抽出共性部分,留给子类去实现不同的部分。所以有了抽象类AbstractSqlParamsParser

有哪些共性部分和非共性部分呢?

  • 共性部分: 就是support方法。每个解析器支持某几种操作符,所以判断该解析器是否支持当前的操作符的逻辑是共同的,所以如下:

    public abstract class AbstractSqlParamsParser implements SqlParamsParser{private String[] opers;   private boolean ignoreCase=true;protected void setOpers(String[] opers){this.opers=opers;}    protected void setIgnoreCase(boolean ignoreCase){this.ignoreCase=ignoreCase;}@Overridepublic boolean support(String oper) {if(opers!=null && oper!=null){for(String operItem:opers){if(ignoreCase){operItem=operItem.toLowerCase();oper=oper.toLowerCase();}if(operItem.equals(oper)){return true;}}}return false;}
    }
    

    opers属性表示当前解析器所支持的所有操作符。ignoreCase表示在匹配操作符的时候是否忽略大小写。这两个属性都设置成private,然后对子类开放了protected类型的set方法,用于子类来设置这两个属性。

  • 非共性部分:留出了doParams方法供子类来具体实现

    @Override
    public SqlParamsParseItemResult getParamsResult(String key, Object value,String oper) {return doParams(key, processStringValue(value), oper);
    }protected abstract SqlParamsParseItemResult doParams(String key, Object value, String oper);
    

##3.3 解析器接口的实现类

目前内置了几个常用的解析器实现,类图如下:

以TimeSqlParamsParser为例来简单说明下:

它主要是用于解析如下形式的:

{"d.time@time>":"2015-3-1"
}

最终想达到的效果是:

unix_timestamp(d.time) > 1425139200

它的解析过程如下:

/*** 以d.time@time>'2015-3-1'为例* 初始参数 key=d.time; value='2015-3-1'; oper=time>* 解析后的key=unix_timestamp(d.time); value=1425139200('2015-3-1'对应的秒数); oper=>*/
@Override
protected SqlParamsParseItemResult doParams(String key, Object value, String oper) {String timeKey="unix_timestamp("+key+")";String realOper=oper.substring(4+fullTimeFlag.length());if(value instanceof String){String tmp=(String)value;Assert.isLarger(tmp.length(),2,"时间参数不合法");//默认进行了字符串处理,即加上了'',现在要去掉,然后解析成时间的秒数value=tmp.substring(1,tmp.length()-1);try {SimpleDateFormat format=new SimpleDateFormat(timeFormat);Date date=format.parse((String)value);value=date.getTime()/1000;} catch (ParseException e) {e.printStackTrace();throw new IllegalArgumentException("timeFormat为"+timeFormat+";value="+value+";出现了解析异常");}}else{Assert.isInstanceof(value,Number.class,"时间参数必须为时间的秒数");}return new SqlParamsParseItemResult(timeKey,realOper,value);
}

解析过程其实就是对key value oper 进行了不同程度的转换。

同时TimeSqlParamsParser还支持其他时间形式的解析,如"2015-3-1 12:23:12",只需如下方式创建一个解析器:

new TimeSqlParamsParser("yyyy-MM-dd HH:mm:ss","full_")

然后他就能够解析下面的形式:

{"d.time@full_time>":"2015-3-1 12:23:12"
}

同时又能保留原有的形式,两者互不干扰。

#4 DefaultSqlParamsHandler使用解析器

有了解析器的一系列实现,下面就需要一个综合的类来使用这些解析器。这就是DefaultSqlParamsHandler:

##4.1 注册使用解析器

public class DefaultSqlParamsHandler {private List<SqlParamsParser> sqlParamsParsers;public DefaultSqlParamsHandler(){sqlParamsParsers=new ArrayList<SqlParamsParser>();sqlParamsParsers.add(new DefaultSqlParamsParser());sqlParamsParsers.add(new InSqlParamsParser());sqlParamsParsers.add(new TimeSqlParamsParser());sqlParamsParsers.add(new TimeSqlParamsParser("yyyy-MM-dd HH:mm:ss","full_"));sqlParamsParsers.add(new DefaultColumnSqlParamsParser());
}

内部已经注册了几个解析器。同时需要对外留出注册自定义解析器的方法:

public void registerSqlParamsHandler(SqlParamsParser sqlParamsParser){if(sqlParamsParser!=null){sqlParamsParsers.add(sqlParamsParser);}
}public void registerSqlParamsHandler(List<SqlParamsParser> sqlParamsParsers){if(sqlParamsParsers!=null){for(SqlParamsParser sqlParamsParser:sqlParamsParsers){registerSqlParamsHandler(sqlParamsParser);}}
}

##4.2 解析过程

这个过程不仅需要使用已经注册的解析器来解析,还包含对解析条件之间的and or 关系的递归处理。代码如下,不再详细说明:

private SqlParamsParseResult getSqlWhereParamsResultByAndOr(Map<String,Object> params,String andOr,boolean isPlaceHolder,SqlParamsParseResult sqlParamsParseResult){if(params!=null){String andOrDelititer=" "+andOr+" ";for(String key:params.keySet()){Object value=params.get(key);if(value instanceof Map){//这里需要进行递归处理嵌套的查询条件SqlParamsParseResult SqlParamsParseResultModel=null;if(key.equals(andKey)){SqlParamsParseResultModel=processModelSqlWhereParams((Map<String,Object>)value,AND,isPlaceHolder);}else if(key.equals(orKey)){SqlParamsParseResultModel=processModelSqlWhereParams((Map<String,Object>)value,OR,isPlaceHolder);}if(SqlParamsParseResultModel!=null && StringUtils.isNotEmpty(SqlParamsParseResultModel.getBaseWhereSql())){sqlParamsParseResult.addSqlModel(andOrDelititer);sqlParamsParseResult.addSqlModel("("+SqlParamsParseResultModel.getBaseWhereSql()+")");sqlParamsParseResult.addArguments(SqlParamsParseResultModel.getArguments());}}else{//这里才是使用已经注册的解析器进行解析SqlParamsParseItemResult sqlParamsParseItemResult=processNormalSqlWhereParams(key,value,isPlaceHolder);if(sqlParamsParseItemResult!=null){sqlParamsParseResult.addSqlModel(andOrDelititer);sqlParamsParseResult.addSqlModel(sqlParamsParseItemResult.getSqlModel(isPlaceHolder,PLACE_HOLDER));sqlParamsParseResult.addArgument(sqlParamsParseItemResult.getValue());}}}StringBuilder baseWhereSql=sqlParamsParseResult.getBaseWhereSql();if(StringUtils.isNotEmpty(baseWhereSql)){sqlParamsParseResult.setBaseWhereSql(new StringBuilder(baseWhereSql.substring(andOrDelititer.length())));}}return sqlParamsParseResult;
}

这里进行了递归调用,主要用于处理 $and $or 的嵌套查询,getSqlWhereParamsResultByAndOr可能内部调用了processModelSqlWhereParams,processModelSqlWhereParams内部又调用了getSqlWhereParamsResultByAndOr

private SqlParamsParseResult processModelSqlWhereParams(Map<String,Object> params,String andOr,boolean isPlaceHolder){return getSqlWhereParamsResultByAndOr(params,andOr,isPlaceHolder,new SqlParamsParseResult());
}

这里就是使用解析器进行解析的过程,先遍历每个解析器是否支持当前的操作符,如果支持则进行相应的解析

private SqlParamsParseItemResult processNormalSqlWhereParams(String key,Object value,boolean isPlaceHolder) {SqlParamsParseItemResult sqlParamsParseItemResult=null;String[] parts=key.split(separatorFlag);if(parts.length==2){for(SqlParamsParser sqlParamsParser:sqlParamsParsers){if(sqlParamsParser.support(parts[1])){sqlParamsParseItemResult=sqlParamsParser.getParamsResult(parts[0],value,parts[1]);break;}}}else{sqlParamsParseItemResult=new SqlParamsParseItemResult(key,"=",SqlStringUtils.processString(value));}return sqlParamsParseItemResult;
}

##4.3 对外留出的扩展

{"c.age@>":14,"$or":{"b.id@=":2,"d.age@<":23}
}

这里面的@ $or 以及 $and 都是可以自己设定的,默认值是上述形式。

#5 工程项目

这个小项目已经发布到osc上,见 osc的search-sqlparams项目

转载于:https://my.oschina.net/pingpangkuangmo/blog/393405

cms查询系统(二)json形式参数的设计与解析相关推荐

  1. cms查询系统(一)背景以及需求分析与设计

    2019独角兽企业重金招聘Python工程师标准>>> 本人想做一个cms查询框架,用于解决实际的业务问题,顺便锻炼下能力 #1 背景介绍 在一个配置管理系统即cms系统中,有很多的 ...

  2. 车辆违章信息管理系统c语言,车辆违章查询系统c语言结课设计.doc

    综合性程序设计报告 设计题目: 车辆违章查询系统 指导教师: 朱世敏 班 级: 21214 学 号: 2121416 设 计 者: 孙令朝 成 绩: _______________ 设计时间: 201 ...

  3. [Unity存档系统]简单介绍Unity常见存档系统二JSON以及使用方法

    学习目标: 如果你和我同样苦恼于游戏相关的数据怎么存储与读取,那么不妨看看这个up主有关Unity存档系统的教程.[Unity] 存档系统 Part 1 | PlayerPrefs | Unity初学 ...

  4. 家族关系查询系统程序设计算法思路_数据结构课程设计--

    数据结构课程设计-- 家族关系查询系统 课 程 设 计 任 务 书 题目题目 家族关系查询系统家族关系查询系统 主要内容.基本要求.主要参考资料等主要内容.基本要求.主要参考资料等 主要内容主要内容 ...

  5. C语言查课系统的报告,C语言课程设计报告车辆违章查询系统doc

    C语言课程设计报告车辆违章查询系统doc C 语言课程设计姓 名:刘 风学 号:8100612时 间:2011/6/11指导教师:王 薇题目:车辆违章查询系统 要求: 能够根据车辆的牌号,进行违章记录 ...

  6. 景点查询系统c语言程序,《C语言程序设计》课程计报告-景点查询系统.doc

    <C语言程序设计>课程计报告-景点查询系统 课 程 设 计 报 告 题目: 景点查询系统 课程名称: C语言与程序设计 专业班级: 信息安全1302班 学 号: 姓 名: 指导教师: 报告 ...

  7. C语言电话号码查询系统[2023-01-16]

    C语言电话号码查询系统[2023-01-16] 一.课程设计(论文)题目 电话号码查询系统 说明: 设计哈希表,实现电话号码查询系统. 二.本次课程设计(论文)应达到的目的 C语言.面向对象程序设计. ...

  8. asp毕业设计——基于asp+access的学生成绩查询系统设计与实现(毕业论文+程序源码)——成绩查询系统

    基于asp+access的学生成绩查询系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于asp+access的学生成绩查询系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦. ...

  9. 武大版c语言程序设计习题 二维数组 简易学生成绩查询系统,《C语言程序设计》程设计报告.docx...

    <C语言程序设计>程设计报告 课程设计报告题目:招生查询系统课程名称:C语言程序设计专业班级:信息安全201302班学号:姓名:指导教师:报告日期: 2014年10月10日计算机科学与技术 ...

最新文章

  1. arcgis server 开发
  2. React学习笔记二——react-router
  3. windows端自动化遇到的问题
  4. Oculus Go 开发者版本开放索取,下个月设备开始配送
  5. Android8.0适配方案
  6. python 程序运行插件_如何使Python插件在Pluma中运行?
  7. 一阶段结束考核题(链表的嵌套使用)
  8. 纯CSS实现React Logo图形,内含详细解析
  9. bzoj 5302: [Haoi2018]奇怪的背包
  10. 发一个多CPU中进程与CPU绑定的例子
  11. mybatis中使用小于号
  12. pls-toolbox_使用T-SQL Toolbox数据库解决时区,GMT和UTC问题
  13. maven 加入第三方库_添加第三方库到Maven资源库
  14. 计算机c盘用户爆满,电脑C盘又爆满了?这招让你一键清除十几G内存
  15. 数学基础 —— 旋转(2D 正旋转)
  16. Spark的lazy特性有什么意义呢?
  17. AHRS(航姿参考系统)和IMU(惯性测量单元)的区别【转】
  18. python x 0b1010_下面代码的输出结果是
  19. SonarQube在Windows环境下下载安装,中文包下载安装,mysql配置,maven配置,idea配置,项目配置
  20. java高级编程之IO流

热门文章

  1. 数据库MySQL--常见基础命令
  2. [BZOJ4719][P1600][NOIP2016]天天爱跑步[LCA+dfs序+差分]
  3. JVM 对象引用标记 与 内存回收算法
  4. VSFTP的PASSIVE模式的防火墙设置
  5. MySQL学习笔记十七:复制特性
  6. 一步一步重写 CodeIgniter 框架 (2) —— 实现简单的路由功能
  7. 766. 托普利茨矩阵
  8. 11-6-线程的概念
  9. java fx 重绘_如何重绘JAVA FX 2.2中的窗口(舞台)
  10. python安装jupyter出现问题_有关jupyterhub安装的问题