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