HotGo--权限管理,RBAC,部门,上下级权限

权限管理,大多是后端处理的功能,涉及到用户,角色,菜单和部门等。

前端权限

根据 get:/role/dynamic 这个接口获取到当前用户角色所对应的菜单权限,动态生成路由信息,然后通过vue-router的 addRoute 动态配置路由

ts 复制代码
// src/router/generator-routers.ts
export function createRouterGuards(router: Router) {
    router.beforeEach(async (to, from, next) => {
        // ...
        await userStore.GetConfig();
        const routes = await asyncRouteStore.generateRoutes(userInfo);
        // 动态添加可访问路由表
        routes.forEach((item) => {
          router.addRoute(item as unknown as RouteRecordRaw);
        });  
    }
}

作者在这里,将请求配置权限菜单的逻辑,放在守卫里,感觉不合理,因为每次路由变化,都会进入这个逻辑,虽然加了判断,已经添加了路由的话,不走下面的加载逻辑,但仍觉的不妥。

ts 复制代码
if (asyncRouteStore.getIsDynamicAddedRoute) {
  next();
  return;
}

以上是跟菜单有关的权限,除此之外,还有对数据也有限制

角色列表中,非超管用户,只能看到自己的角色以及下级的角色,后端在role.go中处理

go 复制代码
// server/internal/logic/admin/role.go
// List 获取列表
func (s *sAdminRole) List(ctx context.Context, in *adminin.RoleListInp) (res *adminin.RoleListModel, totalCount int, err error) {
    // ...
    // 非超管只获取下级角色
    if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
       pid = contexts.GetRoleId(ctx)
       mod = mod.WhereLike(dao.AdminRole.Columns().Tree, "%"+tree.GetIdLabel(pid)+"%")
    }
    // ...
}

这里用到了判断是否是超管,超管的信息是在server/internal/global/init.go中的Init函数中加载的

go 复制代码
func Init(ctx context.Context) {
    // ...
    // 加载超管数据
    service.AdminMember().LoadSuperAdmin(ctx)
    // ...
}

这个Init函数会在main函数中被调用,注意这个Init函数,不是go包中默认运行时加载的init函数。

角色权限

该项目使用了RBAC权限管理模型,外加 casbin 权限框架辅助。从数据库中可以看到有关权限的几张表

  1. hg_admin_member
  2. hg_admin_role
  3. hg_admin_menu
  4. hg_admin_role_menu
  5. hg_admin_member_role
  6. hg_admin_role_casbin

其中hg_admin_role_casbin里存放的时候,角色对应的具体权限,这些数据会被应用启动时加载,缓存在内存中。另外,在权限被修改时,系统会重新修改 casbin 权限内容。

go 复制代码
cmd.Main.Run(ctx)

这里运行,Main 的Func函数,初始化一些内容,包括读取权限信息到casbin。

go 复制代码
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
    //...
    // 初始化casbin权限
    casbin.InitEnforcer(ctx)
    //...
}

role权限修改时,作者将casbin权限清空,重新加载

go 复制代码
// UpdatePermissions 更改角色菜单权限
func (s *sAdminRole) UpdatePermissions(ctx context.Context, in *adminin.UpdatePermissionsInp) (err error) {
    // ...
    return casbin.Refresh(ctx)
}

作者在 server/internal/library/casbin/enforcer.go 的 clear 中,是将全部权限都清空掉,重新加载,这里浪费了一些资源,个人觉得,可以通过 group 清理和重新加载对应role的权限,这样性能会好一些。

上面讲了一大堆权限,具体应用是在这个中间件文件中, 这个中间件会在设置路由的时候配置。

go 复制代码
// server/internal/logic/middleware/admin_auth.go
// 验证路由访问权限
if !service.AdminRole().Verify(ctx, path, r.Method) {
    g.Log().Debugf(ctx, "AdminAuth fail path:%+v, GetRoleKey:%+v, r.Method:%+v", path, contexts.GetRoleKey(ctx), r.Method)
    response.JsonExit(r, gcode.CodeSecurityReason.Code(), "你没有访问权限!")
    return
}

设置权限中间件

go 复制代码
// server/internal/router/admin.go
group.Group(simple.RouterPrefix(ctx, consts.AppAdmin), func(group *ghttp.RouterGroup) {
    group.Bind(
       common.Site, // 基础
    )
    group.Middleware(service.Middleware().AdminAuth)
    group.Bind(
        common.Console,   // 控制台
        // ...
    )
}

数据权限

在前端,权限管理-角色管理中,可以对每个角色这是数据权限,分为按全部权限、按部门权限,按上下级权限等方式划分,设置是比较简单的,只是将对应的id关联到role上即可,具体获取数据的时候,略微复杂一些。在需要进行权限划分的数据获取时,使用 FilterAuthWithField 函数进行过滤数据,过滤的字段不是固定的,可以是created_by, 也可以是member_id。

go 复制代码
// FilterAuthWithField 过滤数据权限,设置指定字段
func FilterAuthWithField(filterField string) func(m *gdb.Model) *gdb.Model {
    return func(m *gdb.Model) *gdb.Model {
       // ...
       switch role.DataScope {
       case consts.RoleDataAll: // 全部权限
          // ...
       case consts.RoleDataNowDept: // 当前部门
          m = m.WhereIn(filterField, getDeptIds(co.User.DeptId))
       case consts.RoleDataDeptAndSub: // 当前部门及以下部门ds
          m = m.WhereIn(filterField, getDeptIds(GetDeptAndSub(ctx, co.User.DeptId)))
       case consts.RoleDataDeptCustom: // 自定义部门
          m = m.WhereIn(filterField, getDeptIds(role.CustomDept.Var().Ints()))
       case consts.RoleDataSelf: // 仅自己
          m = m.Where(filterField, co.User.Id)
       case consts.RoleDataSelfAndSub: // 自己和直属下级
          m = m.WhereIn(filterField, GetSelfAndSub(ctx, co.User.Id))
       case consts.RoleDataSelfAndAllSub: // 自己和全部下级
          m = m.WhereIn(filterField, GetSelfAndAllSub(ctx, co.User.Id))
       default:
          g.Log().Panic(ctx, "dataScope is not registered")
       }
       return m
    }
}

这是一个数据库中间件,gorm中也有相应的功能,会根据不同的数据权限,生成不同的sql where条件,最终查询出不同的数据。其中,根据上下级设置数据权限,具体是通过创建账号关联的,用户创建的账号,就是该用户的下级账号。

相关推荐
2501_915918412 小时前
Web 前端可视化开发工具对比 低代码平台、可视化搭建工具、前端可视化编辑器与在线可视化开发环境的实战分析
前端·低代码·ios·小程序·uni-app·编辑器·iphone
程序员爱钓鱼2 小时前
Go语言实战案例 — 工具开发篇:实现一个图片批量压缩工具
后端·google·go
程序员的世界你不懂3 小时前
【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
java·前端·数据库
索迪迈科技3 小时前
网络请求库——Axios库深度解析
前端·网络·vue.js·北京百思可瑞教育·百思可瑞教育
gnip3 小时前
JavaScript二叉树相关概念
前端
attitude.x4 小时前
PyTorch 动态图的灵活性与实用技巧
前端·人工智能·深度学习
β添砖java4 小时前
CSS3核心技术
前端·css·css3
ChinaRainbowSea4 小时前
7. LangChain4j + 记忆缓存详细说明
java·数据库·redis·后端·缓存·langchain·ai编程
舒一笑4 小时前
同步框架与底层消费机制解决方案梳理
后端·程序员
minh_coo4 小时前
Spring框架事件驱动架构核心注解之@EventListener
java·后端·spring·架构·intellij-idea