Skip to content

角色管理模块开发文档

迭代编号: sprint-2
模块: 角色管理模块
状态: ✅ 已完成


开发任务

  • [√] 角色列表页面
  • [√] 角色管理API
  • [√] 用户角色分配
  • [√] 数据权限配置
  • [√] 角色权限分配
  • [√] 角色权限复制

技术方案

架构设计

┌─────────────────────────────────────────────────────────────┐
│                      角色管理模块                            │
├─────────────────────────────────────────────────────────────┤
│  前端层                                                       │
│  ├── RoleList.vue              角色列表页面                  │
│  ├── RoleForm.vue              角色表单页面                  │
│  ├── RolePermission.vue        角色权限分配                  │
│  ├── RoleDataPermission.vue    数据权限配置                  │
│  └── RoleUser.vue              用户角色分配                  │
├─────────────────────────────────────────────────────────────┤
│  API层                                                        │
│  ├── RoleController            角色管理接口                  │
│  ├── RolePermissionService     角色权限服务                  │
│  ├── RoleDataPermissionService 数据权限服务                  │
│  └── RoleUserService           用户角色服务                  │
├─────────────────────────────────────────────────────────────┤
│  数据层                                                       │
│  ├── sys_role                  角色表                        │
│  ├── sys_role_permission       角色权限关联表                │
│  ├── sys_role_data_permission  角色数据权限表                │
│  └── sys_user_role             用户角色关联表                │
└─────────────────────────────────────────────────────────────┘

数据库设计

角色表 (sys_role)

字段名类型长度必填说明
idbigint20主键ID
role_codevarchar50角色编码
role_namevarchar100角色名称
role_typetinyint1类型:1-系统角色 2-业务角色
data_scopetinyint1数据范围:1-全部 2-本部门 3-本部门及下级 4-仅本人 5-自定义
statustinyint1状态:0-禁用 1-启用
descriptionvarchar500描述
create_timedatetime-创建时间
update_timedatetime-更新时间
create_bybigint20创建人
update_bybigint20更新人
deletedtinyint1删除标记

角色数据权限表 (sys_role_data_permission)

字段名类型长度必填说明
idbigint20主键ID
role_idbigint20角色ID
dept_idbigint20部门ID
create_timedatetime-创建时间

用户角色关联表 (sys_user_role)

字段名类型长度必填说明
idbigint20主键ID
user_idbigint20用户ID
role_idbigint20角色ID
create_timedatetime-创建时间

数据权限范围

数据范围枚举:
├── ALL              # 全部数据
├── DEPT_ONLY        # 本部门数据
├── DEPT_AND_CHILD   # 本部门及下级数据
├── SELF_ONLY        # 仅本人数据
└── CUSTOM           # 自定义规则

数据权限规则引擎

java
@Component
public class DataPermissionRuleEngine {
    
    @Autowired
    private SysRoleDataPermissionMapper dataPermissionMapper;
    
    /**
     * 构建数据权限SQL条件
     */
    public String buildDataScopeCondition(Long userId, String tableAlias) {
        // 获取用户角色
        List<SysRole> roles = getUserRoles(userId);
        
        Set<String> conditions = new HashSet<>();
        
        for (SysRole role : roles) {
            String condition = buildRoleCondition(role, tableAlias);
            if (condition != null) {
                conditions.add(condition);
            }
        }
        
        if (conditions.isEmpty()) {
            return "1=0"; // 无权限
        }
        
        return String.join(" OR ", conditions);
    }
    
    private String buildRoleCondition(SysRole role, String alias) {
        switch (role.getDataScope()) {
            case ALL:
                return null; // 全部数据,无需限制
            case DEPT_ONLY:
                Long deptId = getUserDeptId();
                return alias + ".dept_id = " + deptId;
            case DEPT_AND_CHILD:
                List<Long> childDeptIds = getChildDeptIds(getUserDeptId());
                return alias + ".dept_id IN (" + StringUtils.join(childDeptIds, ",") + ")";
            case SELF_ONLY:
                return alias + ".create_by = " + SecurityUtils.getCurrentUserId();
            case CUSTOM:
                return buildCustomCondition(role.getId(), alias);
            default:
                return "1=0";
        }
    }
}

接口定义

角色管理接口

1. 获取角色列表

yaml
接口: GET /api/v1/sys/role/list
描述: 分页查询角色列表
参数:
  - pageNum: 页码 (默认1)
  - pageSize: 每页条数 (默认10)
  - roleName: 角色名称 (模糊查询)
  - roleType: 角色类型
  - status: 状态
响应:
  code: 200
  data:
    list: 角色列表
    total: 总条数

2. 创建角色

yaml
接口: POST /api/v1/sys/role
描述: 创建角色
请求体:
  roleCode: 角色编码
  roleName: 角色名称
  roleType: 角色类型
  dataScope: 数据范围
  description: 描述
响应:
  code: 200
  data: 创建的角色信息

3. 更新角色

yaml
接口: PUT /api/v1/sys/role/{id}
描述: 更新角色
请求体: 同创建
响应:
  code: 200
  data: 更新后的角色信息

4. 删除角色

yaml
接口: DELETE /api/v1/sys/role/{id}
描述: 删除角色
参数:
  - id: 角色ID
响应:
  code: 200
  message: 删除成功

5. 分配角色权限

yaml
接口: POST /api/v1/sys/role/{id}/permissions
描述: 为角色分配权限
请求体:
  permissionIds: 权限ID列表
响应:
  code: 200
  message: 分配成功

6. 获取角色权限

yaml
接口: GET /api/v1/sys/role/{id}/permissions
描述: 获取角色的权限列表
响应:
  code: 200
  data:
    permissionIds: 权限ID列表
    permissionTree: 权限树

7. 复制角色权限

yaml
接口: POST /api/v1/sys/role/{id}/copy-permissions
描述: 复制其他角色的权限
请求体:
  sourceRoleId: 源角色ID
响应:
  code: 200
  message: 复制成功

8. 配置数据权限

yaml
接口: POST /api/v1/sys/role/{id}/data-permission
描述: 配置角色数据权限
请求体:
  dataScope: 数据范围
  deptIds: 部门ID列表(自定义范围时使用)
响应:
  code: 200
  message: 配置成功

9. 分配用户角色

yaml
接口: POST /api/v1/sys/role/{id}/users
描述: 为角色分配用户
请求体:
  userIds: 用户ID列表
响应:
  code: 200
  message: 分配成功

10. 获取角色用户

yaml
接口: GET /api/v1/sys/role/{id}/users
描述: 获取角色的用户列表
响应:
  code: 200
  data:
    list: 用户列表
    total: 总条数

测试用例

功能测试用例

用例编号用例名称前置条件测试步骤预期结果状态
TC-ROLE-001创建角色已登录管理员1. 进入角色管理
2. 点击新增
3. 填写信息
4. 保存
角色创建成功[√] 通过
TC-ROLE-002编辑角色存在角色数据1. 选择角色
2. 点击编辑
3. 修改信息
4. 保存
角色更新成功[√] 通过
TC-ROLE-003删除角色存在角色数据1. 选择角色
2. 点击删除
3. 确认删除
角色删除成功[√] 通过
TC-ROLE-004分配权限存在角色和权限1. 选择角色
2. 点击分配权限
3. 选择权限
4. 保存
权限分配成功[√] 通过
TC-ROLE-005复制权限存在两个角色1. 选择目标角色
2. 点击复制权限
3. 选择源角色
4. 确认
权限复制成功[√] 通过
TC-ROLE-006配置数据权限存在角色1. 选择角色
2. 点击数据权限
3. 选择范围
4. 保存
数据权限配置成功[√] 通过
TC-ROLE-007分配用户存在角色和用户1. 选择角色
2. 点击分配用户
3. 选择用户
4. 保存
用户分配成功[√] 通过
TC-ROLE-008查看权限清单角色已分配权限1. 选择角色
2. 点击查看权限
权限清单正确显示[√] 通过

数据权限测试用例

用例编号用例名称测试场景预期结果状态
TC-DATA-001全部数据范围设置数据范围为全部可查看所有数据[√] 通过
TC-DATA-002本部门数据范围设置数据范围为本部门仅可查看本部门数据[√] 通过
TC-DATA-003部门及下级范围设置数据范围为部门及下级可查看本部门及下级数据[√] 通过
TC-DATA-004仅本人数据范围设置数据范围为仅本人仅可查看本人创建的数据[√] 通过
TC-DATA-005自定义数据范围设置自定义部门范围仅可查看指定部门数据[√] 通过

性能测试用例

用例编号用例名称测试场景预期结果状态
PERF-ROLE-001角色列表查询1000+角色数据查询时间<200ms[√] 通过
PERF-ROLE-002权限分配保存分配100+权限保存时间<500ms[√] 通过
PERF-ROLE-003数据权限校验并发100请求校验时间<50ms[√] 通过

开发记录

日期工作内容完成状态负责人
2026-04-13角色权限关联表设计[√] 已完成钱七
2026-04-14角色权限分配API开发[√] 已完成钱七
2026-04-15角色权限复制API开发[√] 已完成钱七
2026-04-15角色权限清单查询API[√] 已完成钱七
2026-04-16角色权限分配页面开发[√] 已完成赵六
2026-04-16权限树选择组件优化[√] 已完成赵六
2026-04-17角色权限复制功能[√] 已完成赵六
2026-04-17角色权限清单页面[√] 已完成赵六
2026-04-18数据权限规则表设计[√] 已完成钱七
2026-04-18数据权限规则引擎设计[√] 已完成钱七
2026-04-19数据权限配置页面开发[√] 已完成赵六
2026-04-20角色权限单元测试[√] 已完成孙八
2026-04-20角色权限集成测试[√] 已完成孙八

代码片段

角色权限服务

java
@Service
public class RolePermissionServiceImpl implements RolePermissionService {
    
    @Autowired
    private SysRolePermissionMapper rolePermissionMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignPermissions(Long roleId, List<Long> permissionIds) {
        // 删除原有权限
        rolePermissionMapper.deleteByRoleId(roleId);
        
        // 添加新权限
        if (CollectionUtils.isNotEmpty(permissionIds)) {
            List<SysRolePermission> list = permissionIds.stream()
                .map(permissionId -> {
                    SysRolePermission rp = new SysRolePermission();
                    rp.setRoleId(roleId);
                    rp.setPermissionId(permissionId);
                    return rp;
                })
                .collect(Collectors.toList());
            rolePermissionMapper.batchInsert(list);
        }
        
        // 清除权限缓存
        clearPermissionCache(roleId);
    }
    
    @Override
    public void copyPermissions(Long sourceRoleId, Long targetRoleId) {
        // 获取源角色权限
        List<Long> permissionIds = rolePermissionMapper
            .selectPermissionIdsByRoleId(sourceRoleId);
        
        // 分配给目标角色
        assignPermissions(targetRoleId, permissionIds);
    }
    
    private void clearPermissionCache(Long roleId) {
        // 获取拥有该角色的所有用户
        List<Long> userIds = getUsersByRoleId(roleId);
        
        // 清除用户权限缓存
        for (Long userId : userIds) {
            redisTemplate.delete("user:permissions:" + userId);
        }
    }
}

数据权限拦截器

java
@Intercepts({
    @Signature(type = Executor.class, method = "query", 
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Component
public class DataPermissionInterceptor implements Interceptor {
    
    @Autowired
    private DataPermissionRuleEngine ruleEngine;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        
        // 检查是否需要数据权限过滤
        DataPermission annotation = getDataPermissionAnnotation(ms);
        if (annotation != null) {
            // 构建数据权限条件
            Long userId = SecurityUtils.getCurrentUserId();
            String condition = ruleEngine.buildDataScopeCondition(userId, annotation.tableAlias());
            
            // 修改SQL
            BoundSql boundSql = ms.getBoundSql(parameter);
            String originalSql = boundSql.getSql();
            String newSql = addDataScopeCondition(originalSql, condition);
            
            // 重建BoundSql
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql, 
                boundSql.getParameterMappings(), parameter);
            
            // 复制参数
            boundSql.getParameterMappings().forEach(mapping -> {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            });
            
            // 替换参数
            args[0] = rebuildMappedStatement(ms, newBoundSql);
        }
        
        return invocation.proceed();
    }
}

前端角色权限分配组件

vue
<template>
  <el-dialog v-model="visible" title="分配权限" width="600px">
    <permission-tree
      ref="permissionTreeRef"
      v-model="selectedPermissions"
      :show-checkbox="true"
    />
    <template #footer>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="handleSave">保存</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, watch } from 'vue';
import { assignRolePermissions, getRolePermissions } from '@/api/system/role';
import PermissionTree from './PermissionTree.vue';

const props = defineProps({
  modelValue: Boolean,
  roleId: Number
});

const emit = defineEmits(['update:modelValue', 'success']);

const visible = ref(false);
const permissionTreeRef = ref(null);
const selectedPermissions = ref([]);

// 加载角色权限
const loadRolePermissions = async () => {
  if (!props.roleId) return;
  const res = await getRolePermissions(props.roleId);
  selectedPermissions.value = res.data.permissionIds;
  permissionTreeRef.value?.setCheckedKeys(selectedPermissions.value);
};

// 保存权限分配
const handleSave = async () => {
  const permissionIds = permissionTreeRef.value?.getCheckedKeys();
  await assignRolePermissions(props.roleId, permissionIds);
  ElMessage.success('权限分配成功');
  visible.value = false;
  emit('success');
};

watch(() => props.modelValue, (val) => {
  visible.value = val;
  if (val) {
    loadRolePermissions();
  }
});

watch(() => visible.value, (val) => {
  emit('update:modelValue', val);
});
</script>

问题记录

问题编号问题描述解决方案状态
ISSUE-001数据权限规则复杂,SQL拼接困难使用MyBatis插件拦截SQL,动态添加条件[√] 已解决
ISSUE-002权限缓存一致性问题角色权限变更时清除相关用户缓存[√] 已解决
ISSUE-003数据权限影响查询性能增加索引,优化规则缓存[√] 已解决

文档创建: 2026-04-13
最后更新: 2026-04-20
负责人: 钱七、赵六

Released under the MIT License.