个人笔记而已-不要花时间看哟~
提问01:gin casbin RBAC 设计权限系统表及关联关系,并给出备注注释
Gin + Casbin RBAC 权限系统设计
下面是一个基于Gin框架和Casbin的RBAC(基于角色的访问控制)权限系统的数据库表设计和关联关系说明。
数据库表设计
1. 用户表 (users)
CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码(加密存储)',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
2. 角色表 (roles)
CREATE TABLE `roles` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(50) NOT NULL COMMENT '角色名称',
`description` varchar(255) DEFAULT NULL COMMENT '角色描述',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_name` (`name`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
3. 用户角色关联表 (user_roles)
CREATE TABLE `user_roles` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_role` (`user_id`,`role_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
4. 资源表 (resources)
CREATE TABLE `resources` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '资源ID',
`name` varchar(50) NOT NULL COMMENT '资源名称',
`code` varchar(100) NOT NULL COMMENT '资源代码(唯一标识)',
`type` tinyint NOT NULL COMMENT '资源类型(1:菜单,2:按钮,3:API)',
`parent_id` bigint DEFAULT NULL COMMENT '父资源ID',
`path` varchar(255) DEFAULT NULL COMMENT '资源路径(前端路由或API路径)',
`method` varchar(10) DEFAULT NULL COMMENT 'HTTP方法(GET,POST等,API类型时使用)',
`icon` varchar(50) DEFAULT NULL COMMENT '图标(菜单类型时使用)',
`sort` int DEFAULT '0' COMMENT '排序',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_type` (`type`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资源表(菜单/按钮/API)';
5. 角色资源关联表 (role_resources)
CREATE TABLE `role_resources` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`resource_id` bigint NOT NULL COMMENT '资源ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_role_resource` (`role_id`,`resource_id`),
KEY `idx_resource_id` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色资源关联表';
6. Casbin规则表 (casbin_rule)
CREATE TABLE `casbin_rule` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '规则ID',
`ptype` varchar(10) NOT NULL COMMENT '策略类型(p:策略,g:角色继承)',
`v0` varchar(100) NOT NULL COMMENT '角色/用户(subject)',
`v1` varchar(100) NOT NULL COMMENT '资源/路径(object)',
`v2` varchar(100) DEFAULT NULL COMMENT '操作/方法(action)',
`v3` varchar(100) DEFAULT NULL COMMENT '预留字段1',
`v4` varchar(100) DEFAULT NULL COMMENT '预留字段2',
`v5` varchar(100) DEFAULT NULL COMMENT '预留字段3',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_unique` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`),
KEY `idx_v0` (`v0`),
KEY `idx_v1` (`v1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Casbin规则表';
表关系说明
-
用户与角色:多对多关系,通过
user_roles表关联- 一个用户可以拥有多个角色
- 一个角色可以分配给多个用户
-
角色与资源:多对多关系,通过
role_resources表关联- 一个角色可以访问多个资源
- 一个资源可以被多个角色访问
-
资源自身:树形结构,通过
parent_id自关联- 支持多级菜单/资源结构
-
Casbin规则表:存储实际的访问控制规则
ptype字段区分策略类型(p表示策略规则,g表示角色继承关系)- 通常格式:
- 策略规则:
p, role, resource, action(角色可以访问资源的某个操作) - 角色继承:
g, user, role(用户属于某个角色)
- 策略规则:
RBAC模型设计
Casbin使用的RBAC模型通常如下(policy.csv):
p, admin, /api/v1/users, GET
p, admin, /api/v1/users, POST
p, editor, /api/v1/articles, GET
p, editor, /api/v1/articles, POST
g, alice, admin
g, bob, editor
Gin + Casbin集成建议
- 初始化Casbin:
import (
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/gorm"
)
func SetupCasbin(db *gorm.DB) *casbin.Enforcer {
adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
panic(err)
}
enforcer, err := casbin.NewEnforcer("path/to/rbac_model.conf", adapter)
if err != nil {
panic(err)
}
// 加载策略
err = enforcer.LoadPolicy()
if err != nil {
panic(err)
}
return enforcer
}
- 中间件检查权限:
func CasbinMiddleware(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
// 获取当前用户角色
user, exists := c.Get("user")
if !exists {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
// 获取请求路径和方法
path := c.Request.URL.Path
method := c.Request.Method
// 检查权限
ok, err := enforcer.Enforce(user.(string), path, method)
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "internal server error"})
return
}
if !ok {
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
return
}
c.Next()
}
}
- RBAC模型文件(rbac_model.conf):
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
数据初始化建议
- 初始化超级管理员角色
- 初始化基本资源(如首页、登录页等)
- 为超级管理员分配所有资源权限
这种设计提供了灵活的RBAC权限控制,可以满足大多数Web应用的权限管理需求。
提问02: 需要菜单表吗?如何跟上面的设计完美融合
菜单表设计与RBAC系统融合
在您的设计中,实际上已经包含了菜单功能 - 资源表(resources)中的type字段已经区分了资源类型(1:菜单,2:按钮,3:API)。这种设计是合理的,但我可以提供更明确的菜单表设计方案,并说明如何与现有RBAC系统完美融合。
方案一:使用现有资源表作为菜单表(推荐)
这是最简洁的方案,利用现有的resources表:
ALTER TABLE `resources`
MODIFY COLUMN `type` tinyint NOT NULL COMMENT '资源类型(1:菜单,2:按钮,3:API)',
ADD COLUMN `component` varchar(100) DEFAULT NULL COMMENT '前端组件路径(菜单类型时使用)',
ADD COLUMN `visible` tinyint DEFAULT 1 COMMENT '是否显示(0:隐藏,1:显示)',
ADD COLUMN `is_frame` tinyint DEFAULT 0 COMMENT '是否外链(0:否,1:是)';
优点:
- 保持表结构简单
- 菜单、按钮、API统一管理
- 权限控制一致
查询用户菜单示例:
SELECT r.* FROM resources r
JOIN role_resources rr ON r.id = rr.resource_id
JOIN user_roles ur ON rr.role_id = ur.role_id
WHERE ur.user_id = ? AND r.type = 1 AND r.status = 1
ORDER BY r.sort;
方案二:单独菜单表设计
如果您希望更明确地区分菜单和权限,可以单独设计菜单表:
1. 菜单表 (menus)
CREATE TABLE `menus` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`name` varchar(50) NOT NULL COMMENT '菜单名称',
`code` varchar(100) NOT NULL COMMENT '菜单编码(唯一)',
`parent_id` bigint DEFAULT NULL COMMENT '父菜单ID',
`path` varchar(255) DEFAULT NULL COMMENT '路由路径',
`component` varchar(100) DEFAULT NULL COMMENT '组件路径',
`icon` varchar(50) DEFAULT NULL COMMENT '图标',
`sort` int DEFAULT '0' COMMENT '排序',
`visible` tinyint DEFAULT 1 COMMENT '是否显示(0:隐藏,1:显示)',
`is_frame` tinyint DEFAULT 0 COMMENT '是否外链(0:否,1:是)',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
2. 菜单资源关联表 (menu_resources)
CREATE TABLE `menu_resources` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
`resource_id` bigint NOT NULL COMMENT '资源ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_menu_resource` (`menu_id`,`resource_id`),
KEY `idx_resource_id` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单资源关联表';
融合方式:
- 菜单表只存储展示相关的信息
- 资源表存储权限控制相关的信息
- 通过关联表建立菜单与资源的联系
优点:
- 菜单与权限解耦
- 更清晰的职责划分
- 一个菜单可以关联多个资源(权限)
缺点:
- 表结构更复杂
- 需要维护更多关联关系
完美融合的关键点
无论选择哪种方案,要实现完美融合需要注意:
-
数据一致性:
- 当菜单/资源状态变为禁用时,要同步更新相关缓存
- 删除菜单时要处理关联的资源权限
-
缓存策略:
// 获取用户菜单(带缓存) func GetUserMenus(userID int64) ([]Menu, error) { cacheKey := fmt.Sprintf("user_menus:%d", userID) if cached, err := cache.Get(cacheKey); err == nil { return cached.([]Menu), nil } // 查询数据库 menus := queryMenusFromDB(userID) // 设置缓存 cache.Set(cacheKey, menus, 30*time.Minute) return menus, nil } -
前端对接:
- 返回的菜单结构应该包含完整的路由信息和权限标识
- 示例返回结构:
{ "path": "/system", "component": "Layout", "children": [ { "path": "user", "component": "system/user/index", "meta": { "title": "用户管理", "permissions": ["system:user:query"] } } ] }
-
动态路由注册:
// 前端动态路由示例(Vue) const permission = { state: { routes: [], addRoutes: [] }, actions: { generateRoutes({ commit }, roles) { return new Promise(resolve => { // 通过API获取用户菜单 getMenus().then(res => { const accessedRoutes = filterAsyncRoutes(res.data) commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) }) } } }
推荐方案
对于大多数项目,我推荐方案一(使用现有资源表作为菜单表),因为:
- 菜单本质上也是一种资源,与权限控制紧密相关
- 减少了表关联和复杂度
- 更易于维护数据一致性
- 符合KISS(Keep It Simple, Stupid)原则
只有在菜单系统特别复杂,且与权限系统有明显不同生命周期时,才考虑方案二。