手把手教你搞定菜单权限设计,精确到按钮级别,建议收藏(二)

简介: 在实际的项目开发过程中,菜单权限功能可以说是后端管理系统中必不可少的一个环节,根据业务的复杂度,设计的时候可深可浅,但无论怎么变化,设计的思路基本都是围绕着用户、角色、菜单进行相应的扩展。

3.4、编写用户菜单权限查询服务

在上面,我们介绍到了用户通过角色来关联菜单,因此,很容易想到,流程如下:

  • 第一步:先通过用户查询到对应的角色;
  • 第二步:然后再通过角色查询到对应的菜单;
  • 第三步:最后将菜单查询出来之后进行渲染;

实现过程相比菜单查询服务多了前2个步骤,过程如下:

@Override
public List<MenuVo> queryMenus(Long userId) {
    //1、先查询当前用户对应的角色
    Wrapper queryUserRoleObj = new QueryWrapper<>().eq("user_id", userId);
    List<UserRole> userRoles = userRoleService.list(queryUserRoleObj);
    if(!CollectionUtils.isEmpty(userRoles)){
        //2、通过角色查询菜单(默认取第一个角色)
        Wrapper queryRoleMenuObj = new QueryWrapper<>().eq("role_id", userRoles.get(0).getRoleId());
        List<RoleMenu> roleMenus = roleMenuService.list(queryRoleMenuObj);
        if(!CollectionUtils.isEmpty(roleMenus)){
            Set<Long> menuIds = new HashSet<>();
            for (RoleMenu roleMenu : roleMenus) {
                menuIds.add(roleMenu.getMenuId());
            }
            //查询对应的菜单
            Wrapper queryMenuObj = new QueryWrapper<>().in("id", new ArrayList<>(menuIds));
            List<Menu> menus = super.list(queryMenuObj);
            if(!CollectionUtils.isEmpty(menus)){
                //将菜单下对应的父节点也一并全部查询出来
                Set<Long> allMenuIds = new HashSet<>();
                for (Menu menu : menus) {
                    allMenuIds.add(menu.getId());
                    if(StringUtils.isNotEmpty(menu.getPath())){
                        String[] pathIds = StringUtils.split(",", menu.getPath());
                        for (String pathId : pathIds) {
                            allMenuIds.add(Long.valueOf(pathId));
                        }
                    }
                }
                //3、查询对应的所有菜单,并进行封装展示
                List<Menu> allMenus = super.list(new QueryWrapper<Menu>().in("id", new ArrayList<>(allMenuIds)));
                List<MenuVo> resultList = transferMenuVo(allMenus, 0L);
                return resultList;
            }
        }
    }
    return null;
}
  • 编写一个用户菜单查询接口,如下:
@PostMapping(value = "/queryMenus")
public List<MenuVo> queryMenus(Long userId){
 //查询当前用户下的菜单权限
    return menuService.queryMenus(userId);
}

有的同学,可能觉得没必要存放path这个字段,的确在某些场景下不需要。

为什么要存放这个字段呢?

小编在跟前端进行对接的时候,发现这么一个问题,有些前端的树型组件,在勾选子集的时候,不会将对应的父ID传给后端,例如,我在勾选【列表查询】的时候,前端无法将父节点【菜单管理】ID也传给后端,所有后端实际存放的是一个尾节点,需要一个字段path,来存放节点对应的父节点路径。

88.jpg

其实,前端也可以传,只不过需要修改组件的属性,前端修改完成之后,树型组件就无法全选,不满足业务需求。

所以,有些时候得根据实际得情况来进行取舍。

3.5、编写后端权限控制

后端进行权限控制目标,主要是为了防止无权限的用户,进行接口请求查询。

其中菜单编码menuCode就是一个前、后端联系的桥梁,细心的你会发现,所有后端的接口,与前端对应的都是按钮操作,所以我们可以以按钮为基准,实现前后端双向控制

以【角色管理-查询】这个为例,前端可以通过菜单编码实现是否展示这个查询按钮,后端可以通过菜单编码来判断,当前用户是否具备请求接口的权限。

以后端为例,我们只需编写一个权限注解和代理拦截器即可!

  • 编写一个权限注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermissions {
    String value() default "";
}
  • 编写一个代理拦截器,拦截有@CheckPermissions注解的方法
@Aspect
@Component
public class CheckPermissionsAspect {
    @Autowired
    private MenuMapper menuMapper;
    @Pointcut("@annotation(com.company.project.core.annotation.CheckPermissions)")
    public void checkPermissions() {}
    @Before("checkPermissions()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        Long userId = null;
        Object[] args = joinPoint.getArgs();
        Object parobj = args[0];
        //用户请求参数实体类中的用户ID
        if(!Objects.isNull(parobj)){
            Class userCla = parobj.getClass();
            Field field = userCla.getDeclaredField("userId");
            field.setAccessible(true);
            userId = (Long) field.get(parobj);
        }
        if(!Objects.isNull(userId)){
            //获取方法上有CheckPermissions注解的参数
            Class clazz = joinPoint.getTarget().getClass();
            String methodName = joinPoint.getSignature().getName();
            Class[] parameterTypes = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes();
            Method method = clazz.getMethod(methodName, parameterTypes);
            if(method.getAnnotation(CheckPermissions.class) != null){
                CheckPermissions annotation = method.getAnnotation(CheckPermissions.class);
                String menuCode = annotation.value();
                if (StringUtils.isNotBlank(menuCode)) {
                    //通过用户ID、菜单编码查询是否有关联
                    int count = menuMapper.selectAuthByUserIdAndMenuCode(userId, menuCode);
                    if(count == 0){
                        throw new CommonException("接口无访问权限");
                    }
                }
            }
        }
    }
}
  • 我们以【角色管理-查询】为例,先新建一个请求实体类RoleDto,添加用户ID属性
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class RoleDto extends Role {
 //添加用户ID
    private Long userId;
}
  • 在需要的接口上,添加@CheckPermissions注解,增加权限控制
@RestController
@RequestMapping("/role")
public class RoleController {
    private RoleService roleService;
    @CheckPermissions(value="roleMgr:list")
    @PostMapping(value = "/queryRole")
    public List<Role> queryRole(RoleDto roleDto){
        return roleService.list();
    }
    @CheckPermissions(value="roleMgr:add")
    @PostMapping(value = "/addRole")
    public void addRole(RoleDto roleDto){
        roleService.add(roleDto);
    }
    @CheckPermissions(value="roleMgr:delete")
    @PostMapping(value = "/deleteRole")
    public void deleteRole(RoleDto roleDto){
        roleService.delete(roleDto);
    }
}

依次类推,当我们想对某个接口进行权限控制的时候,只需要添加一个注解@CheckPermissions,并填写对应的菜单编码即可!

四、用户权限测试

我们先初始化一个用户【张三】,然后给他分配一个角色【访客人员】,同时给这个角色分配一下2个菜单权限【系统配置】、【用户管理】,等会用于权限测试。

初始内容如下:90.jpg91.jpg92.jpg93.jpg


数据初始化完成之后,我们来启动项目,传入用户【张三】的ID,查询用户具备的菜单权限,结果如下:

94.jpg

查询结果,用户【张三】有两个菜单权限!

接着,我们来验证一下,用户【张三】是否有角色查询权限,请求角色查询接口如下:

95.jpg

因为没有配置角色查询接口,所以无权访问!

五、总结

整片内容,只介绍了后端关键的服务实现过程,可能也有遗漏的地方,欢迎网友点评、吐槽!

相关文章
|
数据库 数据安全/隐私保护
手把手教你搞定菜单权限设计,精确到按钮级别,建议收藏(一)
在实际的项目开发过程中,菜单权限功能可以说是后端管理系统中必不可少的一个环节,根据业务的复杂度,设计的时候可深可浅,但无论怎么变化,设计的思路基本都是围绕着用户、角色、菜单进行相应的扩展。
1261 0
手把手教你搞定菜单权限设计,精确到按钮级别,建议收藏(一)
|
12月前
|
小程序 前端开发 JavaScript
微信小程序分类菜单激活状态跟随列表滚动自动切换
微信小程序分类菜单激活状态跟随列表滚动自动切换
135 0
微信小程序分类菜单激活状态跟随列表滚动自动切换
【分享】宜搭子表单点击新增自动展开最后一项,折叠前面所有项.
宜搭子表单点击新增自动展开最后一项,折叠前面所有项. by 页一
699 0
【分享】宜搭子表单点击新增自动展开最后一项,折叠前面所有项.
|
前端开发 数据库 数据安全/隐私保护
总结项目功能特点(后台系统1)| 学习笔记
快速学习 总结项目功能特点(后台系统1)
131 0
总结项目功能特点(后台系统1)| 学习笔记
自己做输入框,控制更方便
自己做输入框,控制更方便
70 0
|
Java Android开发
移动应用程序设计基础——点菜单列表实现
进一步理解Android各种控件的使用,加深控件的属性、方法的使用,熟练掌握ListView控件的使用,熟练掌握对话框的使用。 实现点菜单列表 1.1布局结构 列表布局分为两大部分,上半部分显示列表内容,底部显示所有菜品的总价; 菜品项如图所示包括 1.图片,图片格式120*120; 2.标题,居中,android:textAppearance="?android:attr/textAppearanceLarge", 3.菜品介绍内容,最多显示3行,超过部分用…表示,android:textAppearan
164 0
移动应用程序设计基础——点菜单列表实现
|
前端开发
前端工作总结108-修改新增按钮显示逻辑
前端工作总结108-修改新增按钮显示逻辑
87 0
前端工作总结108-修改新增按钮显示逻辑
|
vr&ar 图形学
【Unity3D 灵巧小知识点】 ☀️ | 层级面板中的 ‘小手指‘ 作用: 在Scen中将该物体设置为不可选中状态
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。
【Unity3D 灵巧小知识点】 ☀️ | 层级面板中的 ‘小手指‘ 作用: 在Scen中将该物体设置为不可选中状态
医学四视图-008-增加十字线开关功能,按钮显隐功能
医学四视图-008-增加十字线开关功能,按钮显隐功能
224 0
医学四视图-008-增加十字线开关功能,按钮显隐功能
|
Python Windows
保姆级别指导给UI应用添加菜单【实战分享】
正式的Python专栏第16篇,同学站住,别错过这个从0开始的文章!
219 0
保姆级别指导给UI应用添加菜单【实战分享】