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

相关推荐
2401_8576226622 分钟前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
正小安26 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
2402_8575893626 分钟前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没2 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式