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条件,最终查询出不同的数据。其中,根据上下级设置数据权限,具体是通过创建账号关联的,用户创建的账号,就是该用户的下级账号。

相关推荐
m0_7482309435 分钟前
Redis 通用命令
前端·redis·bootstrap
Hello.Reader40 分钟前
深入理解 Rust 的 `Rc<T>`:实现多所有权的智能指针
开发语言·后端·rust
yoona102043 分钟前
Rust编程语言入门教程(八)所有权 Stack vs Heap
开发语言·后端·rust·区块链·学习方法
YaHuiLiang1 小时前
一切的根本都是前端“娱乐圈化”
前端·javascript·代码规范
考虑考虑2 小时前
MyCat2使用
java·后端·java ee
后端码匠2 小时前
Spring Boot3+Vue2极速整合:10分钟搭建DeepSeek AI对话系统
人工智能·spring boot·后端
ObjectX前端实验室2 小时前
个人网站开发记录-引流公众号 & 谷歌分析 & 谷歌广告 & GTM
前端·程序员·开源
CL_IN2 小时前
企业数据集成:实现高效调拨出库自动化
java·前端·自动化
可乐张2 小时前
AutoGen 技术博客系列 (九):从 v0.2 到 v0.4 的迁移指南
后端·llm
可乐张2 小时前
AutoGen 技术博客系列 (八):深入剖析 Swarm—— 智能体协作的新范式
后端·llm