概念
無論為數(shù)據(jù)操作賦予怎樣的業(yè)務(wù)含義崭篡,其本質(zhì)上仍然是數(shù)據(jù)的增刪改查操作(如下圖)。
隨著業(yè)務(wù)的演進(jìn)吧秕,逐漸衍生出精細(xì)化管理數(shù)據(jù)的訴求琉闪。我遇到的業(yè)務(wù)場景是在企業(yè)級數(shù)據(jù)管理中,對不同職級的員工展示不同的數(shù)據(jù)砸彬。我的業(yè)務(wù)上的訴求是對SELECT
進(jìn)行權(quán)限控制颠毙,對INSERT
疗涉、UPDATE
、DELETE
沒有權(quán)限限制要求吟秩。
數(shù)據(jù)權(quán)限實現(xiàn)的復(fù)雜度還是較高的咱扣,在敘述實現(xiàn)之前,我們先預(yù)設(shè)期望的結(jié)果: 能夠?qū)⒎爆嵉募?xì)節(jié)都封裝在內(nèi)部邏輯中涵防,對外部提供統(tǒng)一的接口調(diào)用闹伪。
相關(guān)設(shè)計理念可以參考我之前寫的文章,《外觀模式(封裝交互壮池,簡化調(diào)用)》偏瓤。
在這個模型中,我們可選切入點有:
- 用戶層面進(jìn)行業(yè)務(wù)邏輯判斷(不推薦)
-
SQL
層面上的抽象 - 數(shù)據(jù)庫
視圖
(不推薦)
我在這里選擇了使用SQL
來完成數(shù)據(jù)權(quán)限的實現(xiàn)椰憋,通過SQL
的組裝來完成寬泛的數(shù)據(jù)權(quán)限的控制厅克。
原型實現(xiàn)
背景:某超市擁有員工 5 名,其組織架構(gòu)如下圖橙依。該小超市的信息化程度極高证舟,已經(jīng)擁有完備的移動版的CRM
系統(tǒng)。
訴求:
- 店長可以看到所有的銷售數(shù)據(jù)窗骑;
- 營業(yè)員可以看到自己的銷售數(shù)據(jù)女责,但是不能看到別人的銷售數(shù)據(jù);
- 收銀出納可以看到所有人的銷售數(shù)據(jù)创译;
- 采購庫管不能看到銷售數(shù)據(jù)抵知;
先貼上原型實現(xiàn),說明流程:
// 規(guī)則對象用于定義規(guī)則
// 這些規(guī)則包括用戶定義和管理員定義
// 規(guī)則應(yīng)可以序列化(此處省略)
public class RuleDataVO {
// 受眾群體
private String audienceGroup;
// 規(guī)則實體
private String rule;
public RuleDataVO(String audienceGroup, String rule) {
this.audienceGroup = audienceGroup;
this.rule = rule;
}
public String getAudienceGroup() {
return audienceGroup;
}
public void setAudienceGroup(String audienceGroup) {
this.audienceGroup = audienceGroup;
}
public String getRule() {
return rule;
}
public void setRule(String rule) {
this.rule = rule;
}
}
public class SaleDataVO {
private Long userId;
private Long storeId;
private String goodName;
private Integer goodPrice;
public SaleDataVO(Long userId, Long storeId, String goodName, Integer goodPrice) {
this.userId = userId;
this.storeId = storeId;
this.goodName = goodName;
this.goodPrice = goodPrice;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getGoodName() {
return goodName;
}
public void setGoodName(String goodName) {
this.goodName = goodName;
}
public Integer getGoodPrice() {
return goodPrice;
}
public void setGoodPrice(Integer goodPrice) {
this.goodPrice = goodPrice;
}
@Override
public String toString() {
return goodName + ":" + goodPrice / 100.0;
}
}
// 用于模擬數(shù)據(jù)庫操作
public class MockDataSource {
// 模擬銷售數(shù)據(jù)庫表
public static class SaleDataTable {
private static List<SaleDataVO> list = new ArrayList<>();
static {
list.add(new SaleDataVO(1L, 1L, "貝因美奶粉", 9800));
list.add(new SaleDataVO(1L, 1L, "毛巾", 2810));
list.add(new SaleDataVO(2L, 1L, "黑人牙膏", 3200));
}
public static List<SaleDataVO> retrieval(Long userId, RuleDataVO ruleDataVO) {
String ruleContent = ruleDataVO.getRule();
if (ruleContent != null) {
if (Objects.equals("自身", ruleContent)) {
// 如果檢查是營業(yè)員
if (userId == 1 || userId == 2) {
return filter(userId);
} else {
return filter(null);
}
} else if (Objects.equals("本門店內(nèi)", ruleContent)) {
return filter(1L, 2L);
} else if (Objects.equals("無", ruleContent)) {
return filter(null);
}
}
return filter(null);
}
private static List<SaleDataVO> filter(Long... userIds) {
List<SaleDataVO> saleDataVOS = new ArrayList<>();
if (userIds == null) return saleDataVOS;
for (SaleDataVO saleDataVO : list) {
for (Long userId : userIds) {
if (saleDataVO.getUserId().equals(userId)) {
saleDataVOS.add(saleDataVO);
break;
}
}
}
return saleDataVOS;
}
}
// 模擬規(guī)則庫表
public static class RuleTable {
private static List<RuleDataVO> list = new ArrayList<>();
static {
list.add(new RuleDataVO("營業(yè)員", "自身"));
list.add(new RuleDataVO("店長", "本門店內(nèi)"));
list.add(new RuleDataVO("收銀出納", "本門店內(nèi)"));
list.add(new RuleDataVO("采購庫管", "無"));
}
public static void add(RuleDataVO dataVO) {
list.add(dataVO);
}
public static RuleDataVO retrieval(String audienceGroup) {
for (RuleDataVO dataVO : list) {
if (dataVO.getAudienceGroup().equalsIgnoreCase(audienceGroup)) {
return dataVO;
}
}
return null;
}
}
}
public class Main {
public static void main(String[] args) {
System.out.println(MockDataSource.SaleDataTable.retrieval(1L, MockDataSource.RuleTable.retrieval("營業(yè)員")));
System.out.println(MockDataSource.SaleDataTable.retrieval(2L, MockDataSource.RuleTable.retrieval("營業(yè)員")));
System.out.println(MockDataSource.SaleDataTable.retrieval(3L, MockDataSource.RuleTable.retrieval("店長")));
System.out.println(MockDataSource.SaleDataTable.retrieval(4L, MockDataSource.RuleTable.retrieval("收銀出納")));
System.out.println(MockDataSource.SaleDataTable.retrieval(5L, MockDataSource.RuleTable.retrieval("采購庫管")));
}
}
// 調(diào)用結(jié)果:
[貝因美奶粉:98.0, 毛巾:28.1]
[黑人牙膏:32.0]
[貝因美奶粉:98.0, 毛巾:28.1, 黑人牙膏:32.0]
[貝因美奶粉:98.0, 毛巾:28.1, 黑人牙膏:32.0]
[]
在這個原型上省略了不必要的復(fù)雜性(如DB操作软族,業(yè)務(wù)操作)刷喜,僅關(guān)注規(guī)則的定義與解析過程。
原型上簡單定義了自身
本門店內(nèi)
無
的語法規(guī)則立砸,結(jié)合上下文判拼接處正確的SQL
語句掖疮。
我理解的權(quán)限控制核心就在這里:定義語法規(guī)則解析并應(yīng)用到SQL規(guī)范中。
- 后端上定義語法規(guī)則仰禽,預(yù)初始化入庫氮墨,即完成數(shù)據(jù)權(quán)限的控制。
- 前端上定義語法規(guī)則(需考慮SQL注入問題)吐葵,即時操作入庫规揪,即完成數(shù)據(jù)權(quán)限的控制;
上述是個非常簡單的原型温峭,說明了解題思路但是實際的可操作性不高猛铅。因此我們需要接著對它進(jìn)行抽象。
抽象
指令:查詢當(dāng)天的銷售數(shù)據(jù)凤藏;
環(huán)境:基于上下文參數(shù)推斷出所屬的資源奸忽,如:所屬的公司堕伪、部門等;
權(quán)限:僅自身相關(guān)的數(shù)據(jù)栗菜、本部門內(nèi)欠雌、本部門及下屬部門、所有疙筹、無富俄;
對象 | 環(huán)境 | 權(quán)限 | SQL |
---|---|---|---|
營業(yè)員 | 好又多超市101分店 | 僅自身相關(guān)的數(shù)據(jù) | uid=$uid |
收銀出納 | 好又多超市101分店 | 本部門及下屬部門 | uid in $uids |
采購庫管 | 好又多超市101分店 | 無 | uid = null |
店長 | 好又多超市101分店 | 本部門及下屬部門 | uid in $uids |
實現(xiàn)步驟拆分
- 組織樹
- 人
- 人與組織樹
- 角色與功能權(quán)限
- 角色與數(shù)據(jù)權(quán)限
- 角色與人
- 應(yīng)用權(quán)限規(guī)則
組織樹
通常業(yè)務(wù)限定組織樹的深度都不會過高,一般在5層以內(nèi)而咆。實現(xiàn)組織樹的方式有多種:
- 遞歸方式
- 前序遍歷樹霍比,參考無限級分類實現(xiàn)思路 (組織樹的分級管理)
定義組織樹目的是承載人的容器,通過將人分配到對應(yīng)組織中完成上下文的聯(lián)系暴备。
通過將人分配到不同的部門中悠瞬,即完成了人與組織的關(guān)系。這樣我們就能通過上下文推導(dǎo)出人所具備的資源涯捻。
通過對組織的資源限制即可完成預(yù)初始化狀態(tài)的SQL配置浅妆,如:
// 大老板
SELECT * FROM table;
// 李妲
SELECT * FROM table WHERE department in ('行政部','銷售部');
// 李達(dá)
SELECT * FROM table WHERE department = '行政部';
// 肖雨
SELECT * FROM table WHERE store = '六盤水...;
解題步驟
* 添加組織(建設(shè)組織樹)
* 添加人
* 添加功能權(quán)限
* 分配角色到功能權(quán)限
* 添加數(shù)據(jù)權(quán)限
* 分配角色到數(shù)據(jù)權(quán)限
* 分配人到組織
* 分配人到角色
目前整個數(shù)據(jù)權(quán)限管理流程已經(jīng)做通了,具體的代碼涉及業(yè)務(wù)細(xì)節(jié)就不貼出來了汰瘫。
如果有各種疑問狂打,歡迎提問。