第四章
細粒度數據查詢權限
(上)
通過基于角色訪問控制,我們可以控制哪些人具有某種權限。比如總公司員工柴其貴、分公司員工李朵朵和營業部員工賈志宏,三個人都具有訪問“查詢員工”頁面權限。
但,由于他們三人所在公司級別不同(總公司、分公司和營業部),進入查詢員工頁面,系統展示出來的員工數據應該是不同的。
此類數據查詢權限,在業務系統里面隨處可見。畢竟 N 多操作,都起源于查詢。連增加數據操作都有,比如增加表單的某個下拉框內容,可能來自數據庫查詢。
數據查詢權限,包括 2 個緯度:行級和列級。
現有方案
常見拼湊 SQL
常見做法是,采用 if else 做判斷,決定執行那條程序分支,也就是 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; //查詢本營業部員工
}
變通方法
也有不少開發者對上述方法做了改進,我了解到如下改進方法:
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 :
<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
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 還有怎樣與業務系統集成。