后端工程师的web框架
引子
由于前后端分离,分工越来越明细了,大家也就越来越专业了,但也带来一个问题,前端的不会写后端,后端的不会写前端,当然是排除全栈的同学啊。做为一个后端程序员来说,注意力在后面业务逻辑的实现,技术越做越深,往往有些好的idea,没有web框架来帮它展现和发挥,急啊。比如,我在做duckula中间件的时候也需要一个ops做为控制调度中心,丑不丑另说,很多的想法需要前端来验证。但前后端分离了,导致我的开发成本偏高,而且在需要复杂操作的功能下,我得学习好久才能完成,干脆, 就自己发明轮子,架构了这么一套适合于后端工程师开发的web框架-tams,它可以通过注解的方式完成一个页面的增删改查等基础操作,结合maven的脚手架和MyBatis Generator来使用,可以做到在10秒(真不吹牛)内完成一个页面的开发工作。
技术栈
tams是一个前后端不分离的web框架,前端采用EasyUI,它在企业应用系统使用的非常广泛,基本取代了之前的extJs之类的富客户端框架,后端采用了 apache的顶层项目Tapestry(http://tapestry.apache.org/),tapestry是面向组件框架的鼻祖,在spring刚出来的10多年前就蛮流行了,蛮多的企业基于它构建自己的企业组件库,沉淀为公司的资产。 但由于tapestry 3、tapestry 4、tapestry 5不向下兼容,伤害了很多企业,还有过高的学习曲线和前后端分离的趋势,导致了tapestry一直是小众web框架。但它的面向组件的思想与现在的低代码要达到的目的是一样的,可以说是殊途同归。本人更倾向于面向组件的方案,组件化是需要精通业务的专家和面向组件的专家共同努力才做到的。现在springboot的一统后端的今天,tapestry 也积极响应,在5.7版本就直接以springboot2为座基,架构了专业的面向组件的web框架。5.8更进一步,升级到spring2.5.4,且支持rest接口,且集成了OpenAPI 3.0 (Swagger),详见:https://tapestry.apache.org/rest-support-580.html ,这样省了许多的框架整合步骤。
而tams蛮早以前也是我基础tapestry和easyui这2款面向组件的架构上构建的web框架,说简单点就是写了一堆的tapestry组件,这些组件可以直接生成easyui的组件代码。 虽然tapestry5.7改了技术座基,但这些组件也都能兼容,说明tapestry之前做的保证“在tapestry5以后会向下兼容”还是很认真的。easyui也是升级了很多版本,不过也一直是向下兼容的。它的ORM采用了mybatis plus。 在今年(2021年),由于要做流计算平台,没有前端,使我想起了tams这个老家伙,但由于不是自己一个人开发,就需要更彻底的封装,我做了layoutquery组件,它是在之前的组件上再做的一次封装。
内容
tams有非常多的组件,不太可能一一介绍,这里会重点介绍layoutquery组件,在使用它的时候会使用其它组件,如下拉列表combobox组件,下拉grid组件, 还有一些就是较特殊的组件,如sql、yaml编写的专用组件,文件上传组件等。还有就是会重点介绍mybatis的插件:SQLInterceptor ,如何用它来做租户、更新人、创建人、更新时候、创建时间等字段的管理。所有的代码会放到:
https://gitee.com/rjzjh/tams/tree/master/tams-demo项目中可自由下载。
maven脚手架
一个框架,最容易使用的途径也就是脚手架了,tams也做了一个脚手架,如果你安装了maven(https://maven.apache.org/),那么使用这个命令生成你的tams项目,它会自带一些页面示例:
1 |
|
参数“type”是指类型,1为支持mybatis的项目,需要数据库支持,0为不含有mybatis的项目,也就不需要数据库了。
查看它的pom文件,你会惊讶地发现,它是一个标准的springboot项目,目录结构如下:
目录 | 说明 |
---|---|
bak | 配置文件,把它放到用户目录下 |
src\main\java\net\wicp\tams\app\tams\demo\beans | 用于存放自定义的bean,mybatis生成的po等javaBean文件 |
components | 项目专用的组件库,通用的组件库会提出来形成一个jar文件。 |
constant | dbcol:表的字段的枚举值,用于组装mybatis的查询语句等。 其它:一些常量和枚举类型 |
dao | mybatis生成的dao |
pages | 是所有页面逻辑存放的地方 |
services | tapestry专用服务 |
spring | spring的service存放的地方 |
src\main\resources\mybatis\ | mybatis配置文件存放的地方 |
src\main\resources\net\wicp\tams\app\tams\demo\components | 项目专用的组件库的模板存放的地方 |
src\main\resources\net\wicp\tams\app\tams\demo\pages | 页面模板存放的地方 |
bak\tams-demo.properties:项目配置文件,把它按实际情况修改后放到操作系统登陆帐号的Home目录
resources\META-INF\tams\dbinit\default.sql:项目初始化sql脚本。
一切准备好了,我们接下来使用maven插件来产生CURD代码。如果你项目不作任何改动,把“tams-demo.properties”文件直接复制到用户的HOME目录后,就可以直接启动生成的项目了,系统会自动在tams-demo.properties文件指定的mysql数据库会自动创建一堆的系统表,同时还会生成一张demo_table表。默认项目里会有这张表的po
产生CURD代码
tams的CURD插件在MyBatis Generator插件上进行了定制,可以一键生成基于表的增删改查。产生curd的maven命令为:
1 |
|
现在说明一个上面参数的意义:
host :数据库地址
pwd :数据库密码
user :数据库用户名
db :数据库库名
tb : 数据库表模式,支持正则表达式,可以批量生产相关po
basepg:基础包路径,就是“pages”目录所在的包
pagepg:要生产的页面所在的相对路径,如上面将会把页面的逻辑java类生成在“net.wicp.tams.app.tams.demo.cas”包下
下面是未列出的参数:
needPage:是否需要生成管理页面,默认为true
skipfileds:跳过哪些字段不需要生成注解,默认为SQLInterceptor管理的字段,如果你要定义自己的跳过字段也请把它们带上:tenantId,id,version,createTime,createBy,updateTime,updateBy,isDeleted
注意这个插件主要生成4个文件,
1、model就是上面提到的bean。
2、表中所有字段组成的枚举类,防止字段改名导致不必要的查询错误
3、在pages页面下生成管理页面(java和tml)
我们执行完这个插件后,一行代码未写,就可以把这个页面跑起来看看效果,访问http://localhost:8080/cas/SysUserManager:
很显然,这个插件还需持续完善,但基本的功能都已具备了,它会把数据字段的common做为label,增删改查全部写好,且有些“打开窗口”、“调用后端服务”等示例功能。其中最应该优化的是这些字段都是ValidateBox组件。
layout组件
现在,我们的主角该上场了,当你翻看前面CRUD生成的代码时,你会发现它的model有许多的注解,这也是layoutQuery组件所需要的,layoutQuery组件它会读取这些注解,然后“翻译”为一个个的小组件,最后构成了一个完成的增删改查页面。先放一放这些注解 ,我们先来了解一下一个layout组件如何使用,在tapestry,一个页面是由它的模块页而(tml)和它的逻辑page类(pages包下面的类)组成,先看看使用了layoutQuery组件的demo\CurdLayout页面模板是怎样,打开文件CurdLayout.tml:
1 |
|
t:type:用于定义layoutQuery组件
xmlns:t:定义tapestry官方定义的组件 ,具可以参考:https://tapestry.apache.org/component-reference.html
xmlns:r:这是tams定义的组件库所使用的名称空间
xmlns:p:这是tapestry的参数需要传入的名称空间
除了这些常配置外,没有一行js代码。再看看页面类CurdLayout.java是有什么特殊的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class CurdLayout extends ParentPageBeanMyBatis<SysUser> {
@Inject
private SysUserMapper sysUserMapper;
/****
* 修改和新增调用的业务逻辑
*
* @parma t 就是ParentPageBeanMyBatis所定义的泛型类
* @param isInsert 是否新增,true:新增 false:修改
*/
@Override
public void doSave(SysUser t, boolean isInsert) {
if (isInsert) {
sysUserMapper.insert(t);
} else {
sysUserMapper.updateByPrimaryKeySelective(t);
}
}
// 删除的业务逻辑
@Override
public void doDel(String id) {
sysUserMapper.deleteById(id);
}
// dao类,给ParentPageBeanMyBatis提供操作的dao实现
@Override
public BaseMapper<SysUser> getBaseMapper() {
return this.sysUserMapper;
}
// 查询的页务逻辑,框架会把页面的查询条件封装到"t"这个参数,父类会拿queryWrapper这个参数进行查询
@Override
public void packageQuery(SysUser t, QueryWrapper<SysUser> queryWrapper) {
if (StringUtil.isNotNull(t.getUserName())) {
queryWrapper.likeRight("user_name", t.getUserName());
}
}
}
看上去也没有什么特别的,首先,它继承了ParentPageBeanMyBatis类,这个类是tams提供的父类。它封装了增删改查的主体逻辑,但也提供了一些勾子需要子类来实现的。增删改查的业务逻辑都有较核心的部分留给了子类来实现,像类已把增删改查的其它逻辑做完了。
好了,这个页面就做完了,看到这是有点蒙,封装的有点狠,那一个页面还有很多东西要处理,如,我要如何定义查询字段?如何定义新增和修改字段,如何定义查询后返回给前端用于列表显示的字段?这一切的一切都在上面提到的注解里,这个页面继承了“ParentPageBeanMyBatis
1 |
|
只要理解了这2个注解,也就会使用layoutQuery组件了,也就会使用tams框架了。
1 |
|
1 |
|
看代码还是枯燥了,那么来几个示例,先看看这张表是啥样的:
1 |
|
枚举
先看“status”状态字段,这明显是个枚举啊,可以这么配置:
1 |
|
查询后的结果,数据库都是存放的yes或是no,但在页面上,框架全部分把它们转为是和否
combobx
看了上面的枚举,那来一个正常的非枚举的combobox如何配置呢?参考级联那一节的combobox配置。只要去掉parent选项就可以了。
ValidateBox
验证框是最常用的输入框,它可以定义正则表达式。看看邮箱的验证配置:
1 |
|
效果如下:
那么这个提示在哪设置的呢?参看枚举类:StrPattern的email枚举值。
1 |
|
combogrid
我们来看看用户地址表“sys_user_addr”,可以参照上面的“产生CURD代码”一节,完成相关管理页面的生成功能,使用的命令是:
1 |
|
生成了model里可以看到,自动生成了所有字段的管理功能,接下来我们来对它做一些改造。首先,用户id肯定是查询用户表,使用下拉列表来选择的方式来的靠谱,如果没有使用框架,对于熟悉前端的开发就会明白,这个小功能要做的功能不少,首先,要后后端提供一个不做翻页的接口,其次前端需要做一个页面弹框来查询选择,或是开发一个下拉框来做选择,最后,查询结果的grid还要翻译user_id选择的用户名。为了这点功能,工作量不会小。tams提供了一个combogrid组件,只需这么一行注解就能搞定:
1 |
|
这里面有2点要注意:
性别要写gender1才是中文解释,这是框架自动产生的一列。而gender是数据库里存的值。同理status1也是一样
url是用户页面的查询地址,加了needpage=false表示不需要翻页,如果有其它查询条件的话,就在?后面再加参数,参数名就是Sysuser类的字段名,这样就重用了之前的查询逻辑,不需要为了这个功能另建接口。减少了工作量不好,还大大降低了维护的成本。
效果如下图所示,在用户这个字段出现了combogrid选择框:
级联
我们是示例项目,可能为了说明组件会做一些不太合理的假设,只要能说明问题就好了,大家不必太在意它的合理性,假设用户下面挂了多个手机号,用逗号分隔的,存放在mobile这个字段里,为了收货方便,我们的每个地址必需要保留一个手机号的,也就是说,我们选择了用户后,需要级联的拿到它所对应的手机,形成一个下拉列表让用户来选择,为了完成手机号的选择功能,前后端要实现的功能不少,首先前端要再调一次后面的查询用户接口拿到所有的手机号,要做一个下拉列表来存放这些手机号,其实,当切换了用户后要清理这个手机选择列表,还有在修改地址信息时也要跟据原来的地址信息初始化这个下拉列表,要考虑的细节问题真不少。但在tams框架下,只要这么配置:
1 |
|
再看看这个selMobile都做了些啥:
1 |
|
ok,只要处理上面2步,轻松搞定联级,且通过这种机制可以做到无限的横向级联和无限的纵向级联。
效果如下:
文件上传
项目中的文件上传的需求较为普遍,为了实现一个文件上传的功能虽多算太复杂,要做的事情也不少,form的“enctype”要设置为“multipart/form-data”,采用post提交,后面注意读取流进行保存。这些tams框架全会做掉,做的更多的是,如果有上传的文件它还会提供一个链接供下载。假设我们需要上传用户的简历(word写的)做为附件,那么我们可以在用户编辑页面做如下配置就可以了:
1 |
|
这个组件的效果如图:
上面点击链接可以下载这个附件,在显示的grid上会显示它的相对路径,那个它的根目录是哪个呢?需要配置文件上传的根目录,在AppModule.java里配置:
1 |
|
那么这个附件的磁盘路径为:d://data/duckula-data/upload/ttttt.properties
sql
这个组件的作用就是提供一个输入框,让用户可以自动的输入符合sql语句,可以会提供编写提示,这个组件使用场景不多,在流计算平台,我们用它来向flinkSQL模板提供给符合标准的SQL。要使用它也是一个配置搞定:
1 |
|
效果如下:
yaml
yaml格式是使用较为普遍的文件格式,tams也有相应的组件提供yaml文件编写并具有格式验证功能,只需如下配置:
1 |
|
效果如下:
调用勾子
我们已经看过了前面较为方便的组件用法,可以看到如果没有复杂的业务逻辑,确实可以做到不懂js就可以完成基本的增删改查功能实现,但不是所有的页面都是这样的,比如在提交表单前做一些复杂的检查,在新增加设置一些字段的默认值,等等各种精细化的页面控制是没办法脱离js的约束的,在tams的layoutQuery组件也提供了许多的钩子来完成需求,不过需要开发人员懂js和easyui(懂js的人使用这个太小儿科了)。我们拿示例页面SysUserAddrManager.tml来简要说明:
1 |
|
上面代码中这几个参数就是我们所说的钩子:
initAddHandle:在点击新增按钮,弹出表单后,会触发
initSaveHandle:在点击修改按钮,弹出表单后,会触发
saveCheckHandle:在保存弹出的表单时会触发,可以做一些个性化的校验,return false :校验不成功 ,将不会提交到后端保存
queryButs:就是一些个性化的按钮,如下图就是点击“打开窗口”按钮触发的:
总结
tams是一套非常适合后端开发人员的工具,简单页面通过脚手架,可以做到秒级完成页面的开发工作。当然,上面提到的只是它部分功能,还是很希望大家能使用,我这边也会在后面的使用中不断地完善,祝大家使用愉快。