1. 权限控制
权限控制机制的本质就是“用钥匙开锁”。
2. 给 Admin 分配 Role
2.1 目标
通过页面操作把 Admin 和 Role 之间的关联关系保存到数据库。
2.2 思路
2.3 代码:前往分配页面
2.3.1 创建保存 Admin-Role 关联关系的数据库表
CREATE TABLE `project_crowd`.`inner_admin_role` (
`id` INT NOT NULL AUTO_INCREMENT,
`admin_id` INT, `role_id` INT,
PRIMARY KEY (`id`) );
这个表并不对应现实生活中或项目业务功能中的一个具体实体,所以没有对应
的实体类,也不通过逆向工程做逆向生成。
2.3.2 修改“分配”按钮
<%--<button type="button" class="btn btn-success btn-xs"><i class=" glyphicon glyphicon-check"></i></button>--%>
<a href="assign/to/assign/role/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>
2.3.3 创建 AssignHandler
@Controller
public class AssignHandler {
@Autowired
private AdminService adminService;
@Autowired
private RoleService roleService;
@Autowired
private AuthService authService;
@RequestMapping("/assign/to/assign/role/page.html")
public String toAssignRolePage(
@RequestParam("adminId") Integer adminId,
ModelMap modelMap
){
// 1.查询已分配角色
List<Role> assignedRoleList = roleService.getAssignedRole(adminId);
// 2.查询未分配角色
List<Role> unAssignedRoleList =roleService.getUnAssignedRole(adminId);
// 3.存入模型(本质上其实是:request.setAttribute("attrName,attrValue);)
modelMap.addAttribute("assignedRoleList", assignedRoleList);
modelMap.addAttribute("unAssignedRoleList",unAssignedRoleList);
return "assign-role";
}
}
2.3.4 RoleService 中的方法
@Override
public List<Role> getUnAssignedRole(Integer adminId) {
return roleMapper.selectUnAssignedRole(adminId);
}
2.3.5 SQL 语句
<select id="selectAssignedRole" resultMap="BaseResultMap">
select id,name from t_role where id in (select role_id from inner_admin_role where admin_id=#{adminId})
</select>
<select id="selectUnAssignedRole" resultMap="BaseResultMap">
select id,name from t_role where id not in (select role_id from inner_admin_role where admin_id=#{adminId})
</select>
2.3.6 在页面上显示角色数据
对 option 标签进行说明:
<option value="将来在提交表单时一起发送给 handler 的值">在浏览器上让用户看到的数据</option>
实际显示角色信息时:
<option value="角色的 id">角色的名称</option>
举例:
<option value="5">市场部经理</option>
页面具体代码:
<div class="form-group" style="margin-left:40px;">
<label for="exampleInputPassword1">已分配角色列表</label><br>
<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>
2.3.7 调整表单让表单能够提交数据
参考下面文件:
2.3.8 jQuery 代码
2.4 代码:执行分配
$(function () {
$("#toRightBtn").click(function () {
// select是标签选择器
// :eq(0)表示选择页面上的第一个
// :eq(1)表示选择页面上的第二个
// ">"表示选择子元素
// :selected表示选择"被选中的"option
// appendTo()能够将jQuery对象追加到指定位置
$("select:eq(0)>option:selected").appendTo("select:eq(1)");
});
$("#toLeftBtn").click(function () {
$("select:eq(1)>option:selected").appendTo("select:eq(0)");
});
});
2.4.1 handler 方法
@RequestMapping("/assign/do/role/assign.html")
public String saveAdminRoleRelationship(
@RequestParam("adminId") Integer adminId,
@RequestParam("pageNum") Integer pageNum,
@RequestParam("keyword") String keyword,
// 我们允许用户在页面上取消所有已分配角色再提交表单,所以可以不提供roleIdList请求参数
// 设置required = false表示这个请求参数不是必须的
@RequestParam(value = "roleIdList", required = false) List<Integer> roleIdList
){
adminService.saveAdminRoleRelationship(adminId, roleIdList);
return "redirect:/admin/get/page.html?pageNum="+pageNum+"&keyword="+keyword;
}
2.4.2 Service 方法
@Override
public void saveAdminRoleRelationship(Integer adminId, List<Integer> roleIdList) {
// 旧的数据如下:
// adminId roleId
// 1 1(要删除)
// 1 2(要删除)
// 1 3
// 1 4
// 1 5
// 新数据如下:
// adminId roleId
// 1 3(本来就有)
// 1 4(本来就有)
// 1 5(本来就有)
// 1 6(新)
// 1 7(新)
// 为了简化操作:先根据adminId删除旧的数据,再根据roleIdList保存全部新的数据
// 1.根据adminId删除旧的关联关系数据
adminMapper.deleteOLdRelationship(adminId);
// 2.根据roleIdList和adminId保存新的关联关系
if (roleIdList != null && roleIdList.size() > 0){
adminMapper.insertNewRelationship(adminId,roleIdList);
}
}
2.4.3 SQL 语句
<delete id="deleteOLdRelationship">
delete from inner_admin_role where admin_id=#{admin}
</delete>
<insert id="insertNewRelationship">
insert into inner_admin_role(admin_id,role_id) values
<foreach collection="roleIdList" item="roleId" separator=",">(#{adminId},#{roleId})</foreach>
</insert>
2.4.4 修正 Bug
$("#submitBtn").click(function () {
// z爱提交表单前把"已分配"部分的option全部选中
$("select:eq(1)>option").prop("selected","selected");
/*// 为了看到上面代码的效果,暂时不让表单提交
return false;*/
});
3. 给 Role 分配 Auth
3.1 目标
把角色和权限的关联关系保存到数据库。
3.2 思路
3.3 代码: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`)
);
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 字段的值来进行比对。建议使用英文。
title 字段:在页面上显示,让用户便于查看的值。建议使用中文。
category_id 字段:关联到当前权限所属的分类。这个关联不是到其他表关联,而是
就在当前表内部进行关联,关联其他记录。所以说,t_auth 表中是依靠 category_id 字
段建立了“节点”之间的父子关系。
name 字段中值的格式:中间的“:”没有任何特殊含义。不论是我们自己写的代码
还是将来使用的框架都不会解析“:”。如果不用“:”,用“%、@、&、*、-”等等这样
的符号也都是可以的。
模块:操作名
user:delete
user:get
role:delete
……
3.3.1 建表
+有(空)参+toString
归位
3.4 代码:打开模态框
3.4.1 准备模态框
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<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">×</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>
<%@include file="/WEB-INF/modal-role-assign-auth.jsp" %>
3.4.2 给“✔”按钮绑定单击响应函数
在 fillTableBody()函数中,修改 checkBtn
var checkBtn = "<button id='"+roleId+ "' type='button' class='btn btn-success btn-xs checkBtn'><i class=' glyphicon glyphicon-check'></i></button>";
// 13.给分配权限按钮绑定单击响应函数
$("#rolePageBody").on("click",".checkBtn",function () {
// 把当前角色id存入全局变量
window.roleId = this.id;
// 打开模态框
$("#assignModal").modal("show");
// 在模态框中装载树 Auth 的形结构数据
fillAuthTree();
});
3.4.3 加入 zTree 的环境
注意:引入的时候 zTree 的 JavaScript 库文件要放在 my-role.js 的前面
3.4.4 显示不正常分析
3.4.5 函数 fillAuthTree()
// 声明专门的函数用来在分配Auth的模态框中显示Auth的树形结构数据
function fillAuthTree() {
// 1.发送Ajax请求查询Auth数据
var ajaxReturn = $.ajax({
"url": "assgin/get/all/auth.json",
"type": "post",
"dataType": "json",
"async": false
});
if (ajaxReturn.status != 200){
layer.msg("请求处理出错!响应状态码是:"+ajaxReturn.status+"说明是:"+ajaxReturn.statusText);
return ;
}
// 2.从响应结果中获取Auth的JSON数据
// 从服务器端查询到的list不需要组装成树形结构,这里我们交给zTree去组装
var authList = ajaxReturn.responseJSON.data;
// 3.准备队zTree进行设置的JSON对象
var setting = {
"data": {
"simpleData":{
// 开启简单JSON功能
"enable": true,
// 使用categoryId属性关联父节点,不用默认的pId
"pIdKey": "categoryId"
},
"key": {
// 使用title属性显示节点名称,不用默认的name作为属性名了
"name": "title"
}
},
"check": {
"enable": true
}
};
// 4.生成树形结构
$.fn.zTree.init($("#authTreeDemo"),setting,authList);
// 获取zTreeObj对象
var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo");
// 调用zTreeObj对象的方法,把节点展开
zTreeObj.expandAll(true);
// 5.查询已分配的Auth的id组成的List
ajaxReturn = $.ajax({
"url": "assign/get/assigned/auth/id/bu/role/id.json",
"type": "post",
"data":{
"roleId": window.roleId
},
"dataType":"json",
"async": false
});
if (ajaxReturn.status != 200){
layer.msg("请求处理出错!响应状态码是:"+ajaxReturn.status+"说明是:"+ajaxReturn.statusText);
return ;
}
// 从响应结果中获取authIdArray
var authIdArray = ajaxReturn.responseJSON.data;
// 6.根据authIdArray把树形结构中对应的节点勾选上
// 6.1 遍历authIdArray
for (var i = 0; i < authIdArray.length ; i++) {
var authId = authIdArray[i];
// 6.2 根据id查询树形结构中对应的节点
var treeNode =zTreeObj.getNodeByParam("id", authId);
// 6.3 将treeNode设置位被勾选
// checked设置为true表示节点勾选
var checked = true;
// checkTypeFlag设置为false,表示不"联动",不联动是为了避免把不该勾选的勾选上
var checkTypeFlag = false;
// 执行
zTreeObj.checkNode(treeNode, checked, checkTypeFlag);
}
}
3.4.6 创建角色到权限之间关联关系的中间表
CREATE TABLE `project_crowd`.`inner_role_auth` (
`id` INT NOT NULL AUTO_INCREMENT,
`role_id` INT, `auth_id` INT,
PRIMARY KEY (`id`) );
3.4.7 根据 role_id 查询 auth_id
@ResponseBody
@RequestMapping("/assign/get/assigned/auth/id/bu/role/id.json")
public ResultEntity<List<Integer>> getAssignedAuthIdByRoleId(
@RequestParam("roleId") Integer roleId
){
List<Integer> authIdList = authService.getAssignedAuthIdByRoleId(roleId);
return ResultEntity.successWithData(authIdList);
}
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthMapper authMapper;
@Override
public List<Auth> getAll() {
return authMapper.selectByExample(new AuthExample());
}
@Override
public List<Integer> getAssignedAuthIdByRoleId(Integer roleId) {
return authMapper.selectAssignedAuthIdByRoleId(roleId);
}
}
<select id="selectAssignedAuthIdByRoleId" resultType="int">
select auth_id from inner_role_auth where role_id=#{roleId}
</select>
3.5 代码:执行分配
3.5.1 给“分配”按钮绑定单击响应函数
// 14.给分配全选模态框中的“分配”按钮绑定单击响应函数
$("#assignBtn").click(function () {
// 14.1 收集树形结构的各个几点中被勾选的节点
// [1]声明一个专门的数组存放id
var authIdArray = [];
// [2]获取zTreeObj对象
var zTreeObj = $.fn.zTree.getZTreeObj("authTreeDemo");
// [3]获取全部被勾选的节点
var checkedNodes = zTreeObj.getCheckedNodes();
// [4]遍历checkedNodes
for (var i = 0; i < checkedNodes.length; i++) {
var checkedNode = checkedNodes[i];
var authId = checkedNode.id;
authIdArray.push(authId);
}
// 14.2发送请求执行分配
var requestBody = {
"authIdArray": authIdArray,
// 为了服务器端handler方法能够统一使用List<Integer>方式接收数据,roleId也存入数组
"roleId": [window.roleId]
};
requestBody = JSON.stringify(requestBody);
$.ajax({
"url": "assign/do/role/assign/auth.json",
"type": "post",
"data": requestBody,
"contentType": "application/json;charset=UTF-8",
"dataType": "json",
"success":function (response) {
var result = response.result;
if (result == "SUCCESS"){
layer.msg("操作成功!");
}
if (result == "FAILED"){
layer.msg("操作失败!"+response.message);
}
},
"error":function (response) {
layer.msg(response.status+ " "+response.statusText);
}
});
$("#assignModal").modal("hide");
});
3.5.2 后端执行分配,保存关联关系
@ResponseBody
@RequestMapping("/assign/do/role/assign/auth.json")
public ResultEntity<String> saveRoleAuthRelathinship(
@RequestBody Map<String, List<Integer>> map
){
authService.saveRoleAuthRelathinship(map);
return ResultEntity.successWithoutData();
}
@Override
public void saveRoleAuthRelathinship(Map<String, List<Integer>> map) {
// 1.获取roleId的值
List<Integer> roleIdList = map.get("roleId");
Integer roleId = roleIdList.get(0);
// 2.删除旧的关联关系数据
authMapper.deleteOldRelathinship(roleId);
// 3.获取authIdList
List<Integer> authIdList = map.get("authIdArray");
// 4.判断authIdList是否有效
if (authIdList != null && authIdList.size() > 0){
authMapper.insertNewRelathinship(roleId, authIdList);
}
}
<select id="deleteOldRelathinship">
delete from inner_role_auth where role_id=#{roleId}
</select>
<insert id="insertNewRelathinship">
insert into inner_role_auth(auth_id,role_id) values
<foreach collection="authIdList" item="authId" separator=",">(#{authId},#{roleId})</foreach>
</insert>