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

本人想作一个cms查询框架,用于解决实际的业务问题,顺便锻炼下能力 #1 背景介绍算法

在一个配置管理系统即cms系统中,有不少的实体,实体间有不少的关联关系,这些实体就是构建成了一张网。以下图所示: cms系统中实体间的关系图sql

目前面临的问题是,如何轻松应对其余用户对实体的各类各样的查询需求?数据库

#2 需求整理json

站在用户的角度来分析需求缓存

##2.1 用户的输入服务器

###2.1.1 用户要查询的数据框架

  • 可能只须要获取某些想要的字段
  • 可能想获取每一个实体的全部字段

###2.1.2 查询条件线程

  • 各类各样的查询条件,如debug

    如 =、 !=、>、<、like、in、exists 等查询条件,时间段查询等
  • 查询条件间的and or 关系,以及多层条件嵌套设计

    如 a>2 and (b>3 or c<4)

##2.2 查询输出

###2.2.1 字段对应的值的格式化

如某个表的type字段的值为0或1,须要将这些0或1转化成有意义的值,如 0表示online 1表示offline

###2.2.2 返回的数据形式的格式化

sql查询的结果是平铺的形式,然而返回给用户的但愿是一个格式良好的形式,因此要求必须具有格式化的功能。

目前可能的格式化形式以下所示:

  • 形式1 a下的全部的b(外层主体内容是a,而后包含一个b的集合)

    {
    	"aName":"name1",
    	"bs":[
    		{
    			"bName":"name2"
    		},
    		{
    			"bName":"name3"
    		}
    	]
    }
  • 形式2 b的信息(外层主体是b,而后包含一个a的信息)

    {
    	"bName":"name2",
    	"a":{
    		"aName":"name1"
    	}
    }
  • 形式3 a下全部的b、c(外层主体是a,而后包含一个b的集合,也包含一个c的集合)

    {
    	"aName":"name1",
    	"bs":[
    		{
    			"bName":"name2"
    		},
    		{
    			"bName":"name3"
    		}
    	],
    	"cs":[
    		{
    			"cName":"name4"
    		},
    		{
    			"cName":"name5"
    		}
    	]
    }

#3 大概的设计

  • 1 用户查询封装成QueryBody

  • 2 对QueryBody进行解析,解析成sql

  • 3 根据sql查询出对应的结果

  • 4 对sql查询的结果进行值的格式化和形式的格式化,返回满意的结果

##3.1 用户查询封装成QueryBody

QueryBody就是配置用户需求的地方。它的来源有两种方式,分别以下:

  • 方式1 用户配置QueryBody的一些参数直接进行http请求

    这种方式的状况下,用户须要了解QueryBody的配置意义,同时用户能够自行进行各类各样的查询

  • 方式2 根据用户的查询需求,在后台配置QueryBody的参数,并映射对应的key,而后让用户拿着key来进行查询,以下

    {
    	"key1":{QueryBody1的配置},
    	"key2":{QueryBody2的配置}
    }

    用户拿着key1来查询,即咱们使用key1对应的QueryBody配置来进行查询。这种方式,用户不须要关心QueryBody的配置,但只能按照咱们后台所配置的参数进行查询了。方式1就不须要维护信息,方式2就须要维护key对应的QueryBody的配置信息

以上的这两种方式均可能会出现,因此都要支持。

##3.2 对QueryBody进行解析,解析成sql

这一部分其实就是一个sql生成器。这里须要说明的是,咱们并非去设计一个复杂的sql生成器,而是针对cms系统常见的查询操做可以生成sql就好了。因此不要期望这个查询框架能自动帮你生成复杂的sql。 可是有不少地方要注意:

###3.2.1 对解析要进行缓存

方便下一次相同的请求直接跳过解析过程,加快搜索。然而一旦服务器关闭,缓存就消失,因此是否要考虑将解析缓存持久化起来,在服务器启动的时候,就去先加载解析缓存。

同时容许清空缓存等操做

###3.2.2 普通查询sql的几个要素

普通sql以下所示:

select 表1.column_a,表1.column_b,表2.column_c,表3.column_d 
from  表1 表2 表3 
where 表1.column_e>4 and 表2.column_f='test'

主要分红三大部分:

  • 第一部分 要查询的column

    这一部分,可让用户本身输入,还要进行下配置映射,避免对外暴漏数据库中的表和字段名

    上述方式通常不多,因此大部分的时候仍是,查询表1 表2 表3 的所有有用字段的信息,因此须要在后台配置哪些表的哪些字段须要对外暴漏。

    上面的两种方式也都是要支持的

  • 第二部分 表之间的链接关系

    一种方式就是,直接配置表与表之间的链接关系(简单粗暴,可是会有不少的重复配置信息)

    另外一种方式就是,只配置两两表之间的链接关系,经过一个针对图的算法模块来找到表之间的链接关系。如博客开头的图片中,算法模块可以自动计算出entity1和entity4之间的链接关系是 entity1->entity2->entity3->entity4。这种方式大大减小了配置的冗余度。

    虽然算法这一块是美好的,仍然会存在不少的问题。算法要找出最短路径,可是最短路径的链接关系不必定是用户想要的,因此有时候又不能来依靠算法,还须要人为的干预配置。如上图中的entity1到entity7有2条途径,算法只能给咱们推荐一个最短的路径,但这并不必定是用户想要的,因此在这个时候,咱们是须要配置使用哪条路径的

  • 第三部分 查询参数部分

    用户的查询条件就是一个json对象,咱们要把这个json对象转化成sql中的where部分

    用户的查询条件是各类各样的,同时查询条件是能够嵌套的,以下两种查询条件

    {
    	"a.name":"lg",
    	"b.age@>":24
    }

    这里就表示查询 where a.name='lg' and b.age>24

    {
    	"a.name":"lg",
    	"$or":{
    		"b.age@>":24,
    		"c.id@in":[1,2,3]
    	}
    }

    这里就表示查询 where a.name='lg' and (b.age>24 or c.id in (1,2,3) )

    因此但愿可以作到上述的查询效果,这就须要设计一系列内置的查询参数解析器,同时方便用户自定义扩展查询参数解析器,来支持更加复杂的查询

##3.3 根据sql查询出对应的结果

这一块就须要借助于如Spring的JdbcTemplate来执行相应的sql,或者借助于其余,这就须要思考如何更加方便的接入呢?

##3.4 对sql查询的结果进行值的格式化和形式的格式化,返回满意的结果

sql的查询结果是平铺的,这时候就须要进行聚合操做,聚合操做就须要依据外层实体的主键做为聚合的重要依据了。

对数据进行格式化处理,就须要遍历查询结果,一一进行处理。而后返回给用户,若是用户还要进行相应的处理操做,又要遍历一次,形成浪费,因此该框架还要支持用户配置一些处理操做。

#4 还涉及的问题

##4.1 日志

  • 该框架应该不能定死所使用的日志系统,因此须要采用slf4j这个统一的日志接口

  • 对于程序中哪部分的日志采用debug级别,哪部分的日志采用info级别须要仔细考量。简单来讲就是,程序的主要执行过程采用info级别,使得咱们可以迅速定位错误缘由就能够了,而一些详细的解析过程采用debug级别就够了。

##4.2 配置文件的监控模块

为了避免用重启服务器,就能方便的发布新的API、或者改动老的API,就须要对这些配置文件进行监控。

由于这些各类各样的配置文件会不少,因此就须要把监控单独写成一个模块,方便外界随意的添加监控项。

同时还须要监控的总开关和每一个监控项的子开关,来随时关闭或者打开某个监控项。

##4.3 上下文环境Context

在解析的过程当中,用户的查询QueryBody,会在不少地方都要用到,若是都是做为方法的参数传递来传递去,将很是难看和难以维护,很明显这里就须要用到ThreadLocal形式了,将相似QueryBody和一些对应的缓存存储到绑定的对应的线程中,经过ThreadLocal对象来随时随地获取这些数据。最好是弄一个上下文环境来封装数据。

##4.4 异常处理

配置文件加载、解析产生的异常要进行规范的自定义处理

##4.5 集成与配置

如何更加方便的使用与配置这个cms查询框架

#5 最终达成的效果

仍是如文章最上面的图

1 用户输入以下:

{
	"entites":["entity1","entity2s@listentity2","entity3s@listentity3"],
	"params":{
		这里放置查询参数
	}
}

则表明查询的是全部的entity1,以及它所包含的entity2和entity3,返回的数据格式是以下形式的:

{
	"entity1Name":"aaa",
	"entity2s":[
		{"entity2Name":"xxx"},
		{"entity2Name":"xxx"}
	],
	"entity3s":[
		{"entity3Name":"xxx"}
	]
}

2 若是用户输入以下:

{
	"entites":["entity2","entity1@mapentity1"],
	"params":{
		这里放置查询参数
	}
}

则表示用户想查询entity2的信息,而且想知道每一个entity2所属的entity1信息,是以下格式的:

{
	"entity2Name":"aaa",
	"entity1":{
		"entity1Name":"xxx"
	}
}
相关文章
相关标签/搜索