1 easyconf的诞生
2 easyconf的设计理念
2.1 总体设计
2.2 细节设计
2.2.1 CRUD操作
2.2.2 即时校验
2.2.3 下拉框设计
3 easyconf使用指南
3.1 基本步骤
3.2 表配置文件
3.3 easyconf.js的定制
3.3.1 语言
3.3.2 URL地址
3.3.3 自定义校验方法
4 easyconf后端开发指南
4.1 请求说明
4.2 返回说明
5 下一步的工作
大概半年前做一个原型系统,有很多配置数据存储在数据库中,用户需要对这些配置项进行管理。但凡开发过这样的配置管理系统的人都会发现,为A表编写的配置管理的代码与B表大同小异。如果能够进一步抽象,我们或许可以只用开发一套前端和后端代码,就能够满足几乎所有不同的配置表的需求。
我很懒,不喜欢做重复的工作,所以我设计了easyconf——基于AugularJS的配置管理系统开发框架。easyconf支持json格式的表配置文件,我们可以在此文件中定义需要管理的表的各种信息,诸如列信息,以及列对应的前端控件信息等。easyconf的最小集是index.html和easyconf.js两个前端代码,后端可按照本文中的指南进行自定义开发,当然,我也提供了一个基于python的demo后端。
虽然,easyconf被设计用来开发配置管理系统,你也可以脑洞大开地拿它来开发各种管理系统,诸如“XX学籍管理系统”,“XX图书馆管理系统”等。很熟悉吧,至少本科的课程设计做过这样的系统。如果有了easyconf,你甚至无需写1字节代码就可以做出一个管理系统,你仅仅需要做好表设计以及对表配置文件的定制化。
截止目前(2016-07),easyconf已支持0-String/1-Long/2-Double/3-Boolean/4-Date,共5种数据类型;0-text/1-combo box,共2种控件类型;0-精确/1-包含/2-小于/3-大于/4-小于等于/5-大于等于,共6种查询方式。
MVC架构提供了Model将数据与后端中的类进行映射,而另有一些开发者将后端中的类与前端中的控件进行了映射。然而,管理系统是一类非常“单纯”的系统,大部分CRUD操作只针对单表进行,所以,我们可以放心地进行简化映射关系,将数据与前端控件直接映射起来:
上图显示了一个生动的实例,假设我们有众多的服务器架设在全国各地,那么我们需要一张名为service的表存储这些服务器的信息,其中包括服务器的IP,端口号,所在省市,以及是否启用。使用MVC架构或者基于MVC的优化架构,我们必须至少为后端编写一定量的代码(定义类)。假若使用数据对前端控件的直接映射,那么将免去这方面的工作。
easyconf支持对有主键的单表的CRUD操作。以服务器配置为例,打开配置管理主页如下:
点击“新增”,将进入新增面板:
在配置管理主页,点击“查找”,将按按照条件在主页输出查找结果:
点击查找结果的“详情”,将进入详情面板:
点击查找结果的“更新”,将进入更新面板:
点击查找结果的“删除”,将删除一条配置数据:
点击“提交”后再对用户的输入进行校验是不够人性化的,easyconf能够即时校验用户的输入:
上图展示了两种校验,第一种是数据类型校验,由于定义的“服务器监听端口”字段为整型,当输入字母“a”时将提示“必须为整数”;第二种是自定义校验,对的,easyconf还支持自定义校验函数,在本例中我自定义了IP和端口合法性校验函数。
唯一性是不太好处理的校验项:首先,为了追求真正的唯一性,在新增面板打开到关闭这段期间内进行锁表是不合适的;其次,退而求其次,每次focus在主键字段,或者修改主键字段,或者从主键字段blur都查询一下是否唯一,这种方式也是不合理的,会大大增加系统的开销。easyconf采取的是一种准即时的方式,提交新增请求后,若后端判断不满足唯一性约束,则前端记录下本次的主键字段的输入为历史输入,下一次提交前,每一次主键字段的修改都会与历史输入进行比较,只要是一致,就会提示“数据已存在”:
easyconf使用了两种下拉框:静态下拉框和动态下拉框。静态下拉框的候选内容是固定的,在表配置文件中定义。动态下拉框的候选内容是可变的,其取数的方式也在表配置文件中定义。在本例中,“省”字段的候选内容从数据库表“province”中读取,“市”字段的候选内容从数据库表“city”中读取,读取的时候还需要加上“省”字段作为查询条件;“是否启用”字段为静态下拉框:
easyconf的使用可以分为以下几个基本步骤:
表配置文件的定制是使用easyconf的核心步骤,让我们看一下这个配置文件中的各种配置项的说明:
1 { 2 title:<title>, //此处定义了标题显示内容 3 table:<table>, //表名 4 count:10, //查询结果每页条数 5 columns: //此处定义了需要显示的表的各列信息 6 [ 7 { 8 id:<col1>, //列在数据库中定义的名称 9 name:<列1>, //列在页面上显示的中文名 10 isShow:<true>, //在查询结果中是否显示 11 isPrimaryKey:[true], //是否为主键,在新增页面时,主键的列的控件触发onchange事件后,需要进行唯一性校验。 12 isNull:<true>, //是否为空,在新增或修改页面时,主键的列的控件触发onchange事件后,需要进行是否为空校验。 13 type:<0>, //列的数据类型,0- String /1-Long/2-Double/3-Boolean/4-Date 14 control:<0>, //列在页面上显示的控件类型,0-text/1-combo box 15 check: //在新增或修改页面时,主键的列的控件触发onchange事件后所调用的自定义校验函数 16 [ 17 { 18 funcname:<function1>, 19 argument:<col1, col2…> 20 }, //调用函数时,第一个参数为本控件的值,之后的参数为argument中列对应的控件的值。 21 … 22 ], 23 isSelect:<true>, //是否作为查询条件。 24 selectType:<0>, //查询条件,0-精确/1-包含/2-小于/3-大于/4-小于等于/5-大于等于 25 candidate:<0/1>, //0-固定值,1-动态值,为固定值时从fixed中取候选内容,为动态值时从flexible中取key,value,table和query组sql语句从数据库中查询出候选内容。候选内容的格式为”key-value”。 26 fixed: 27 [ 28 {key1:<value1>}, 29 {key2:<value2>}, 30 … 31 ], 32 flexible: 33 { 34 key:<col>, 35 value:<col>, 36 table:<table>, 37 where:<col1, col2…> 38 }, 39 dft:<dft? //默认值,查询层控件或新增页面对应控件的默认值。 40 }, 41 … 42 ] 43 }
在easyconf.js中我们可以定制前端上显示的要素的名称,提示的信息:
1 //定义控件的显示名称 2 names:{ 3 SEARCH:"查找", 4 INSERT:"新增", 5 DETAIL:"详情", 6 UPDATE:"更新", 7 DELETE:"删除", 8 FRESH:"刷新", 9 PREV:"上页", 10 NEXT:"下页", 11 GO:"跳转", 12 RETURN:"返回", 13 COMMIT:"提交", 14 OPERATIONS:"操作" 15 }, 16 //定义小标题 17 subTitles:['查找', '详情', '更新', '新增'], 18 //定义提示信息 19 msgs:{ 20 OK:"通过", 21 NOTNULL:"不允许为空", 22 INTEGER:"必须为整数", 23 DOUBLE:"必须为浮点数", 24 TYPEERROR:"类型错误", 25 DATE:"必须为日期[YYYY-MM-DD hh:mm:ss.ms]", 26 NOTUNIQUE:"数据已存在", 27 },
在easyconf.js中,我们还可以定义URL地址,譬如域名为“www.domain.com”,定义INSERT请求的URL为“Insert”,那么完整的URL地址为“www.domain.com/Insert”:
1 apps:{ 2 INIT:"Init", 3 LIST:"Search", 4 DELETE:"Delete", 5 UPDATE:"Update", 6 INSERT:"Insert", 7 RANGE:"Range" 8 },
在easyconf.js中,我们还可以自定义校验方法,在服务器配置的例子中,我们定义IP和端口的校验方法如下:
1 // user's function here 2 userfunc: { 3 checkIp:function(ip) { 4 var exp=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; 5 var reg = ip.match(exp); 6 if(reg != null) 7 return true; 8 else 9 return false; 10 }, 11 12 checkPort:function(port) { 13 if (0 <= port && port <= 65535) 14 return true; 15 else 16 return false; 17 } 18 }
根据本指南,我们可以为easyconf开发自己的后端:
请求 | 方式 | url | 请求数据 | 返回数据 | 说明 |
配置管理主页 | GET | <domain>/<main>.html | 无 | <main.html> | domain为域名,main.html为index.html的重命名副本 |
初始化 | GET | <domain>/<INIT> | 无 | <table.json> | table.json为表配置文件 |
列表查询 | GET | <domain>/<LIST>?table=<table>&data=<col1,col2...>&query=<col1:value1,col2:value2...>&begin=<begin>&count=<count> | 无 | 查询返回,见下 | 后端收到请求后,解析data和query参数,data参数和query中的col和value均进行了base64编码。然后组sql语句进行查询,查询出来的结果为数据库中的第begin*count+1条到begin*(count+1)条。 |
删除 | POST | <domain>/<DELETE> | 删除请求,见下 | 通用返回,见下 | 后端收到请求后,解析query变量。然后组sql语句进行删除。如果原数据不存在,也返回成功。 |
更新 | POST | <domain>/<UPDATE> | 更新请求,见下 | 通用返回,见下 | 后端收到请求后,解析data和query变量。然后组sql语句进行更新。如果原数据不存在,也返回成功。 |
新增 | POST | <domain>/<INSERT> | 新增请求,见下 | 通用返回,见下 | 后端收到请求后,解析data变量。然后组sql语句进行更新。 |
候选项查询 | GET | <domain>/<LIST>?table=<table>&key=<key>&value=<value>&query=<col1:value1,col2:value2...> | 无 | 候选项返回,见下 | 后端收到请求后,解析query参数。然后组sql语句进行查询,返回查询结果。 |
删除请求:
1 { 2 table:<table>, 3 query:<col1:value1,col2:value2...> 4 }
更新请求:
1 { 2 table:<table>, 3 data:<col1:value1,col2:value2...>, 4 query=<col1:value1,col2:value2...> 5 }
新增请求:
1 { 2 table:<table>, 3 data:<col1:value1,col2:value2...>, 4 }
查询返回:
1 { 2 result:<00>, //响应结果,00为成功 3 message:<OK>, //响应信息 4 data:[ //查询无结果则返回空列表 5 [ //查询出来的每一条数据 6 value1, //每一条数据的每列的值,如果该列是动态下拉框,那么还需要根据表配置文件进行查询组成“key|value”的形式,key和value均base64编码 7 value2, 8 … 9 ], 10 … 11 ] 12 }
通用返回:
1 { 2 result:<00>, //响应结果,00为成功,插入时01为主键冲突 3 message:<OK>, //响应信息 4 }
候选项返回:
1 { 2 result:<00>, //响应结果,00为成功 3 message:<OK>, //响应信息 4 data:[ //如果查询无数据则返回空列表 5 { //每一条数据 6 key:<key> 7 value:<value> 8 } 9 … 10 ] 11 }
easyconf从设计到编码花了我两天时间,目前的版本能够满足基本的配置管理需求。但是仍由很多地方急需改进和优化,例如:页面的美化(并且也能够通过表配置文件定制),更加健全的异常处理,外键约束的校验,表操作的权限管理,等等。由于懒,我搞出来这个一个玩意儿,同时又给自己挖了好大一个坑,之后慢慢填吧。大家可以从github上下载easyconf以及demo后端。