1.权限控制

权限控制的本质是使用钥匙开锁。
image.png

2.给Admin分配Role

1.目标

通过前端页面操作,将Admin与Role之间的关系保存到数据库

2.思路

六.权限分配 - 图2

3.代码

1.创建保存admin-Role关联关系的数据库表

  1. USE project_rowd;
  2. CREATE TABLE inner_admin_role
  3. (
  4. id INT NOT NULL AUTO_INCREMENT,
  5. admin_id INT,
  6. role_id INT,
  7. PRIMARY KEY (id)
  8. );

2.修改admin-page.jsp中对应按钮的单击响应函数:

跳转到assign/to/page.html页面,附带当前的页码、关键词以及当前按钮对应的admin的id

<a href="assign/to/page.html?adminId=${admin.id}&pageNum=${requestScope.pageInfo.pageNum}&keyword=${param.keyword}" class="btn btn-success btn-xs">
    <i class=" glyphicon glyphicon-check">
    </i>
</a>

3.创建处理权限分配功能的控制类:AssignHandler

项目结构:
image.png
代码:

@RequestMapping("/assign/to/page.html")
public String toAssignPage(@RequestParam("adminId") Integer adminId, ModelMap modelMap){

    // 得到对应当前adminId未被分配的角色(Role)List
    List<Role> UnAssignedRoleList = roleService.queryUnAssignedRoleList(adminId);

    // 得到对应当前adminId已被分配的角色(Role)List
    List<Role> AssignedRoleList = roleService.queryAssignedRoleList(adminId);

    // 将已选择的、未选择的放入modelMap
    modelMap.addAttribute("UnAssignedRoleList",UnAssignedRoleList);
    modelMap.addAttribute("AssignedRoleList",AssignedRoleList);

    // 请求转发到assign-role.jsp
    return "assign-role";
}

4.service层方法

在RoleService中

@Override
public List<Role> queryUnAssignedRoleList(Integer adminId) {
    return roleMapper.queryUnAssignedRoleList(adminId);
}

@Override
public List<Role> queryAssignedRoleList(Integer adminId) {
    return roleMapper.queryAssignedRoleList(adminId);
}

5.roleMapper.xml文件

roleMapper.xml文件中对应的两个SQL语句(通过in 与 not in查询已分配和未分配的):

<!--查询对应adminId的未被分配的Role-->
<select id="queryUnAssignedRoleList" resultMap="BaseResultMap">
  select id,name
  from t_role
  where
  id not in (select role_id from inner_admin_role where admin_id = #{adminId})
</select>

<!--查询对应adminId的已被分配的Role-->
<select id="queryAssignedRoleList" resultMap="BaseResultMap">
  select id,name
  from t_role
  where
  id in (select role_id from inner_admin_role where admin_id = #{adminId})
</select>

6.在页面上显示角色数据

①对option标签进行说明:

<option value="将来在提交表单时一起发给Handler的值">在浏览器上让用户看见的值</option>

②实际显示角色消息时:

<option value="角色的id">角色的名称</option>

③举例:

<option value="5">项目总监</option>

④页面具体代码
在assign-role.jsp页面中
未分配角色列表:

<label for="exampleInputPassword1">未分配角色列表</label><br>
                            <select class="form-control" multiple="" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.UnAssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>

已分配角色列表:

<label for="exampleInputPassword1">已分配角色列表</label><br>
                            <!-- 被选中要分配的部分,name设置为roleIdList -->
                            <select name="roleIdList" class="form-control" multiple="" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.AssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>

7.调整表单让数据能够提交

在assign-role.jsp页面中

<form action="assign/do/assign.html" method="post" role="form" class="form-inline">
                        <!--隐藏域保存不会改变的adminId、pageNum、keyword,在提交时一起传给后端-->
                        <input type="hidden" value="${param.adminId}" name="adminId"/>
                        <input type="hidden" value="${param.pageNum}" name="pageNum"/>
                        <input type="hidden" value="${param.keyword}" name="keyword"/>
                        <div class="form-group">
                            <label for="exampleInputPassword1">未分配角色列表</label><br>
                            <select class="form-control" multiple="" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.UnAssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>
                        </div>
                        <div class="form-group">
                            <ul>
                                <li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li>
                                <br>
                                <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li>
                            </ul>
                        </div>
                        <div class="form-group" style="margin-left:40px;">
                            <label for="exampleInputPassword1">已分配角色列表</label><br>
                            <!-- 被选中要分配的部分,name设置为roleIdList -->
                            <select name="roleIdList" class="form-control" multiple="multiple" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.AssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>
                        </div>
                        <button id="submitBtn" type="submit" style="width:100px;margin-top: 20px;margin-left: 230px;" class="btn btn-sm btn-success btn-block">提交</button>
                    </form>

8.jQuery代码

①思路
image.png
②调整左右按钮代码,绑定事件

<li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li>
                                <br>
                                <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li>

③绑定事件

<script type="text/javascript">
    $(function () {

        // 给向右的按钮添加单击响应函数,将左边选中的添加到右边
        $("#toRightBtn").click(function (){
            $("select:eq(0)>option:selected").appendTo("select:eq(1)");
        });
        // 给向左的按钮添加单击响应函数,将右边选中的添加到左边
        $("#toLeftBtn").click(function (){
            $("select:eq(1)>option:selected").appendTo("select:eq(0)");
        });

        // 给提交按钮添加单击响应函数,使其在提交前,先全选“已分配角色列表”的选项,使提交时会提交全部
        // 避免不提交之前存在的option的bug
        $("#submitBtn").click(function () {
            $("select:eq(1)>option").prop("selected","selected");
        });

    });
</script>

④.提交表单

<form action="assign/do/assign.html" method="post" role="form" class="form-inline">
  ...
</form>

9.后端Handler代码

@RequestMapping("/assign/do/assign.html")
public String saveAdminRoleRelationship(
        @RequestParam("adminId") Integer adminId,
        @RequestParam("pageNum") Integer pageNum,
        @RequestParam("keyword") String keyword,
        // 允许roleIdList为空(因为可能已分配的被清空)
        @RequestParam(value = "roleIdList", required = false) List<Integer> roleIdList
){
    // 调用service层方法
    adminService.saveAdminRoleRelationship(adminId, roleIdList);

    //重定向(减少数据库操作)返回信息页
    return "redirect:/admin/page/page.html?pageNum="+pageNum+"&keyword="+keyword;
}

10.Service层方法

@Override
public void saveAdminRoleRelationship(Integer adminId, List<Integer> roleIdList) {

    // 先清除旧的对应inner_admin_role表中对应admin_id的数据
    adminMapper.clearOldRelationship(adminId);

    // 如果roleIdList非空,则将该list保存到数据库表中,且admin_id=adminId
    if (roleIdList != null && roleIdList.size() > 0){
        adminMapper.saveAdminRoleRelationship(adminId,roleIdList);
    }
    // roleIdList为空,则清空后不做操作

}

11.AdminMapper代码
这里注意因为插入的语句传入了两个变量,因此需要在mapper接口中用@Param标注出来;下面就是对应的mapper接口的方法:

void saveAdminRoleRelationship(@Param("adminId") Integer adminId, @Param("roleIdList") List<Integer> roleIdList);

void clearOldRelationship(Integer adminId);

12.修正一个bug
问题:
image.png
解决方法:

// 给提交按钮添加单击响应函数,使其在提交前,先全选“已分配角色列表”的选项,使提交时会提交全部
        // 避免不提交之前存在的option的bug
        $("#submitBtn").click(function () {
            $("select:eq(1)>option").prop("selected","selected");
        });

11.前端代码

查询到数据,并存入modelMap中后,跳转到assign-role.jsp页面
在该页面显示查询到的信息:
需要在文件的头部引入jstl,使用forEach遍历的到的List,分别在两个select标签中显示出来;并且将前面页面传给后端的页码、关键词、Admin的id放在隐藏域,一起发送给后端。
①项目结构:
image.png
assign-role.jsp代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="zh-CN">
<%@include file="/WEB-INF/include-head.jsp" %>
<script type="text/javascript">
    $(function () {

        // 给向右的按钮添加单击响应函数,将左边选中的添加到右边
        $("#toRightBtn").click(function (){
            $("select:eq(0)>option:selected").appendTo("select:eq(1)");
        });
        // 给向左的按钮添加单击响应函数,将右边选中的添加到左边
        $("#toLeftBtn").click(function (){
            $("select:eq(1)>option:selected").appendTo("select:eq(0)");
        });

        // 给提交按钮添加单击响应函数,使其在提交前,先全选“已分配角色列表”的选项,使提交时会提交全部
        // 避免不提交之前存在的option的bug
        $("#submitBtn").click(function () {
            $("select:eq(1)>option").prop("selected","selected");
        });


    });
</script>
<body>
<%@include file="/WEB-INF/include-nav.jsp" %>
<div class="container-fluid">
    <div class="row">
        <%@include file="/WEB-INF/include-sidebar.jsp" %>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
            <ol class="breadcrumb">
                <li><a href="#">首页</a></li>
                <li><a href="#">数据列表</a></li>
                <li class="active">分配角色</li>
            </ol>
            <div class="panel panel-default">
                <div class="panel-body">
                    <form action="assign/do/assign.html" method="post" role="form" class="form-inline">
                        <!--隐藏域保存不会改变的adminId、pageNum、keyword,在提交时一起传给后端-->
                        <input type="hidden" value="${param.adminId}" name="adminId"/>
                        <input type="hidden" value="${param.pageNum}" name="pageNum"/>
                        <input type="hidden" value="${param.keyword}" name="keyword"/>
                        <div class="form-group">
                            <label for="exampleInputPassword1">未分配角色列表</label><br>
                            <select class="form-control" multiple="" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.UnAssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>
                        </div>
                        <div class="form-group">
                            <ul>
                                <li id="toRightBtn" class="btn btn-default glyphicon glyphicon-chevron-right"></li>
                                <br>
                                <li id="toLeftBtn" class="btn btn-default glyphicon glyphicon-chevron-left" style="margin-top:20px;"></li>
                            </ul>
                        </div>
                        <div class="form-group" style="margin-left:40px;">
                            <label for="exampleInputPassword1">已分配角色列表</label><br>
                            <!-- 被选中要分配的部分,name设置为roleIdList -->
                            <select name="roleIdList" class="form-control" multiple="" size="10" style="width:100px;overflow-y:auto;">
                                <c:forEach items="${requestScope.AssignedRoleList}" var="role">
                                    <option value="${role.id}">${role.name}</option>
                                </c:forEach>
                            </select>
                        </div>
                        <button id="submitBtn" type="submit" style="width:100px;margin-top: 20px;margin-left: 230px;" class="btn btn-sm btn-success btn-block">提交</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>

3.给Role分配Auth

1.目标

将Role(角色)与Auth(权限)的关联关系保存到数据库。

2.思路

通过role-page页面的下面的按钮,打开分配Auth的模态框
六.权限分配 - 图7

3.代码

1.准备数据库表

t_auth表:

use project_rowd;

# 建t_auth表
CREATE TABLE t_auth (
    id int(11) NOT NULL AUTO_INCREMENT,
    name varchar(200) DEFAULT NULL,
    title varchar(200) DEFAULT NULL,
    category_id int(11) DEFAULT NULL,

    PRIMARY KEY (id)
);

# 给t_auth表插入数据
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(1,'','用户模块',NULL);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(2,'user:delete','删除',1);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(3,'user:get','查询',1);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(4,'','角色模块',NULL);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(5,'role:delete','删除',4);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(6,'role:get','查询',4);
INSERT INTO t_auth(id,`name`,title,category_id) VALUES(7,'role:add','新增',4);

name字段:给资源分配权限或给角色分配权限时使用的具体值,将来做权限验证也是使用name字段的值来比对。建议使用英文。
name字段中的“:”没有任何含义,可以不用“:”,可以使用其他的,如“@”,“#”,“$”,“%”等都可以,只是为了我们方便查看。
title字段:在页面上显示,让用户便于查看的值,建议使用中文。
category_id字段:关联到当前权限所属的分类。这个关联不是到其他表关联,就在当前表内进行关联,关联到其他记录。
图解:
image.png

2.逆向工程

逆向生成Auth实体类、Mapper,并创建AuthService接口、等。

3.代码:打开模态框

①准备模态框
项目结构:
image.png
代码:

<%@ page language="java" contentType="text/html;charset=UTF-8"
    pageEncoding="UTF-8"%>
<div id="assignModal" class="modal fade" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
                    aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title">角色权限分配</h4>
            </div>
            <div class="modal-body">
                <ul id="authTreeDemo" class="ztree"></ul>
            </div>
            <div class="modal-footer">
                <button id="assignBtn" type="button" class="btn btn-primary">好的,我设置好了!执行分配!</button>
            </div>
        </div>
    </div>
</div>

②修改my-role.js文件
image.png按钮绑定绑定单击响应函数
项目结构:
image.png
代码:
在fillTableTBody()函数中,修改checkBtn

var checkBtn = "<button type='button' id='"+roleId+"' class='btn btn-success btn-xs checkBtn'><i class=' glyphicon glyphicon-check'></i></button>"

③在role-page.jsp中引入模态框字段

<%@include file="/WEB-INF/modal-role-assign-auth.jsp"%>

④在role-page.jsp中给对应按钮添加单击响应函数:

// 给分配权限的按钮添加单击响应函数,打开分配模态框
$("#rolePageTBody").on("click",".checkBtn",function () {

    // 将当前按钮的id放入全局变量
    window.roleId = this.id;
    // 打开模态框
    $("#assignModal").modal("show");
    // 生成权限信息
    generateAuthTree();
});

4.代码:模态框显示树形结构

①在my-role.js中声明generateAuthTree()函数
通过zTree生成权限信息generateAuthTree(),且将从后端查到的已经分配的权限回显到模态框中:
这里直接交给zTree自己组装树形结构
要使自动组装需要开启简单JSON功能

// 生成权限信息的树形结构
function generateAuthTree(){

    var ajaxReturn = $.ajax({
        url: "assign/get/tree.json",
        type: "post",
        async: false,
        dataType: "json"
    });

    if (ajaxReturn.status != 200){
        layer.msg("请求出错!错误码:"+ ajaxReturn.status + "错误信息:" + ajaxReturn.statusText);
        return ;
    }

    var resultEntity = ajaxReturn.responseJSON;

    if (resultEntity.result == "FAILED"){
        layer.msg("操作失败!"+resultEntity.message);
    }

    if (resultEntity.result == "SUCCESS"){
        var authList = resultEntity.data;
        // 将服务端查询到的list交给zTree自己组装
        var setting = {
            data: {
                // 开启简单JSON功能
                simpleData: {
                    enable: true,
                    // 通过pIdKey属性设置父节点的属性名,而不使用默认的pId
                    pIdKey: "categoryId"
                },
                key: {
                    // 设置在前端显示的节点名是查询到的title,而不是使用默认的name
                    name:"title"
                },
            },

            check: {
                enable:true
            }
        };

        // 生成树形结构信息
        $.fn.zTree.init($("#authTreeDemo"), setting, authList);

        // 设置节点默认是展开的
        // 1 得到zTreeObj
        var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo");
        // 2 设置默认展开
        zTreeObj.expandAll(true);

        // ----------回显部分----------

        // 回显(勾选)后端查出的匹配的权限
        ajaxReturn = $.ajax({
            url: "assign/get/checked/auth/id.json",
            type: "post",
            dataType: "json",
            async: false,
            data:{
                "roleId":window.roleId
            }
        });

        if (ajaxReturn.status != 200){
            layer.msg("请求出错!错误码:"+ ajaxReturn.status + "错误信息:" + ajaxReturn.statusText);
            return ;
        }

        resultEntity = ajaxReturn.responseJSON;

        if (resultEntity.result == "FAILED"){
            layer.msg("操作失败!"+resultEntity.message);
        }

        if (resultEntity.result == "SUCCESS"){
            var authIdArray = resultEntity.data;

            // 遍历得到的autoId的数组
            // 根据authIdArray勾选对应的节点
            for (var i = 0; i < authIdArray.length; i++){
                var authId = authIdArray[i];

                // 通过id得到treeNode
                var treeNode = zTreeObj.getNodeByParam("id",authId);

                // checked设置为true,表示勾选节点
                var checked = true;

                // checkTypeFlag设置为false,表示不联动勾选,
                // 即父节点的子节点未完全勾选时不改变父节点的勾选状态
                // 否则会出现bug:前端只要选了一个子节点,传到后端后,下次再调用时,发现前端那个子节点的所有兄弟节点也被勾选了,
                // 因为在子节点勾选时,父节点也被勾选了,之后前端显示时,联动勾选,导致全部子节点被勾选
                var checkTypeFlag = false;

                // zTreeObj的checkNode方法 执行勾选操作
                zTreeObj.checkNode(treeNode,checked,checkTypeFlag);
            }
        }

    }
}

1.创建角色到权限之间关联关系的中间表

该表和inner_admin_role相似,不需要进行逆向工程

use project_rowd;

CREATE TABLE inner_role_auth( 
    id INT NOT NULL AUTO_INCREMENT,
    role_id INT,
    auth_id INT,
    PRIMARY KEY (id) 
);

2.Handler层(显示树形结构部分)

@ResponseBody
@RequestMapping("/assign/get/tree.json")
public ResultEntity<List<Auth>> getAuthTree(){
    List<Auth> authList = authService.queryAuthList();

    return ResultEntity.successWithData(authList);
}

3.Service层

@Override
public List<Auth> queryAuthList() {
    // 直接传入一个new的AuthExample,查询全部的Auth
    return authMapper.selectByExample(new AuthExample());
}

4.Handler层(回显已分配权限部分)

// 获得被勾选的auth信息
@ResponseBody
@RequestMapping("/assign/get/checked/auth/id.json")
public ResultEntity<List<Integer>> getAuthByRoleId(Integer roleId){
    List<Integer> authIdList = authService.getAuthByRoleId(roleId);
    return ResultEntity.successWithData(authIdList);
}

5.Service层

@Override
public List<Integer> getAuthByRoleId(Integer roleId) {
    return authMapper.getAuthByRoleId(roleId);
}

6.Mapper接口

List<Integer> getAuthByRoleId(Integer roleId);

7.authMapper.xml文件的sql

<!--从inner_role_auth查找匹配roleId的auth_id-->
<select id="getAuthByRoleId" resultType="int">
  select auth_id from inner_role_auth
  where role_id = #{roleId}
</select>

5.分配权限功能

1.模态框的提交按钮绑定单击响应函数

给模态框的提交按钮绑定单击响应函数(在my-role.js中)

// 给分配权限的模态框中的提交按钮设置单击响应函数
$("#assignBtn").click(function () {
    // 声明一个数组,用来存放被勾选的auth的id
    var authIdArray = [];

    // 拿到zTreeObj
    var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo");

    // 通过getCheckedNodes方法拿到被选中的option信息
    var authArray = zTreeObj.getCheckedNodes();

    for (var i = 0; i < authArray.length; i++) {
        // 从被选中的auth中遍历得到每一个auth的id
        var authId = authArray[i].id;
        // 通过push方法将得到的id存入authIdArray
        authIdArray.push(authId);
    }
    var requestBody = {
        // 为了后端取值方便,两个数据都用数组格式存放,后端统一用List<Integer>获取
        "roleId":[window.roleId],
        "authIdList":authIdArray
    }
    requestBody = JSON.stringify(requestBody);

    $.ajax({
        url: "assign/do/save/role/auth/relationship.json",
        type: "post",
        data: requestBody,
        contentType: "application/json;charset=UTF-8",
        dataType: "json",
        success: function (response) {
            if (response.result == "SUCCESS"){
                layer.msg("操作成功!");
            }
            if (response.result == "FAILED"){
                layer.msg("操作失败!提示信息:"+ response.message);
            }
        },
        error: function (response) {
            layer.msg(response.status + "  " + response.statusText);
        }
    });

    // 关闭模态框
    $("#assignModal").modal("hide");
});

2.Handler层代码

@ResponseBody
@RequestMapping("/assign/do/save/role/auth/relationship.json")
public ResultEntity<String> saveRoleAuthRelationship(
        // 用一个map接收前端发来的数据
        @RequestBody Map<String,List<Integer>> map ) {
    // 保存更改后的Role与Auth关系
    authService.saveRoleAuthRelationship(map);

    return ResultEntity.successWithoutData();
}

3.Service层方法

@Override
public void saveRoleAuthRelationship(Map<String, List<Integer>> map) {
    // 从map获取到roleId、authIdList
    List<Integer> roleIdList = map.get("roleId");
    Integer roleId = roleIdList.get(0);

    List<Integer> authIdList = map.get("authIdList");

    // 1 清除原有的关系信息
    authMapper.deleteOldRelationshipByRoleId(roleId);


    // 2 当authIdList有效时,添加前端获取的新的关系信息
    if (authIdList != null && authIdList.size() > 0){
        authMapper.insertNewRelationship(roleId,authIdList);
    }
}

4. Mapper接口

void deleteOldRelationshipByRoleId(Integer roleId);

void insertNewRelationship(@Param("roleId") Integer roleId, @Param("authIdList") List<Integer> authIdList);

5.authMapper.xml文件的sql代码

<!--通过roleId删除inner_role_auth表中旧的关系-->
<delete id="deleteOldRelationshipByRoleId">
  delete from inner_role_auth where
  role_id = #{roleId}
</delete>

<!--向inner_role_auth表中插入新的关系-->
<insert id="insertNewRelationship">
  insert into inner_role_auth(role_id, auth_id) values
  <foreach collection="authIdList" item="authId" separator=",">
    (#{roleId},#{authId})
  </foreach>
</insert>