權(quán)限之?dāng)?shù)據(jù)權(quán)限

概念

無論為數(shù)據(jù)操作賦予怎樣的業(yè)務(wù)含義崭篡,其本質(zhì)上仍然是數(shù)據(jù)的增刪改查操作(如下圖)。

image.png

隨著業(yè)務(wù)的演進(jìn)吧秕,逐漸衍生出精細(xì)化管理數(shù)據(jù)的訴求琉闪。我遇到的業(yè)務(wù)場景是在企業(yè)級數(shù)據(jù)管理中,對不同職級的員工展示不同的數(shù)據(jù)砸彬。我的業(yè)務(wù)上的訴求是對SELECT進(jìn)行權(quán)限控制颠毙,對INSERT疗涉、UPDATEDELETE沒有權(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)。

image.png

訴求:

  • 店長可以看到所有的銷售數(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ī)則

組織樹

image.png

通常業(yè)務(wù)限定組織樹的深度都不會過高,一般在5層以內(nèi)而咆。實現(xiàn)組織樹的方式有多種:

定義組織樹目的是承載人的容器,通過將人分配到對應(yīng)組織中完成上下文的聯(lián)系暴备。

image.png

通過將人分配到不同的部門中悠瞬,即完成了人與組織的關(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é)就不貼出來了汰瘫。
如果有各種疑問狂打,歡迎提問。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末混弥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子对省,更是在濱河造成了極大的恐慌蝗拿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒿涎,死亡現(xiàn)場離奇詭異哀托,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)劳秋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門仓手,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玻淑,你說我怎么就攤上這事嗽冒。” “怎么了补履?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵添坊,是天一觀的道長。 經(jīng)常有香客問我箫锤,道長贬蛙,這世上最難降的妖魔是什么雨女? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮阳准,結(jié)果婚禮上氛堕,老公的妹妹穿的比我還像新娘。我一直安慰自己野蝇,他們只是感情好岔擂,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浪耘,像睡著了一般乱灵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上七冲,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天痛倚,我揣著相機(jī)與錄音,去河邊找鬼澜躺。 笑死蝉稳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掘鄙。 我是一名探鬼主播耘戚,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼操漠!你這毒婦竟也來了收津?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤浊伙,失蹤者是張志新(化名)和其女友劉穎撞秋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚣鄙,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡吻贿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哑子。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舅列。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卧蜓,靈堂內(nèi)的尸體忽然破棺而出帐要,到底是詐尸還是另有隱情,我是刑警寧澤烦却,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布宠叼,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏冒冬。R本人自食惡果不足惜伸蚯,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望简烤。 院中可真熱鬧剂邮,春花似錦、人聲如沸横侦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枉侧。三九已至引瀑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榨馁,已是汗流浹背憨栽。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留翼虫,地道東北人屑柔。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像珍剑,于是被迫代替她去往敵國和親掸宛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容