暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java框架笔记之RBAC权限管理系统

java学途 2021-06-21
3112


不点蓝字,我们哪来故事?




BootStrap的官方文档:https://www.bootcss.com/


RBAC权限管理系统

目标:

  • 了解权限管理在系统中的作用

  • 熟练使用SSM搭建项目开发环境

  • 熟练使用jQuery完成前台页面的基本功能

  • 完成RBAC权限管理系统的开发

  • 通过该系统熟悉WEB应用的基本开发流程


权限管理系统

基于角色的访问控制,这种模型的基本概念是把许可权(Permission)与角色(Role)联系在一起,用户通过充当合适角色的成员而获得该角色的许可权。

在权限管理中使用最多的还是基于角色访问控制(RBAC: Role Based Access Control)

基于角色的访问控制(RBAC):角色决定访问权限。用组织角色来同意或拒绝访问


RBAC重要对象

  • 用户(User):角色施加的主体;用户通过拥有某个或多个角色以得到对应的权限。

  • 角色(Role):表示一组权限的集合。

  • 权限(Permission):用于限定能够访问的一个资源。


思路:

  1. 需提供操作来为用户分配角色和权限(用户、角色和权限数据还有其之间关系数据都得保存起来)。

  2. 获取访问资源的用户(登录)和被访问的资源

    • 若用户是管理员,直接放行访问;

    • 若资源不需要权限控制,直接放行访问;

    • 其他情况则从数据库中查询该用户(先找角色,再找角色对应的权限)是否具有访问该资源的权限,若没有,提醒用户权限不足,若有放行访问。


环境搭建

  1. 数据库表结构理解, 部门表,员工表,角色表,权限表

  2. 创建Maven项目, 项目添加依赖, 以及相关配置文件

  3. 重命名包,删除一些之前引入的样式文件与JS 文件


创建表,使用逆向工程根据表创建出基本的项目结构,拷贝项目需要的资源到项目中:



实现分页功能:

查询的类:用户可设置的参数

    //封装分页查询对象
    @Getter
    @Setter
    public class QueryObject {
    private int currentPage = 1; //当前页
    private int pageSize = 3; //每页显示条数
    //计算开始的索引
    public int getStart(){
    return (currentPage - 1) * pageSize;
    }
    }

    分页结果对象:包含页面中的7个分页数据

      //封装页面中需要的数据(分页结果对象)
      @Getter
      public class PageResult {
      private Integer currentPage; //当前页
      private Integer pageSize; //每页显示条数
      //sql查询出来的数据
      private List<?> list; //结果集
      private Integer totalCount; //结果总数
      //计算出来的数据
      private Integer prevPage; //上一页
      private Integer nextPage; //下一页
      private Integer totalPage; //总页数


      //当总条数为0时使用
      public PageResult(Integer pageSize) {
      this(1, 0, Collections.EMPTY_LIST, pageSize);
      }


      public PageResult(Integer currentPage, Integer totalCount, List<?> list,
      Integer pageSize) {
      this.currentPage = currentPage;
      this.pageSize = pageSize;
      this.list = list;
      this.totalCount = totalCount;
      //计算
      this.totalPage = totalCount % pageSize == 0 ? totalCount pageSize :
      totalCount pageSize + 1;
      this.prevPage = currentPage > 1 ? currentPage - 1 : 1;
      this.nextPage = currentPage < totalPage ? currentPage + 1 : totalPage;
      }
      }

      在部门的 Mapper 中增加分页查询的两个方法及对应XML 元素

        <!-- 查询总条数 -->
        <select id="selectForCount" resultType="java.lang.Integer">
        SELECT COUNT(*) FROM department
        </select>
        <!-- 查询分页结果集 -->
        <select id="selectForList" resultType="cn.wolfcode.rbac.domain.Department">
        SELECT id,name,sn FROM department LIMIT #{start},#{pageSize}
        </select>

        在service中添加分页查询的方法:

          /**
          * 分页查询
          *
          * @param qo 封装查询时需要的数据#{xxx}来取其属性值
          * @return
          */
          @Override
          public PageResult query(QueryObject qo) {


          int count = departmentMapper.selectForCount(qo);//高查询时需要参数
          //判断是否有数据
          if (count == 0) {
          return new PageResult(qo.getPageSize());
          }
          //查询结果集
          List<Department> list = departmentMapper.selectForList(qo);
          return new PageResult(qo.getCurrentPage(), count, list, qo.getPageSize());
          }

          controller调用分页方法:

            // 提供一个方法处理查询所有部门数据请求,响应 HTML
            @RequestMapping("/list")
            public String list(Model model , @ModelAttribute("qo") QueryObject qo){
            //调用service的方法的到结果集
            PageResult result = departmentService.query(qo);
            //共享结果集:在页面遍历显示
            model.addAttribute("result", result);
            return "department/list"; //返回视图名称:加上前缀和后缀的jsp
            }

            分析分页插件的使用:已经准备好,无需再写:

            以前的分页代码非常麻烦,现在就比较简洁:



            员工管理

            分页查询与部门的分页查询基本相同,

            创建controller,service,mapper分页相关方法,

            员工的domain中添加一个部门对象,员工与部门之间是多对一的关系。

              private Department dept;//关联属性

              SQL的多表联查:

                <!--员工多表联查-->
                <select id="selectForList" resultMap="BaseResultMap">
                SELECT e.id, e.name, e.password, e.email, e.age, e.admin, d.id d_id, d.name d_name
                FROM employee e LEFT JOIN department d
                ON e.dept_id = d.id
                LIMIT #{start} , #{pageSize}
                </select>

                将查询结果封装到resultMap中:


                高级查询:关键字和部门的下拉框查询

                除了封装分页数据,还要封装用户给的关键字信息,同时还要分页显示,所以继承QueryObject

                  @Getter
                  @Setter
                  public class EmployeeQuery extends QueryObject {
                  //解决keyword属性不存在的问题 :自己的QO
                  private String keyword;
                  //显示部门信息:通过deptId 来显示
                  private Long deptId = -1L;//默认值-1,因为jsp页面 -1 表示全部


                  //覆写get方法,在里面排除keyword为空的情况
                  public String getKeyword() {
                  return StringUtils.hasLength(keyword) ? keyword.trim() : null;
                  }
                  }

                  controller器类:

                    @Controller
                    @RequestMapping("/employee")
                    public class EmployeeController {


                    @Autowired
                    private IEmployeeService employeeService;
                    @Autowired
                    private IDepartmentService departmentService;


                    // 显示列表
                    @RequestMapping("/list")
                    public String list(Model model, @ModelAttribute("qo") EmployeeQuery qo) {
                    PageResult result = employeeService.query(qo);
                    model.addAttribute("result", result);
                    //部门下拉框:显示部门的信息-->查到部门共享出去
                    model.addAttribute("depts", departmentService.listAll());
                    return "employee/list"; //跳转到员工的视图
                    }
                    }

                    在查询中条加了条件:

                    部门下拉框的显示和数据回显:



                    员工的添加和编辑:

                    controller中的input和saveOrUpdate方法

                      @RequestMapping("/input")
                      public String input(Long id, Model model) {
                      if (id != null) { // 编辑
                      Employee employee = employeeService.get(id);//查询该对象然后共享
                      model.addAttribute("employee", employee);
                      //编辑状态下的部门回显: 在input界面或取遍历显示
                      model.addAttribute("depts",departmentService.listAll());
                      }
                      return "employee/input";
                      }


                      @RequestMapping("/saveOrUpdate")
                      public String saveOrUpdate(Employee employee) {
                      //通过 id 判断是添加还是修改
                      if (employee.getId() == null) {//添加
                      employeeService.save(employee);
                      } else {//编辑
                      employeeService.update(employee);
                      }
                      return "redirect:/employee/list.do";
                      }

                      编辑时input界面的回显问题:

                      编辑员工时,不显示密码操作相关组件:

                        <%--  新增的时候才显示密码,编辑不显示密码:根据共享的员工是否为空来判断是添加还是编辑 --%>
                        <c:if test="${empty employee}">
                        <%-- 密码显示相关的标签 --%>
                        </c:if>



                        角色管理:

                        1.创建角色表, 员工角色关系管理中间表

                        2.实现角色页面的crud功能:逆向工程、添加分页查询的方法

                        3.添加service和impl,拷贝部门的 Service 接口及实现类修改。

                        4.编写 RoleController 和拷贝员工 list.jsp 修改,拷贝部门的 Controller 及 jsp 修改。



                        编辑员工时,角色列表的移动

                        1. 在EmployeeController中查询到角色数据,共享到页面

                          编辑员工时,角色列表的左边框的选项的显示以供选择。


                        2. 在input.jsp页面中回显角色数据

                        3. 在input.jsp页面中使用JS完成角色的左移有移功能

                          //左移动和右移
                          function moveSelected(src, taget) {
                          //把源中被选中的option,全部追加到目标元素中
                          $("." + taget).append($("." + src + "> option:selected"));
                          }


                          //全部移动
                          function moveAll(src, taget) {
                          $("." + taget).append($("." + src + "> option"));
                          }


                          超管隐藏角色编辑

                          选中超级管理员复选框,超级管理员不需要显示角色的编辑下拉框。

                          若是超级管理员,若打勾,则隐藏或者删除角色配置的页面元素;若不是打勾,则显示或者还原角色配置的页面元素。

                          ps:当需要拿到某一个元素再做操作的时候,需要先把页面加载完成才能拿到。如对按钮组件监听绑定事件。

                            //加载完整个页面:类似于window.onload=function(){}
                            $(function () {}


                            页面代码的实现:

                              //员工input页面的超级管理员的点击显示处理
                              var roleDiv;//提成一个全局变量
                              //监听超管复选框的点击事件
                              $("#admin").click(function () {
                              //拿到事件源,获得选中的状态
                              //$(this).val() 拿到的是on,这个值要用.prop("checked")来拿(复选框获取选中状态的方法)
                              var val = $(this).prop("checked");
                              //判断是否被选中
                              if (val) {
                              //被选中,就将角色的选择框删除
                              roleDiv = $("#role").detach();//不删除事件和附加数据,因为数据还需要恢复
                              } else {
                              //没有选中:将数据恢复到角色框中-->作为兄弟跟在adminDiv后面
                              $("#adminDiv").after(roleDiv);
                              }
                              });


                              员工保存时全选角色下拉框

                              下拉框中选中的数据的才会提交,若没有选中的数据是不会提交的,但是其实我们是想不选中的那些也要提交的。

                              思路:修改按钮为普通按钮,绑定点击事件处理函数,在函数中把右边的 select 元素中的 option 设置为选中的,再提交表单。

                                // 保存时:解决右边数据提交只会提交被选中的id:改成手动提交,且提交之前将所有的数据都为选中状态
                                $("#submitBtn").click(function () {
                                //把右边的下拉框的option全部选择
                                $(".selfRoles option").prop("selected", true);
                                //提交表单
                                $("#editForm").submit();
                                });



                                后台员工保存时处理角色

                                保存员工时:除向员工表中保存数据,还要处理与部门的关系数据,还需保存该角色的权限,即往 role_permission 表中存数据

                                1. 修改 EmployeeController

                                    @RequestMapping("/saveOrUpdate")
                                    public String saveOrUpdate(Employee employee,Long[] ids) {
                                    //通过 id 判断是添加还是修改
                                    if (employee.getId() == null) {//添加
                                    employeeService.save(employee, ids);
                                    } else {//编辑
                                    employeeService.update(employee,ids);
                                    }
                                    return "redirect:/employee/list.do";
                                    }
                                  • 修改员工 Service 接口及实现类

                                      void save(Employee employee, Long[] ids);//保存员工是需要保存和角色的关系:通过角色的id来对应部门


                                        @Override
                                        public void save(Employee employee, Long[] ids) {
                                        employeeMapper.insert(employee);//插入员工,再插入关系
                                        //判断传过来的 ids 数组是否为空
                                        if (ids != null && ids.length > 0) {
                                        //不为空时,保存时就要连关系一起保存
                                        for (Long roleId : ids) {
                                        employeeMapper.insertRelation(employee.getId(), roleId);
                                        }
                                        }
                                        }
                                      • 修改员工 Mapper 接口及 XML



                                      部门回显

                                      回显部门:要先查到,共享,再在页面中拿到数据。额外SQL或连表查询,后者效率高。

                                      1. EmployeeMapper.xml



                                      2. input.jsp





                                      角色回显

                                      还是先查出来,共享再回显。后台查询员工时也把该员工的角色查询出来

                                      1. 修改员工实体类,新增 roles 属性

                                          private List<Role> roles = new ArrayList<>();//角色属性:一个员工可能有多个角色
                                        • 修改 EmployeeMapper.xml 中的 BaseResultMap,使用额外 SQL 把员工角色查询出来。



                                        • 页面回显角色



                                        角色去重

                                        刚上面角色回显的时候,发现左右两边的角色有重复,应该是有右边有的角色,不应该在左边出现。

                                        简单直接的思路:将右边存在的角色id存在数组中,判断左边的角色id在数组中是否存在,存在就将它删除。

                                          //编辑时角色页面的两边数据去重
                                          //将右边的数据放到数组中,再判断左边的数据在数组中是否存在,存在则删除
                                          var selfIds = [];
                                          $('.selfRoles > option').each(function (index,ele) {//遍历右边获得的元素集
                                          selfIds.push($(ele).val());//拿到元素的id存到数组中
                                          });
                                          $('.allRoles > option').each(function (index,ele) {//遍历左边获得的元素集
                                          var id = $(ele).val();//拿到左边id
                                          if($.inArray(id,selfIds) > -1){//右边的id在数组中已经存在
                                          $(ele).remove();//删除已存在
                                          }
                                          });

                                          另一种思路:页面加载完,拿左右两边的 option 对比,遍历左边每个,若不存右边,则保留,若存在则删除。


                                          超管隐藏角色编辑

                                          如果当前用户是超级管理员,它本身就具有全部角色,所以不需要显示角色相关的编辑界面。

                                          思路:在页面接在完成的时候,就去判断当前员工是不是超管,是就隐藏。

                                            $(function () {
                                            //超级管理员隐藏角色的编辑:页面加载完,若是超管,隐藏角色分配框
                                            //方式1:var checked = $('#admin').prop('checked');
                                            if (checked){
                                            console.log($('#role').detach());
                                            }
                                            //方式2:<c:if test="${employee.admin}">
                                            roleDiv = $('#role').detach();
                                            </c:if>
                                            }



                                            员工编辑时处理角色 :更新关系

                                            更新员工表中此数据还要处理与角色的关系数据,还有删除中间表 employee_role 中数据之后再往中间表 employee_role 中添加新的数据。

                                            1. 修改员工控制器

                                            2. 修改员工 Service 接口及实现类

                                            3. 修改员工 Mapper 接口及 XML




                                            员工删除

                                            除删除角色表此数据,还要从中间表 role_permission 中删除与此角色相关的数据。建议先删关系(防止设置了外键删除失败)


                                            修改员工 Service 实现类



                                            java学途

                                            只分享有用的Java技术资料 

                                            扫描二维码关注公众号

                                             


                                            笔记|学习资料|面试笔试题|经验分享 

                                            如有任何需求或问题欢迎骚扰。微信号:JL2020aini

                                            或扫描下方二维码添加小编微信

                                             




                                            小伙砸,欢迎再看分享给其他小伙伴!共同进步!



                                            文章转载自java学途,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                            评论