Golang Web 权限设计(简)

https://images.unsplash.com/photo-1627483262268-9c2b5b2834b5?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb

用户与角色

角色设计的意意义可以帮助前端根据权限的不同展示不同的菜单。对于我这个后台就是不同的角色要对应不同的侧边菜单。这里我选择了 antd pro 重组件,他里面有一个 menu 控制侧边栏的展示。我要做的就是设计一个用户模型,一个角色模型,还有一个系统菜单模型,然后通过关联关系将它们联系起来。

这里的难点也许是,你必须要理解前端的动态菜单的生成需要哪些属性。一个菜单包含哪些属性必须由前端来定夺。可以看到antd pro 的动态菜单支持的属性,然后仿照它来设计一个菜单模型。

type SysMenu struct {
	Shared
	Path       string    `json:"path" gorm:"comment:菜单路径;unique;"` //菜单路径必须是唯一的不存在同名
	MenuName   string    `json:"menuName" gorm:"comment:菜单名称"`
	Icon       string    `json:"icon" gorm:"comment:菜单图标"`
	HideInMenu bool      `json:"hideInMenu" gorm:"comment:是否隐藏;default:false"`
	Component  string    `json:"component" gorm:"comment:菜单路径"`
	ParentID   uint      `json:"parentID" gorm:"comment:父菜单标题"`
	Routes     []SysMenu `json:"routes" gorm:"-"` //很关键可以忽略外键
}

然后设计多对多的关系来设计权限和菜单的关联关系

type Authority struct {
	Shared
	AuthorityName string    `json:"authorityName" gorm:"comment:角色名称;unique"`
	SysMenus      []SysMenu `json:"sysMenus" gorm:"comment:系统菜单;many2many:authority_sys_menus;"`
}

为了简化逻辑,我这边一个用户就设定一个角色,所以用户和角色是一个一对一关系

type SysUser struct {
	Shared
	SysUserName     string    `json:"sysUserName" gorm:"unique;comment:用户名" binding:"required"`
	SysUserNickName string    `json:"sysUserNickName" gorm:"comment:昵称" binding:"required"`
	SysUserPassword string    `json:"sysUserPassword,omitempty" gorm:"comment:密码" binding:"required"`
	SysUserEmail    string    `json:"sysUserEmail" gorm:"comment:邮箱" binding:"required"`
	AuthorityID     uint      `json:"authorityId" gorm:"comment:外键;"`
	Authority       Authority `json:"authority" gorm:"comment:角色;"`
	// <https://gorm.io/docs/delete.html>
	// DeletedAt gorm.DeletedAt `json:"-" gorm:"index;column:deleted_at;null"`
}

这样就可以通过gorm来关联这些对象,然后返回给前端某个用户所对应的菜单。

然后再前端通过 antd pro layout 的动态菜单能力,在登陆之后直接拿到用户对应角色的菜单,然后渲染 loopMenuItem(props.currentSysUser.authority.sysMenus)

/**
   * @description: 迭代循环菜单项用于动态生成菜单
   * @param {MenuDataItem} menus
   * @return {*}
   * @author: Kane
   */
  const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] =>
    menus
      ? menus.map(({ icon, routes, menuName, ...item }) => ({
          ...item,
          name: menuName,
          icon: icon && IconMap[icon as string],
          routes: routes && loopMenuItem(routes),
        }))
      : [];

<ProLayout
      logo={logo}
      {...props}
      {...proSettings}
      collapsed={collapsed}
      onCollapse={(c) => {
        setCollapsed(c);
      }}
      menuItemRender={(menuItemProps: any, defaultDom: any) => {
        if (
          menuItemProps.isUrl ||
          !menuItemProps.path ||
          location.pathname === menuItemProps.path
        ) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      menu={{ request: async () => loopMenuItem(props.currentSysUser.authority.sysMenus) }}
    >
</ProLayout>

后续

当然这里没在 API 层做一个拦截。这个后续结合 Gin 中间件可以很好实现。