通用查询设计与实现

2,770 阅读9分钟

在公司做这个通用查询功能也已经有很长一段时间了,今天有空就来总结一下通用查询是如何一步步设计实现的以及它设计的初衷是什么。我将采用三段论的形式来叙述这篇文章,即通用查询是什么?为什么需要?该怎么实现?

缘起

相信大家都知道基本上每个公司(尤其是互联网公司)都有这么一个内部系统,这个系统一般有这样的特征:

  • 系统内部员工使用
  • 系统主要是为其他系统提供必要的数据支持
  • 对于界面UI没有特别的要求,样式简单且单一
  • 注重数据

如下图(注意本篇文章所有的截图都来自于开发环境,数据没有任何意义):

image.png 又或者这样:

image.png

像这样的界面你是不是觉得很熟悉?没错今天就是来讲它。
技术栈
后端使用 spring boot、jpa、MySQL
前端使用 element-ui + vue

为什么需要通用查询

在定义通用查询是什么之前我们来说说为什么需要通用查询,这样一步步引出什么是通用查询。
就像上面的截图那样一个功能,这样的功能从需求提出到上线,我们一般会经历这样一段过程:

  • 运营或其他人提出需求
  • 产品经理设计
  • 技术开发
  • 功能测试
  • 部署上线

技术开发那块可能需要前后端配合,前端需要实现页面列表,后端需要开发接口提供列表所需要的数据,然后前后端分别自测接口联调等等。

像这样的一个简单的功能真的需要经历这么多的步骤以及这么多的人员才可以顺利上线吗?
回头想想,这样一个列表功能前端以及后端都是一些类似的重复的工作。

前端都是一些列表,不同的就在于数据、表头、检索条件。
后端一般都是提供一个接口,接口实现一般都是编写一个SQL从数据库中获取数据,一般来说都不需要对数据做特殊处理直接返回给前端,不同的就是SQL脚本不一样而已。

那么,有没有一种方式解决这样的问题呢?答案是有的,通用查询应用而生。

什么是通用查询

通过上面的分析我们急需有一个功能可以简化上面繁杂的开发任务,因为前后端实现这种列表功能代码都很类似甚至是重复。我觉得在软件设计中所有重复的东西都可以复用。
那么通用查询最好能做到 零开发、简单测试、零部署上线,只需要通用简单的配置就可以实现这样的一个列表功能。

如何实现通用查询功能

设计一个通用查询主要有哪些哪点?又该如何去设计呢?我们可以把前后端分开考虑

前端

相似处:

  • 都是样式一致的列表

不同处:

  • 列表的数据不同
  • 列表数据展示方式不同(文本展示、图片展示等)
  • 列表的表头不同
  • 列表的检索条件不同
  • 列表页的功能不同(导出Excel、添加等等)
  • ...

后端

相似处:

  • 都是通用SQL从数据库获取数据并返回结果

不同处:

  • 查询的SQL语句不一样
  • 查询条件,接受参数不一样
  • 返回结果不一样
  • ...

设计思路

通过上面的比较,可以如下考虑:
前端和后端写一套公共的代码,所有列表的需求只需要配置数据SQL、配置表格样式、配置检索条件、配置表格中的列,然后将配置写入数据库。当要加载某一个功能时就可以从数据库中获取对应的配置,获取业务SQL并执行SQL拿到列表中的数据。

下面举个例子
比如我有一个根据多个纬度获取人员信息列表功能,我们可以把获取人员信息这个SQL脚本配置到数据库里,进入获取人员信息列表页面的时候发起后端请求数据,我们找到获取人员信息对应的SQL并执行这条语句,然后将返回结果返回给前端不就可以了,所以这个时候根据什么来找到哪条具体的业务SQL这个很关键,一般我们都是通过ID来找到某条记录,但是在这里很显然不能这么做,因为前端无法确定ID是多少。 最好的办法是通过前端页面的路由Key作为这条记录的key值 image.png 前端每个页面都有唯一的路由,我们可以通过这个路由定位到需要执行哪一条业务SQL语句。 这样就解决了执行SQL的问题 入参可以通过参数名和参数值指定,出参最后给到前端的都是JSON,我们可以统一返回一个Map。

如果设计思路没有太懂没关系,上代码:

数据库表结构设计:

-- 通用查询配置表
CREATE TABLE IF NOT EXISTS `T_QUERY_SQL_CONFIG` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
  `route_key` varchar(150) DEFAULT NULL COMMENT '路由key',
  `config_name` varchar(100) DEFAULT NULL COMMENT 'SQL配置名字',
  `rule_key` varchar(100) DEFAULT NULL COMMENT 'SQL语句配置在规则表里面的key',
  `data_url` varchar(255) DEFAULT NULL COMMENT '数据来源于后台接口URL',
  `paged` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否分页',
  `exported` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否导出',
  form_query_id bigint(20) DEFAULT NULL COMMENT '结合通用表单修改时获取数据回显的通用表单配置ID',
  `form_del_id` bigint(20) DEFAULT NULL COMMENT '结合通用表单删除时通用表单配置ID',
  query_need_condition tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否列表查询需要带条件(默认不查询)',
  component_path varchar(255) DEFAULT NULL COMMENT '异步组件地址',
  button_config varchar(2000) DEFAULT NULL COMMENT '按钮配置JSON',
  js_value varchar(500) DEFAULT NULL COMMENT '字段js增强',
  `edit_operation` tinyint(2) NOT NULL DEFAULT '0' COMMENT '修改操作 1 表示普通修改 2 表示结合通用表单修改',
  `del_operation` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除操作 1 表示普通删除 2 表示结合通用表单删除',
  `del_url` varchar(255) DEFAULT NULL COMMENT '删除列操作URL',
  table_config varchar(800) DEFAULT NULL COMMENT '表格配置'

  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除标志',
  `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
  `created_by` varchar(20) DEFAULT NULL COMMENT '创建人',
  `created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_by` varchar(20) DEFAULT NULL COMMENT '更新人',
  `updated_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  UNIQUE  INDEX  unique_index_routeKey (route_key  ASC),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='通用查询配置';

-- 通用查询列配置表
CREATE TABLE IF NOT EXISTS `T_QUERY_COLUMN_CONFIG` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
  `sql_config_id` bigint(20) DEFAULT NULL COMMENT '通用查询配置ID',
  `column_name` varchar(100) DEFAULT NULL COMMENT '列名',
  `column_display` varchar(100) DEFAULT NULL COMMENT '列展示中文名',
  `display` tinyint(1) NOT NULL DEFAULT '0' COMMENT '该列是否显示',
  `sorted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '该列是否排序',
  `display_way` tinyint(2) NOT NULL DEFAULT '0' COMMENT '该列显示形式 0.普通文本 1.switch 2.图片',
  `searched` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否需要搜索',
  `search_type` tinyint(2) NOT NULL DEFAULT '0' COMMENT '搜索类型 0.文本框 1.下拉框 2.时间插件',
  `json_value` varchar(500) DEFAULT NULL COMMENT '搜索类型为下拉框形式,下拉框内容JSON值',
  `placeholder` varchar(100) DEFAULT NULL COMMENT '作为搜索列时输入框提示语',
  `column_width` int(11) DEFAULT NULL COMMENT '列宽',
  general_form varchar(500) DEFAULT NULL COMMENT '列关联的通用表单配置',
  general_query varchar(500) DEFAULT NULL COMMENT '列关联的通用查询配置',
  js_value varchar(500) DEFAULT NULL COMMENT '字段js增强',
  `js_operated` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否有JS操作',
  `exported` tinyint(1) NOT NULL DEFAULT '1' COMMENT '该字段是否允许导出操作',

  `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除标志',
  `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
  `created_by` varchar(20) DEFAULT NULL COMMENT '创建人',
  `created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_by` varchar(20) DEFAULT NULL COMMENT '更新人',
  `updated_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='通用查询列配置';

上面两张表就是实现通用查询功能数据库表支持,T_QUERY_SQL_CONFIG 是通用查询配置表,主要用于配置列表行为的,比如是否分页,是否可以导出excel、列表样式配置、对应的前端路由等等,而 T_QUERY_COLUMN_CONFIG 表是对列表里面的列做配置的,可以看出 T_QUERY_SQL_CONFIGT_QUERY_COLUMN_CONFIG 是一对多的关系,也就是一个列表配置对应多个列表里面的列配置。

通用查询配置阶段

我结合前端UI设计来配合讲解如何设计通用查询的配置

image.png 上面的图可以分成三块:数据源配置、表格配置、其他配置 数据来源可以是SQL语句,也可以来源于接口,先说SQL语句的方式吧,也是最常用的。
填写SQL: 这个SQL就是执行某个功能对应的业务SQL,比如这里是查询弹窗列表,那个这个SQL就是获取弹窗列表的SQL。
配置名称:就是记录一下名称
对应路由:这个非常重要,是对应到前端的页面路由,我的设计就是每一个页面的路由对应一个列表页,自然也就对应一个查询功能了。比如下面这个功能获取页面专题列表,通过这个前端路由来定位整个通用查询的配置,所以这个路由Key是唯一的。

image.png

按钮配置:主要是配置列表上面的按钮的,如下图 添加专题按钮 就是这个配置生成的。

image.png

异步组件Path:前端自定义的VUE组件,需要这个原因是支持一些复杂场景,比如列表中的某一列可以点击,并且点击之后弹出一个新的页面,这种方式通用查询是无法做到的,因为这个不固定,我无法知道这一列点击之后需要做什么操作,因此,这里的方案就是把未知的交由使用者去实现,这也是给通用查询做一个扩展。