第四章
细粒度数据查询权限
(上)
通过基于角色访问控制,我们可以控制哪些人具有某种权限。比如总公司员工柴其贵、分公司员工李朵朵和营业部员工贾志宏,三个人都具有访问“查询员工”页面权限。
但,由于他们三人所在公司级别不同(总公司、分公司和营业部),进入查询员工页面,系统展示出来的员工数据应该是不同的。
此类数据查询权限,在业务系统里面随处可见。毕竟 N 多操作,都起源于查询。连增加数据操作都有,比如增加表单的某个下拉框内容,可能来自数据库查询。
数据查询权限,包括 2 个纬度:行级和列级。
现有方案
常见拼凑 SQL
常见做法是,采用 if else 做判断,决定执行那条程序分支,也就是 SQL 。
String sql=””;
if( user.getCompanyLevel()==Constants.总公司 ) {
sql=”select * from demouser u, company c where u.companyId=c.id”; //查询所有员工
} else if( user.getCompanyLevel()==Constants.分公司 ) {
String currentUserCompanyId=user.getCompanyId();
sql=” select * from demouser u, company c where u.companyId=c.id and c.id”= currentUserCompanyId + “ or c.pid=”+currentUserCompanyId; //查询本分公司及下属营业部员工
} else if( user.getCompanyLevel()==Constants.营业部 ) {
String currentUserCompanyId=user.getCompanyId();
sql=” select * from demouser u, company c where u.companyId=c.id and c.id”= currentUserCompanyId; //查询本营业部员工
}
// 执行数据库查询,并将结果封装为JAVA对象
变通方法
也有不少开发者对上述方法做了改进,我了解到如下改进方法:
1. 采用 AOP 技术,向 find/select 模型注入 where 条件;
2. 将 SQL 语句全部提取出来,集中保存在某个 SQL 配置文件里面,类似 HIBERNATE 那样。
上述方法,都不能减少 if else 判断,只是把 SQL 语句做了转移。 AOP 注入方式,将 if else 判断从模型层转移到注入层。集中提取 SQL 方式,只是将 SQL 转移到统一的保存文件, if else 依然转移不掉。
关于列级控制、分页查询和自定义条件查询,那就更麻烦了,在此不做叙述。
如果使用 Metadmin
在设计 Metadmin 之初,我们确定了这些目标:
1. 将行列级授权逻辑、 if else 判断全部从业务代码中剥离出去,达到权限与业务完全解开耦合;
2. 提供 API 供业务方法调用,通过该方法获取该用户具有权限查询的数据;
3. 整个过程不要编码,也不要 XML ,通过界面设计出来,并且每个查询逻辑设计完毕,可以立即在线测试,保证查询逻辑无误。
为此, Metadmin 提供如下服务:
1. Metadmin 提供数据查询 API :告诉 metadmin ,当前是谁想要查什么数据, metadmin 就能返回该用户具有权限查询的数据;
2. Metadmin 提供的 API 支持分页和自定义条件查询,当然这一切都是在该用户的授权范围内;
3. 权限设计器,通过设计器展现出业务数据库表,运用鼠标拖拽等操作把查询逻辑设计出来,并可以在线测试;
4. 支持复杂、特定数据库逻辑手工输入 SQL ,调优性能。
以下演示来自 metadmin 下载包里面包含的演示示例,可以在 www.metadmin.com 下载 Metadmin 安装程序包。
API
MetadminService 类:
static QueryResult | query (int privilegeId, User user, java.util.Map context) |
static QueryResult | query (int privilegeId, User user, java.util.Map context, CustomizedWhere where) |
static QueryResult | query (int privilegeId, User user, java.util.Map context, CustomizedWhere where, int first, int max) |
static QueryResult | query (int privilegeId, User user, java.util.Map context, int first, int max) |
static int | queryCount (int privilegeId, User user, java.util.Map context) |
static int | queryCount (int privilegeId, User user, java.util.Map context, CustomizedWhere where) |
WebMetadminService 类,为 WEB 程序定制的类,从 HttpRequest 自动读取当前用户:
static java.util.Collection | query (HttpServletRequest req, int privilegeId) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, CustomizedWhere where) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, CustomizedWhere where, int first, int max) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, int first, int max) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, java.util.Map context) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, java.util.Map context,CustomizedWhere where) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, java.util.Map context,CustomizedWhere where, int first, int max) |
static java.util.Collection | query (HttpServletRequest req, int privilegeId, java.util.Map context, int first, int max) |
static int | queryCount (HttpServletRequest req, int privilegeId) |
static int | queryCount (HttpServletRequest req, int privilegeId, CustomizedWhere where) |
static int | queryCount (HttpServletRequest req, int privilegeId, java.util.Map context) |
static int | queryCount (HttpServletRequest req, int privilegeId, java.util.Map context,CustomizedWhere where) |
业务数据库
第一章讲解了 Metadmin 对于数据库的分类:权限数据和业务数据,两者保存在不同 schema 里面。
WEB-INF/metadmin/datasources.xml :
<?xml version="1.0"?> <datasources> <datasource name="metadmin" configFile="metadmin.properties"/> <datasource name="mydemo" configFile="mysql.properties" schemas="mydemo, metadmin"/> </datasources>
name=”mydemo” 的数据源表示业务数据源,具体配置信息在 mysql.properties 文件里面,打开设计器时,只展示该数据库的 mydemo 和 metadmin 两个 schema 数据库表和视图。
具体数据源配置信息,参阅: http://www.metadmin.com/doc/main.html#数据源 2.6
数据查询设计器
在打开数据查收设计器之前,开发者先准备好 JavaBean ,也就是打开把查询出来的数据保存到哪个 Java 值对象。演示程序提供了 Employee ,将查询出来的数据保存到该 JavaBean 里面。
Employee.java
package org.back.demo;
import java.util.Date;
public class Employee {
private int id;
private int companyId;
private int departmentId;
private String loginName;
private String name;
private String password;
private String companyName;
private String departmentName;
private int isManager;
private Date hireDate;
// … get/set methods…
}
启动 web 服务器,在浏览器输入: http://localhost:8080/mydemo/metadmin/designer
(假定您发布的 web context 是 mydemo ,且服务器端口是 8080 )
打开左边条形框里面的“数据查询”,在树上右击,选择“新增数据查询”。如图示:
在弹出的框里面输入相关信息,如图示:
我们先新建总公司用户查看数据的 SQL ,分公司和营业部用户查询 SQL 以及怎样与业务系统集成,由于篇幅关系,下章讲述。
在数据查询树上,单击“查询所有员工”,系统自动展现数据查询设计器。
然后按照如下步骤操作:
1. 展开 mydemo schema ,展开表;
2. 双击 company, department, demouser 表,因为要查询这三张表;
3. 勾选 company 表 name 字段, department 表 name 字段,勾选 demouser 表所有字段;
4. 在映射类里面,输入 org.back.demo.Employee ;
5. 检查下面的字段映射是否有误,修改 company 表的 name 映射属性为 companyName ,修改 department 表的 name 映射属性为 departmentName 。
如图示:
到此,设计还差一个步骤,设置 where 条件。本查询 where 条件是 3 张表关联。
按照如下步骤操作:
1. 点击设计器下方的“ WHERE ”标签页
2. 右击条件组,选择“新增二元条件”;
3. 点击第一个字段节点,在右边选择“ company.id ”也就是 company 表的 id 字段;
4. 然后,设置第二个字段为“ demouser.companyId ”字段;
5. 右击条件组,选择“新增二元条件”;
6. 将第一个字段选择为“ department.id ”,第二个字段选择为“ demouser.departmentId ”。
至此,三表关联完毕。也就是完成 SQL : company.id=demouser.companyId and department.id=demouser.departmentId 。
如图示:
现在,我们可以测试了!选择设计器下方“测试”标签页,点击控制台执行小图标。 Metadmin 将显示该 sql 语句能查询的数据,行列级!
如图示:
至此,我们完全放心该 SQL 语句没有任何问题。点击“保存”图标,保存设计结果。
下章,讲解其他 SQL 还有怎样与业务系统集成。