函數式編程在前端權限管理中的應用

函數式編程在前端權限管理中的應用

解決什麼問題

本文主要是本身在實際業務開發中的一些總結,寫出來但願與你們一塊兒探討。javascript

首先介紹一下業務背景:前端

  • 咱們開發的是一套2B的企業培訓SaaS系統,企業能夠在平臺上用直播的方式對本身的員工進行培訓。
  • 這套SaaS系統能夠對接不一樣的平臺,如釘釘、微信等等(不一樣平臺會限制一些功能,如釘釘不能顯示員工手機號),也能夠進行內網部署(內網會關閉一些線上功能)。因爲部署環境和對接系統的不一樣,平臺所能使用的功能受限,對應的前端權限也不同。
  • 這裏前端的開發主要涉及帳戶系統級的培訓管理和單個房間內直播時的控制兩個部分,它們在一個SPA裏面,須要一套系統管理兩個部分的權限。
  • 培訓管理會分爲帳戶管理員,子管理員(後續可能會增長系統管理角色),直播控制人員分爲講師,嘉賓,助手等角色。這裏全部的人員均可能在一個控制頁,可是因爲角色的不一樣,UI也會不同。

綜上,在不一樣部署平臺下,不一樣級別(角色)的人員在同一個房間裏,他們所看到的界面和能使用的功能是不同的。並且一個角色受限於部署平臺和主管理員所購買的平臺服務,或者隨着主管理員關閉/開放某些功能,看到的界面也會不同。java

因此咱們須要作一套權限管理系統來綜合處理這些信息(平臺、帳戶、角色),保證各角色看到不一樣的界面。能夠看到咱們這裏說的權限已經不只限於簡單的角色權限,還包括角色之上的平臺和帳戶管理員的限制。git

由於最後的權限取決於所登陸的帳戶,因此在開發中,咱們將權限和帳戶信息放到了一塊兒,統稱爲metaConfig,即帳戶元信息,包含帳戶名字、角色等基本信息,所屬主帳號,具體角色信息,角色權限等,它將決定最終的界面顯示。github

如何解決

咱們使用React和Redux來開發,metaConfig對象能夠直接存在Redux中進行管理。編程

  • 在視圖組件中能夠經過connect函數,將metaConfig裏配置的屬性或權限數據映射成各組件所需的視圖數據,最終呈現出不一樣的界面。
  • 在路由控制器內,也能夠從Redux中拿到metaConfig來決定路由權限。
  • 一些請求方法的權限也能夠根據對應的metaConfig屬性來決定。

在咱們的系統中,metaConfig的處理是放在reducer中的,會有一個默認的defaultMetaConfig,經過reducer生成最後的metaConfig。權限管理最關鍵的就是如何生成各個角色對應的metaConfig,總結起來就是:redux

metaConfig=f(defaultMetaConfig)

分層(管道處理)

把複雜問題拆分紅簡單問題是開發中的一個重要手段。這裏咱們能夠經過分層處理的方式,將權限管理拆分紅多個層級,每層對應一個處理模塊。這樣一個大的權限處理過程就變成解決每一個層級的權限處理。微信

咱們先來看看系統權限管理受到哪些因素影響:部署方式(外網內網),對接平臺,帳戶管理員購買的服務和開啓/關閉的功能,帳戶級角色(帳戶管理員、子管理員),房間角色(管理員、講師、助手、嘉賓等)。函數式編程

咱們把每一層抽象成一個處理器,稱爲parser,簡單區分以後能夠分爲:deployParser(部署方式),platformParser(平臺),accountParser(帳戶),roleParser(角色)。函數

UNIX中有一個pipeline(管道)的概念,一個程序的輸出直接成爲下一個程序的輸入。結合到咱們的業務,能夠輸入一個默認的metaConfig,而後依次經過各個層級的parser函數,最終輸出一個metaConfig。

js中pipeline的簡單實現以下,compose函數的處理順序是反着的,能夠查看Redux中 compose的實現。
// 管道函數
const pipe = (...funcs) => {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((a, b) => (...args) => b(a(...args)))
}

把咱們抽象好的parser丟到pipe函數中,結果以下:

metaConfig = {
            ...pipe(
                deployParser,
                platformParser,
                accountParser.createAccountParser(account),
                roleParser,
            )(defaultMetaConfig)
        }

注意accountParser.createAccountParser(account)這行,咱們下面一節分析。

這樣咱們經過管道函數將權限的處理拆分紅多個層級,每一個層級會操做metaConfig內對應的屬性,是否是簡單明瞭。由於是函數式的處理,能夠直接放在reducer中計算出metaConfig而後保存到Redux中。

這裏處理權限(不只限於權限,還包括一些帳戶基礎信息)的操做分爲兩種狀況:

  • 直接賦值,如帳戶信息。
// 須要重置的數據
        newConfig.isSuperManager = false
        newConfig.isAdmin = true
        newConfig.name = manager.name
  • merge操做,將當前層級的權限與上一級傳過來的權限進行merge操做,得出權限結果傳給下一級。由於此處有一個權限限制的概念,若是platformParser(平臺)中沒有短信功能,則accountParser(帳戶)也應該沒有,在merge函數中使用&操做將該層級與上級權限merge,得出該權限結果傳給下一級。
accountParser = metaConfig => ({
    ...metaConfig,
    // 在accountParser中進行merge操做,合併從上一層傳來的metaConfig,這樣的權限處理可能有多處
    somePermission: mergeSomePermission(metaConfig.somePermission),
    ...
})

// merge函數內進行具體的&操做,
mergeSomePermission = prePermission => {
    // 當前層級沒有使用短信的權限
    prePermission.canUseMsg = prePermission & false,
    // 每一個merge函數能夠處理多個權限點,這裏只寫了一個
    ...
}

細化分層(柯里化與組合)

經過上面的分層能夠從大的方向上去解決權限問題,可是業務中的權限是動態的,不斷擴展的,如何處理業務迭代中產生的這些問題?好比上面例子中mergeSomePermission的短信權限是限定死的爲false,可是可能有的角色有這個權限,而其餘角色沒有這個權限。在account這層一個簡單的parser沒法處理不一樣帳戶間之間的差別, 並且不一樣級別帳戶須要處理的權限範圍可能也不同,同一層級還須要不一樣的處理函數,用account做爲參數,來細化各個處理器。

咱們可能須要下面的代碼,在帳戶權限處理中加入不一樣帳戶的處理器:

accountParser = (account, metaConfig) => {
    cosnt { superManager = null, normalManager = null } = account

    // 分別處理superManager和normalManager的權限
    let newConfig = superManagerParser(superManager, metaConfig)
    newConfig = normalManagerParser(normalManager, newConfig)
    
    return newConfig
}

superManagerParser = (superManager = null , metaConfig) => 
// 若是是主管理員則處理
superManager
? ({
    ...metaConfig,
    // 根據superManager信息處理
    somePermission: mergeSomePermission(superManager, metaConfig.somePermission),

    // 主管理員功能須要多處理一些權限
    someSystemPermission: mergeSomeSystemPermission(superManager, metaConfig.somePermission)
    
})
: metaConfig

normalManagerParser = (normalManager, metaConfig) =>
normalManager
? ({
    ...metaConfig,
    // 根據normalManager信息處理
    somePermission: mergeSomePermission(normalManager, metaConfig.somePermission)
})
: metaConfig

從以前的管道處理中咱們已經看到一些函數式編程的影子,咱們能夠繼續使用一些函數式的方法來加工上面的函數。管道處理中的accountParser.createAccountParser(account)就是處理這個問題的。

// 函數柯里化
createSuperManagerParser = (superManager = null) => metaConfig => 
// 若是是主管理員則處理
superManager
? ({
    ...metaConfig,
    // 主管理員功能須要多處理一些權限
    someSystemPermission: mergeSomeSystemPermission(superManager, metaCofig.somePermission)
    somePermission: mergeSomePermission(superManager, metaCofig.somePermission)
})
: metaConfig

// 函數柯里化
createNormalManagerParser = (normalManager = null) => metaConfig =>
normalManager
? ({
    ...metaConfig,
    somePermission: mergeSomePermission(normalManager, metaCofig.somePermission)
})
: metaConfig


// 合併成一個帳戶級的parser
const createAccountParser = account => {
    const { normalManger = null, super_manager = null } = account || {}

    return pipe(
        createSuperManagerParser(super_manager),
        createNormalManagerParser(normalManger),
    )
}

咱們使用柯里化將兩個parser函數處理後,可使它們都接受metaCofig做爲參數,並繼續使用一個管道組合成帳戶級別的accountParser,它的參數仍是metaConfig。這樣咱們在account這層用柯里化和組合使得parser也能夠用管道進行再次分層處理。

一樣的操做也能夠應用在角色處理器roleParser中。應用RBAC權限管理,一個角色對應一個parser,使用柯里化和pipe合成一個大的roleParser。

介紹到這裏,本文所要說的函數式編程在前端權限管理中的應用就差很少了。

爲何這麼處理

大體有如下幾點緣由:

  • 分層解耦。將各部分的代碼分隔開,每一個層級只處理本身的部分。代碼清晰易維護,團隊其餘成員也能迅速理解思路。
  • 可組合擴展。經過柯里化和管道、組合,能夠實現無限分級,即便後面權限變得更復雜,也能夠經過添加層級、組合parser來應對。
  • 整個處理過程是函數式的,只有簡單的輸入輸出,對外界系統無影響,放在Redux的reducer中真香。

總結

本文主要介紹了函數式編程(管道、柯里化、組合)在前端權限管理中的應用,經過分層解耦,多級分層將複雜的權限管理拆解成細粒度的parser函數。水平有限,其實也沒有用的很深,只是基本解決了現有的問題。業務開發久了,可能以爲沒什麼提高,可是在平常的開發中也是能夠活學活用的,將一些編程的基礎思想積極應用到開發中也許有意向不到的結果。這裏寫出來供你們參考,若是有更好的想法也歡迎一塊兒討論。

原文地址:https://github.com/woxixiulayin/blog-assets/issues/1

相關文章
相關標籤/搜索