在跨数据库环境下使用JFinal-Layui的权限管理思路分享

        之前琴海森林在JFinal分享他的脚手架项目JFinal-Layui(http://www.jfinal.com/project/258),强烈安利给准备做企业中小型项目的同学们尝试。本人已在公司实际项目中运用,开发体验非常不错,方便快捷。特别是经典的功能-用户-角色的权限管理功能做的非常好,十分灵活,即可以为单一用户直接分配角色,又可以逆向为角色指定用户。

image.png

为用户分配角色

image.png

为角色分配用户

    使用了这套脚手架做常规企业内部系统是非常方便的,节省了自行开发系统管理模块的大量时间。不过其中有一个比较常见的业务场景就是企业一般有自己OA系统,可能是自己开发的,也可能是第三方外包开发的。OA负责管理企业的用户、组织数据,并在OA平台上对各个内部系统集成了单点登录功能。比如:

image.png

这样,通常会要求各子系统在实现登录和权限控制时,需要获取OA数据库的用户、组织数据,而不能使用脚手架自带的用户组织表中的数据。另外企业级OA所使用的数据库很有可能不是mysql,而是sqlserver或者oracle。

那么如果我想用一套已经实现了权限控制功能的脚手架去进行二次开发做企业内部系统,如何在不变更业务系统数据库的前提下,实现对OA数据库中的用户进行子系统的权限分配呢,这里我分享一下我的实现思路。

    首先既然要对企业OA数据库用户进行权限配置,那么我们就要获取到OA的用户信息,意味着我们需要在系统中配置多数据源。当然,在JFinal项目中配置多数据源是再简单不过的,如图:

image.png

在自己系统的配置文件中,写上OA数据库的链接参数,然后按照JFinal的文档,在主配置类中的configPlugin中配置OA数据源和对应的ActiveRecord

		DruidPlugin oaDbPlugin = new DruidPlugin(p.get("oa_jdbcUrl"), p.get("oa_user"), p.get("oa_password").trim());
		ActiveRecordPlugin oaArp=new ActiveRecordPlugin("oa",oaDbPlugin);
		oaArp.setDialect(new SqlServerDialect());
		Engine oaEngine = oaArp.getEngine();
		oaEngine.setSourceFactory(new ClassPathSourceFactory());
		oaEngine.addSharedStaticMethod(StrKit.class);
		oaArp.addSqlTemplate(WebContant.oaSqlTemplate);		
		me.add(oaDbPlugin);
		me.add(oaArp);

注意上图第二行代码,我们为OA的ActiveRecord定义了别名"oa",所以以后我们就可以使用Db.use("oa")来切换到OA数据库取进行数据查询等操作。

并且,因为子系统中,一般不需要对OA的用户和组织数据进行增删改的操作,仅仅用于获取用户数据,以方便集成单点登录和子系统权限分配就可以了。所以在这里,我并没有为OA的用户表生成对应的JFinal Model以及MappingKit文件,使用通用的Record对象足矣。

现在回到JFinal-Layui的脚手架的用户管理界面,如图:

image.png

其实上图中我们只需要拿到OA的用户以列表页进行展示,然后有一个角色分配的按钮,和若干查询条件就搞定了,所以改造以后页面可以简化为:


image.png

这个页面,无非就是吧之前查询系统的sys_user表切换为查询OA的用户表,所以后台改起来很简单,在脚手架中写一个自己用的OAUserService和OAUserController

image.png

在Controller类中实现获取用户数据的方法

	/**
	 * 分页查询OA人员信息
	 */
	public void list() {
		JSONObject params = getAllParamsToJson();
		Grid grid = oaUserService.queryForListBySqlTemplate("oa",getParaToInt("pageNumber",1), getParaToInt("pageSize",15), params,"synchro.queryOaUserByConditions",null);
		renderJson(grid);
	}

其中queryForListBySqlTemplate方法是写在BaseService中的扩展方法,用于通过执行sql模板中的sql语句,查找无而对应实体的数据列表,返回layui table 所需要的json格式数据,用于前端列表页渲染

代码实现如下:

	/**
	 * 自定义sql查询,sql定义在sql模板文件中 
	 * @param dbName 数据库配置名称,用于切换多数据源查询,为null时查询主数据源
	 * @param params 查询参数(JSONObject)
	 * @param sqlTemplateName sql唯一名称(命名空间.sql语句名称)
	 * @param orderBygroupBySql 排序语句
	 * @return 
	 */	
	public Grid queryForListBySqlTemplate(String dbName,Integer pageNumber,Integer pageSize,JSONObject params,String sqlTemplateName,String orderBygroupBySql) {
		DbPro dbPro = Db.use();
		//切换数据源
		if(StrKit.notBlank(dbName)) {
			dbPro = Db.use(dbName);
		}
		SqlPara sqlPara = dbPro.getSqlPara(sqlTemplateName,params);
		if(StrKit.notBlank(orderBygroupBySql)) {//如果有排序语句,则追加
			sqlPara.setSql(sqlPara.getSql() + " " + orderBygroupBySql);
		}
		Page<Record> page = dbPro.paginate(pageNumber,pageSize, sqlPara);
		return new Grid(page.getList(),pageNumber,pageSize,page.getTotalRow());
	}	

该方法提供通用的执行sql模板中定义的sql获取表格数据的方法,其中dbName参数用于切换不同数据源,sqlTemplateName用于指定具体sql id。

接着,在功能管理中,把用户管理的链接做切换即可。

image.png

这样关于用户列表中的数据切换我们就做完了。

然后,用户的角色分配界面,不需要做任何变更

image.png

从系统的表设计中,我们可以知道角色分配功能(即菜单),用户分配角色,所以不管用户数据来源是什么,只要拿到用户id,我们就可以对该用于进行角色授权操作。用户-角色关系由中间表保存,这个界面的前端与后台代码几乎不需要做任何变更。

image.png

这样我们已经实现了给OA中的用户分配应用系统的角色。如果对权限配置需求不高,这样已经可以满足系统的使用。而且不受业务系统数据库与OA数据库之间的差异,也不需要考虑OA方提供接口或者做成微服务来调用,一个多数据源配置轻松搞定。

关于如何使用JFinal SQL动态管理,请移步https://www.jfinal.com/doc/5-13 学习

关于JFinal-layui的BaseService,参见 https://gitee.com/QinHaiSenLin/Jfinal-layui/blob/master/src/main/java/com/qinhailin/common/base/service/BaseService.java?oid=d1f7a3727d67f85e29af621197c448225e2424ff

不过,如果我们想做的更加完善,我们还想反过来,在角色管理页面,我们直接为该角色指定用户,功能界面如下:

image.png

可以看到,对于单一角色的用户分配,这个界面操作非常流畅,从左边可以筛选用户赋予该角色权限,从右边可以将已经拥有该角色权限的用户剔除。

那么很明显的,左右两边的数据来源呢,自然应该是来源我们OA数据库了。这里,如果OA数据库与系统数据库类型一致,都是mysql,我们自然会想到

左边的sql写法类似为 select * from sys_user where user_id not in (select user_id from sys_user_role where role_id = ?)

右边的sql写法类似为 select * from sys_user where user_id in (select user_id from sys_user_role where role_id = ?)

也就是说左右两边的用户的查询sql仅仅只有in和not in的区别。

假如用户数据库与业务数据库不是同一类型数据库,比如我这个项目,系统用脚手架开发,自然业务数据都放在了mysql中,而公司的用户数据,都在OA数据库,也就是sqlserver中。

上面那两条sql的写法显然不能够使用。也就是我们无法在跨数据库数据表之间进行关联查询,虽然Sqlserver提供了链接对象查询机制去查询其他数据库数据的功能,有兴趣可以查看下面的博文:

https://blog.csdn.net/jk1992jk/article/details/80220863

可是并不是我们现在想要的,因为我们业务系统主数据库是mysql,自然不会在OA数据库里去查子系统的数据。

仔细查看上述的两条sql,我们应该能察觉到这种关系

select 用户数据 from OA where 用户id in (select 用户id from 系统数据库 where 角色id = ?

主sql部分我们查询的OA数据库,而子句部分查询的是系统数据库。OA用户数据数据范围受业务系统用户角色表的制约,那么~~

没错,我们可以把这种类型的关联sql拆分成两条sql查询,就完美的避开了跨库跨表关联查询的需求。

伪代码如下:

List<String> userIdList = Db.find("select user_id from sys_user_role where role_id = ?");

List<User> userList = Db.use("oa").find("select * from oa_user where ...",userIdList);

通过拆分,我们就完成了配置角色用用户界面的数据展示。以上图左侧角色可选用户数据查询方法为例,完整代码如下:

Controller层:

	/**
	 * 查询角色可选的用户列表
	 */
	public void queryUserListNotInRoleCode(){
		int pageNumber=getParaToInt("pageNumber",1);
		int pageSize=getParaToInt("pageSize",10);
		String roleCode=getPara("roleCode");
		List<Record> userRoleList = sysUserRoleService.queryForList("select user_code from sys_user_role where role_code = ?",roleCode);	
		JSONObject params=new JSONObject();
		params.put("userName", getPara("userName"));
		params.put("orgName", getPara("orgName"));
		params.put("loginId", getPara("loginId"));
 		params.put("itemList",userRoleList);			    
		//Grid grid=sysUserRoleService.queryUserListNotInRoleCode(pageNumber, pageSize,roleCode,record);
		Grid grid=sysUserRoleService.queryOAUserNotInRoleList(pageNumber, pageSize,params);
		renderJson(grid);
	}	

其中itemList就是我们从业务系统数据库里得到的用户id集合,后面需要用到

Service层:

	/**
	 * 查询OA数据库中未配置具体角色的用户
	 * @param pageNumber
	 * @param pageSize
	 * @param roleCode
	 * @param record
	 * @return
	 */
	public Grid queryOAUserNotInRoleList(int pageNumber,int pageSize,JSONObject params){
		Grid grid = queryForListBySqlTemplate("oa", pageNumber, pageSize, params,"synchro.queryOAUserNotInRoleList",null);
		
		return grid;
	}

sql模板(使用jfinal的enjoy引擎编写):

根据条件查询某个角色可选用户
#sql("queryOAUserNotInRoleList")
select t1.id,t1.loginid,t1.lastname,t2.departmentname
from HrmResource t1 left join HrmDepartment t2 on t1.departmentid = t2.id
where t1.loginid is not null and t1.loginid != ''
	#if(itemList.size()>0)
	 and t1.loginid not in (#for(item : itemList) #para(item.user_code) #(for.last ? "": ",")  #end)
	#end
	#if(notBlank(loginId))
	 and t1.loginid like '%'+ #para(loginId) + '%'
	#end
	#if(notBlank(userName))
	 and t1.lastname like '%'+ #para(userName) + '%'
	#end
	#if(notBlank(orgName))
	 and t2.departmentname like '%'+ #para(orgName) + '%'
	#end
#end

注意一下怎么进行for循环的迭代itemList,以及OA数据库对应的使用模糊查询的写法即可。

接着,我们还需要改造系统的登录代码,这里就不详细解释了,参考脚手架自带的登录功能,自己写一个新的登录方法,切换到OA数据库取匹配用户和密码即可,其余操昨,比如往session里存放数据之类的,照搬就行了。

好了,说到这里,关于怎么在不使用JFinal-layui自带的用户组织表进行业务系统开发并进行权限分配的思路就很清晰了。剩下的就等各位自己去摸索了~~~

再次放上脚手架分享地址:http://www.jfinal.com/project/258




评论区

琴海森林

2019-05-11 17:42

这么用心的分享,点赞

381513938

2019-05-14 00:24

在权限方面,目前采用的是用户绑定角色,角色绑定页面、按钮权限,缺乏数据管理权限。可否用户直接设置数据权限:例如,用户可以查看那些组织结构的数据?

弯道加速跑

2019-05-14 13:51

@381513938 你是指用户直接和菜单关联?这个需要自己扩展。其实,如果只是为了给少数用户设置权限,那现在的设计也可以满足需求,单独给这些人创建角色进行绑定即可。

381513938

2019-05-14 22:40

@弯道加速跑 不是菜单,主要是部门数据权限的设置,传统的做法是用户-角色-部门,若用户要管理多个部门,则需要关联多个角色,不灵活。

弯道加速跑

2019-05-15 09:08

@381513938 脚手架设计的时候肯定没有考虑到所有的用户需求~不过已经实现了角色-权限的功能,自己扩展出用户-权限,组织-权限的功能,最后权限是三者取并集,也是很容易的

Sohnny

2019-05-18 09:11

@381513938 SoJpt脚手架已经考虑了这情况,所以菜单,角色,权限,部门及单位等都放在了一张popedom表里,默认最简单的场景,只给角色付权,需要给其他部门或单位赋权,直接改改代码即可。

Sohnny

2019-05-18 09:16

@弯道加速跑 三者取并集不觉得麻烦?保存,查询,都得分别操作3张表,实现相对复杂。

弯道加速跑

2019-05-20 10:38

@Sohnny 你说的也是,不过我觉得目前角色授权是一种比较简洁的授权方式,部门授权和用户授权都可以基于新建角色绑定不同范围的用户来间接实现而不用改任何代码。对于一般中小型企业而言已经足够使用。

Sohnny

2019-05-20 15:19

@弯道加速跑 对, 得根据实际情况来使用, 间接查询在有的批量查询的场景下会效率低下.而且也增加了sql的复杂度