函数式编程在前端权限管理中的应用

函数式编程在前端权限管理中的应用

解决什么问题

本文主要是本身在实际业务开发中的一些总结,写出来但愿与你们一块儿探讨。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

相关文章
相关标签/搜索