档案管理系统RBAC权限模型设计与实操落地指南

一、数据库模型设计

构建档案软件的权限体系,首选RBAC(基于角色的访问控制)模型。为了满足档案管理中常见的“部门隔离”和“密级控制”需求,我们需要在标准RBAC基础上扩展数据权限字段。以下是基于MySQL 8.0的完整建表语句,可直接复制执行。

```sql -- 1. 用户表 CREATE TABLE `sys_user` ( `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `dept_id` bigint(20) DEFAULT NULL COMMENT '归属部门ID', `user_name` varchar(30) NOT NULL COMMENT '用户账号', `real_name` varchar(30) DEFAULT NULL COMMENT '用户姓名', `password` varchar(100) DEFAULT NULL COMMENT '密码', `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', PRIMARY KEY (`user_id`), UNIQUE KEY `uk_user_name` (`user_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 2. 角色表(扩展数据范围字段) CREATE TABLE `sys_role` ( `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_name` varchar(30) NOT NULL COMMENT '角色名称', `role_key` varchar(100) NOT NULL COMMENT '角色权限字符串', `data_scope` char(1) DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定义数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)', `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)', PRIMARY KEY (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; -- 3. 菜单权限表 CREATE TABLE `sys_menu` ( `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', `menu_name` varchar(50) NOT NULL COMMENT '菜单名称', `parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID', `order_num` int(4) DEFAULT '0' COMMENT '显示顺序', `perms` varchar(100) DEFAULT NULL COMMENT '权限标识(如 archive:file:add)', `menu_type` char(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', PRIMARY KEY (`menu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单权限表'; -- 4. 用户-角色关联表 CREATE TABLE `sys_user_role` ( `user_id` bigint(20) NOT NULL COMMENT '用户ID', `role_id` bigint(20) NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色关联表'; -- 5. 角色-菜单关联表 CREATE TABLE `sys_role_menu` ( `role_id` bigint(20) NOT NULL COMMENT '角色ID', `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色和菜单关联表'; ```

二、初始化基础权限数据

表结构建立后,需要初始化一套基础的档案管理权限数据。我们将创建一个“档案管理员”角色,拥有全部权限;一个“普通职员”角色,仅拥有上传和查看自己档案的权限。执行以下SQL脚本完成数据初始化。

```sql -- 插入部门 INSERT INTO `sys_dept` VALUES (100, 0, '总部', 0, '0'); -- 插入用户(密码假设已加密为 admin123) INSERT INTO `sys_user` VALUES (1, 100, 'admin', '系统管理员', '$2a$10$7JB720yubVSZvUI0rEqT/.VqGO0THJ8wXtz/r5J.8j/2/U/u/jj2', '0'); INSERT INTO `sys_user` VALUES (2, 100, 'zhangsan', '张三', '$2a$10$7JB720yubVSZvUI0rEqT/.VqGO0THJ8wXtz/r5J.8j/2/U/u/jj2', '0'); -- 插入角色 -- 管理员:data_scope=1 (全部数据) INSERT INTO `sys_role` VALUES (1, '档案管理员', 'admin', '1', '0'); -- 普通职员:data_scope=5 (仅本人数据) INSERT INTO `sys_role` VALUES (2, '普通职员', 'common', '5', '0'); -- 分配用户角色 INSERT INTO `sys_user_role` VALUES (1, 1); INSERT INTO `sys_user_role` VALUES (2, 2); -- 插入菜单权限 INSERT INTO `sys_menu` VALUES (1, '档案管理', 0, 1, '', 'M', '0'); INSERT INTO `sys_menu` VALUES (2, '档案列表', 1, 1, 'archive:file:list', 'C', '0'); INSERT INTO `sys_menu` VALUES (3, '档案上传', 1, 2, 'archive:file:add', 'F', '0'); INSERT INTO `sys_menu` VALUES (4, '档案下载', 1, 3, 'archive:file:download', 'F', '0'); INSERT INTO `sys_menu` VALUES (5, '档案删除', 1, 4, 'archive:file:remove', 'F', '0'); -- 为档案管理员分配所有权限 INSERT INTO `sys_role_menu` SELECT 1, menu_id FROM sys_menu; -- 为普通职员分配查看和上传权限 INSERT INTO `sys_role_menu` VALUES (2, 1); INSERT INTO `sys_role_menu` VALUES (2, 2); INSERT INTO `sys_role_menu` VALUES (2, 3); ```

三、后端功能权限校验逻辑

后端校验的核心是判断当前用户是否拥有访问特定API所需的权限标识。以下以Python (Flask + SQLAlchemy) 为例,展示完整的校验逻辑。其他语言(Java/Go/Node.js)逻辑一致,仅需调整SQL查询方式。

1. 获取用户权限列表的辅助函数

在用户登录成功后,需将用户的权限字符串缓存到Redis或Session中,避免每次请求都查库。以下是获取权限列表的查询逻辑:

```python def get_user_permissions(user_id): SQL逻辑:通过用户表 -> 关联角色表 -> 关联菜单表 -> 获取权限标识 sql = """ SELECT DISTINCT m.perms FROM sys_user u LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id LEFT JOIN sys_role r ON ur.role_id = r.role_id LEFT JOIN sys_role_menu rm ON r.role_id = rm.role_id LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id WHERE u.user_id = :user_id AND u.status = '0' AND r.status = '0' AND m.perms IS NOT NULL """ 假设使用数据库连接执行查询 results = db.session.execute(sql, {'user_id': user_id}).fetchall() 提取权限字符串并存入集合,方便O(1)查询 permission_set = {row[0] for row in results} return permission_set ```

2. 权限校验装饰器

创建一个装饰器,用于保护需要特定权限的API接口。例如,只有拥有 archive:file:remove 权限的用户才能调用删除接口。

```python from functools import wraps from flask import jsonify 模拟当前登录用户的权限缓存(实际生产中请从Redis或Session获取) current_user_permissions = set() def check_permission(required_perm): def decorator(f): @wraps(f) def decorated_function(args, kwargs): 1. 获取当前登录用户ID(从JWT Token或Session中解析) user_id = get_current_user_id_from_token() 2. 加载用户权限(此处简化,实际应加缓存) global current_user_permissions if not current_user_permissions: current_user_permissions = get_user_permissions(user_id) 3. 核心校验逻辑 if required_perm in current_user_permissions: return f(args, kwargs) else: return jsonify({'code': 403, 'msg': '无权操作,缺少权限: ' + required_perm}), 403 return decorated_function return decorator ```

3. 应用校验到接口

在路由上直接使用装饰器,实现零侵入式权限控制。

```python @app.route('/api/archive/file/', methods=['DELETE']) @check_permission('archive:file:remove') 必须拥有删除权限 def delete_archive_file(file_id): 执行删除业务逻辑 delete_from_db(file_id) return jsonify({'code': 200, 'msg': '删除成功'}) ```

四、数据权限(行级)实操落地

档案管理系统RBAC权限模型设计与实操落地指南

档案软件中,不仅要控制“能不能点按钮”,还要控制“能看到哪些数据”。例如,普通职员只能看到自己上传的档案。这需要在SQL查询层面动态拼接 WHERE 条件。

1. 构建数据权限SQL片段

我们需要一个函数,根据用户角色的 data_scope 字段,生成对应的SQL过滤条件。假设档案表为 sys_archive,其中包含 dept_idcreate_by 字段。

```python def build_data_scope_sql(user_id): 1. 查询用户角色的最大数据权限范围 如果一个用户有多个角色,取权限最大的那个(例如:同时是普通职员和管理员,则取管理员权限) sql = """ SELECT MIN(r.data_scope) as scope, u.dept_id FROM sys_user u JOIN sys_user_role ur ON u.user_id = ur.user_id JOIN sys_role r ON ur.role_id = r.role_id WHERE u.user_id = :user_id """ result = db.session.execute(sql, {'user_id': user_id}).fetchone() data_scope = result[0] user_dept_id = result[1] sql_fragment = "" 2. 根据data_scope拼接SQL if data_scope == '1': 全部数据:不加过滤条件 sql_fragment = " 1=1 " elif data_scope == '3': 本部门数据 sql_fragment = f" dept_id = {user_dept_id} " elif data_scope == '5': 仅本人数据 sql_fragment = f" create_by = {user_id} " else: 自定义或其他复杂情况,默认只看本部门(可根据业务扩展) sql_fragment = f" dept_id = {user_dept_id} " return sql_fragment 使用示例 @app.route('/api/archive/list', methods=['GET']) def get_archive_list(): user_id = get_current_user_id_from_token() 生成权限过滤SQL auth_sql = build_data_scope_sql(user_id) 将过滤条件拼接到主查询中 query_sql = f""" SELECT file_id, file_name, dept_id FROM sys_archive WHERE {auth_sql} """ data = db.session.execute(query_sql).fetchall() return jsonify({'code': 200, 'data': data}) ```

五、前端按钮级权限控制

后端拦截了API请求,但为了用户体验,前端也应直接隐藏无权操作的按钮。以下是Vue.js的实现方案,React逻辑类似。

1. 配置权限指令

在Vue项目的 main.js 或指令文件中,注册一个全局指令 v-hasPermi

```javascript import Vue from 'vue' / 权限指令 @param {Object} el 指令绑定的DOM元素 @param {Object} binding 指令绑定对象,binding.value为需要的权限数组 / Vue.directive('hasPermi', { inserted(el, binding, vnode) { const { value } = binding // 假设从Vuex或Store中获取当前用户的权限列表 const permissions = store.getters && store.getters.permissions if (value && value instanceof Array && value.length > 0) { const permissionFlags = value // 判断用户权限列表中是否包含指令要求的权限标识 const hasPermissions = permissions.some(permission => { return permissionFlags.includes(permission) }) if (!hasPermissions) { // 如果没有权限,直接移除该DOM元素 el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error('请设置操作权限标签值') } } }) ```

2. 在页面模板中使用

在列表页面的操作列中,对删除、下载按钮应用该指令。如果当前用户没有对应权限,按钮将不会渲染。

```html ```

通过以上五个步骤,从数据库设计到前端控制,完成了一套完整的、可落地的档案软件操作权限系统。开发者只需根据实际业务调整具体的权限标识字符串(如 archive:file:add)即可直接复用。

AI咨询
热线电话

028-85154420

15388110056

全国售前咨询电话

扫码咨询
安答联动微信公众号二维码

微信扫码关注安答联动

申请试用
热线电话
申请试用

安答联动档案管理系统