乐于分享
好东西不私藏

Ruoyi-Android-App 的权限模块思考

Ruoyi-Android-App 的权限模块思考

Ruoyi-Android-App 的权限模块中,实现「授权菜单查看 + 按钮级权限控制」是基于「角色-菜单-按钮」三级权限模型来实现的,以下是实战步骤和技巧


一、先理清权限模型基础

Ruoyi 前后端分离架构的权限逻辑是一致的,Android 端只是把后端返回的权限数据做本地处理:

  • 顶级:菜单(分为目录、菜单两类,用于侧边栏/页面导航展示)
  • 子级:按钮(对应页面内的操作按钮,比如新增、编辑、删除,每个按钮绑定一个权限标识)
  • 权限绑定:用户 → 角色 → 菜单+按钮权限,后端登录后会返回当前用户拥有的所有菜单和按钮权限集合。

二、第一步:获取并缓存权限数据

用户登录成功后,后端接口会返回当前用户的路由菜单(包含按钮权限),Android 端需要先把权限缓存到本地,后续做过滤和控制。

1. 后端返回的数据结构示例(按钮绑定在菜单下)

json
{"code"200,"data": [    {"menuId"1,"menuName""系统管理","menuType""M"// 目录"path""/system","children": [        {"menuId"101,"menuName""用户管理","menuType""C"// 菜单"path""/system/user","component""UserActivity"// Android端对应Activity/Fragment路径// 按钮权限集合,绑定在当前菜单下"buttons": ["system:user:add""system:user:edit""system:user:remove""system:user:query"]        }      ]    }  ]}

2. Android 端缓存权限数据

我们一般用MMKV或者SP缓存,同时内存中保留一份方便读取:

// 权限工具类,单例保存权限数据publicclassPermissionUtils{privatestatic PermissionUtils instance;// 当前用户所有授权菜单集合private List<Menu> mAuthMenuList;// 当前用户所有授权按钮权限标识集合private Set<String> mAuthButtonSet;privatePermissionUtils(){}publicstatic PermissionUtils getInstance(){if (instance == null) {synchronized (PermissionUtils.class) {if (instance == null) {                    instance = new PermissionUtils();                }            }        }return instance;    }// 登录成功后初始化权限publicvoidinitPermission(List<Menu> menuList){this.mAuthMenuList = filterAuthMenu(menuList);this.mAuthButtonSet = parseAllButtons(menuList);// 序列化缓存到本地        MMKV.defaultMMKV().encode("auth_menu", GsonUtils.toJson(mAuthMenuList));        MMKV.defaultMMKV().encode("auth_button_set", mAuthButtonSet);    }// 过滤出用户授权的菜单(后端其实已经过滤,前端可以再做一次兜底过滤)private List<Menu> filterAuthMenu(List<Menu> menuList){        List<Menu> result = new ArrayList<>();for (Menu menu : menuList) {// 如果有子菜单递归过滤if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {                menu.setChildren(filterAuthMenu(menu.getChildren()));// 如果子菜单过滤后不为空,才保留父菜单if (!menu.getChildren().isEmpty()) {                    result.add(menu);                }            } else {// 叶子菜单判断是否授权(后端已经返回授权的,这里可以加自己的逻辑)                result.add(menu);            }        }return result;    }// 把所有菜单下的按钮权限收集到Set中,方便后续判断private Set<String> parseAllButtons(List<Menu> menuList){        Set<String> buttonSet = new HashSet<>();for (Menu menu : menuList) {if (menu.getButtons() != null && !menu.getButtons().isEmpty()) {                buttonSet.addAll(menu.getButtons());            }if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {                buttonSet.addAll(parseAllButtons(menu.getChildren()));            }        }return buttonSet;    }// 判断是否拥有某个按钮权限publicbooleanhasButtonPermission(String permission){return mAuthButtonSet != null && mAuthButtonSet.contains(permission);    }// 获取授权后的菜单列表,用于展示侧边栏/菜单列表public List<Menu> getAuthMenuList(){return mAuthMenuList == null ? new ArrayList<>() : mAuthMenuList;    }}

三、第二步:实现授权菜单的查看功能

拿到过滤后的授权菜单,直接绑定到 RecyclerView 展示即可,做菜单导航功能:

示例:菜单列表展示

// 菜单页面ActivitypublicclassMenuListActivityextendsBaseActivity{private RecyclerView rvMenu;private MenuAdapter menuAdapter;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(R.layout.activity_menu_list);        rvMenu = findViewById(R.id.rv_menu);        rvMenu.setLayoutManager(new LinearLayoutManager(this));// 直接从工具类拿已经过滤好的授权菜单        List<Menu> authMenus = PermissionUtils.getInstance().getAuthMenuList();        menuAdapter = new MenuAdapter(authMenus);        rvMenu.setAdapter(menuAdapter);// 点击菜单跳转对应页面        menuAdapter.setOnItemClickListener((adapter, view, position) -> {            Menu menu = authMenus.get(position);            jumpToPage(menu);        });    }// 根据菜单配置的组件路径跳转对应Activity/FragmentprivatevoidjumpToPage(Menu menu){try {            Class<?> clazz = Class.forName(menu.getComponent());            startActivity(new Intent(this, clazz));        } catch (ClassNotFoundException e) {            Toast.makeText(this"页面未找到", Toast.LENGTH_SHORT).show();        }    }}

菜单展示技巧:

  1. 分级展示:目录类型的菜单可以做成二级折叠列表,用ExpandableListView或者RecyclerView的多视图类型实现,和Ruoyi后台端的侧边栏效果一致;
  2. 本地缓存持久化:下次打开APP不需要重新请求菜单接口,直接从本地缓存加载权限,提升启动速度;
  3. 权限刷新:如果管理员在后台修改了当前用户的权限,APP退出登录清空缓存,重新登录拉取最新权限即可。

四、第三步:实现按钮级权限控制

核心逻辑:页面渲染按钮的时候,判断当前用户是否有该按钮的权限标识,没有权限就隐藏/禁用按钮

技巧1:自定义权限注解+反射控制(Activity/Fragment通用)

这种方式入侵性低,代码简洁:

// 1. 定义权限注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public@interface RequiresPermission {String value();}

在页面中给需要控制权限的按钮加上注解:

publicclassUserManagerActivityextendsBaseActivity{// 需要新增权限才显示@RequiresPermission("system:user:add")private Button btnAdd;// 需要编辑权限才显示@RequiresPermission("system:user:edit")private Button btnEdit;// 需要删除权限才显示@RequiresPermission("system:user:remove")private Button btnDelete;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(R.layout.activity_user_manager);// 绑定控件...// 注入权限控制        PermissionInjector.injectPermission(this);    }}

权限注入工具类,通过反射处理按钮显示隐藏:

publicclassPermissionInjector{publicstaticvoidinjectPermission(Activity activity){// 获取Activity里所有的成员变量        Field[] fields = activity.getClass().getDeclaredFields();for (Field field : fields) {// 判断是否有我们的权限注解            RequiresPermission annotation = field.getAnnotation(RequiresPermission.class);if (annotation != null) {                String permission = annotation.value();// 判断是否有权限if (!PermissionUtils.getInstance().hasButtonPermission(permission)) {try {// 设置可访问,拿到控件实例                        field.setAccessible(true);                        Object view = field.get(activity);if (view instanceof View) {// 没有权限直接隐藏,也可以改成禁用                            ((View) view).setVisibility(View.GONE);                        }                    } catch (IllegalAccessException e) {                        e.printStackTrace();                    }                }            }        }    }}

技巧2:适配器中动态控制Item按钮权限

如果是列表项中的按钮(比如每行的编辑删除按钮),在RecyclerView.Adapter中绑定数据的时候直接判断即可:

publicclassUserAdapterextendsBaseQuickAdapter<UserBaseViewHolder{@Overrideprotectedvoidconvert(BaseViewHolder holder, User item){// ...绑定其他数据// 控制删除按钮显示boolean hasDeletePermission = PermissionUtils.getInstance()                .hasButtonPermission("system:user:remove");        holder.setGone(R.id.btn_delete, !hasDeletePermission);    }}

技巧3:接口请求前做权限二次校验

APP端的权限控制是做交互层面的隐藏,后端接口一定要做二次校验,不过前端也可以在请求前做判断,避免无效请求:

// 点击删除按钮之前先判断权限btnDelete.setOnClickListener(v -> {if (!PermissionUtils.getInstance().hasButtonPermission("system:user:remove")) {        Toast.makeText(this"无删除权限", Toast.LENGTH_SHORT).show();return;    }// 执行删除请求...});

五、常见问题处理技巧

  1. 动态权限刷新:如果需要不重新登录刷新权限,调用PermissionUtils.getInstance().initPermission(newMenuList),然后重新加载菜单列表,重启当前Activity即可刷新按钮状态;
  2. 空菜单处理:如果用户没有授权任何菜单,展示「暂无权限」提示页;
  3. 混淆问题:使用反射注入注解的方式,需要在混淆规则中保留RequiresPermission注解和你的View字段,避免被混淆导致注解失效;
pro
-keepattributes *Annotation*-keep class 你的包名.permission.** { *; }
  1. 性能优化:反射注入只会在页面创建的时候执行一次,对性能影响很小,如果担心反射,可以改成在onCreate中手动调用判断,代码稍微多一点但是更直接。

总结

整个流程的核心就是:

  1. 登录拉取权限 → 本地缓存分类:菜单集合+按钮权限集合
  2. 展示菜单的时候直接用过滤后的授权菜单
  3. 按钮通过权限标识判断,没有权限就隐藏/禁用,接口层面二次校验,既保证了交互体验,也保证了权限安全。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Ruoyi-Android-App 的权限模块思考

猜你喜欢

  • 暂无文章