day56_BOS项目_08
- 今天内容安排:
- 1、权限管理(初始化、查询、添加)
- 2、角色管理(添加、查询)
- 3、用户管理(添加、查询)
- 4、修改自定义BOSRealm中的授权方法(基于数据库实现)
- 5、使用ehcache 缓存权限数据
- 6、系统的左侧菜单根据当前登录用户的权限动态展示
1、权限管理(初始化、查询、添加)
注意1:权限数据属于比较特殊的数据,系统在上线之后,必须先把权限数据给它初始化到数据库中去,然后这个系统才可以跑起来。如果不初始化权限数据的话,那么登录上系统之后,会发现一个菜单也没有,什么也不能干。所以说,所有的系统在上线的时候都会进行权限数据的初始化。
注意2:我们的初始化文件数据一般都会整理成一个sql脚本文件,系统上线之后,首先去数据库中去执行这个sql脚本文件,执行完之后,我们的数据库中就有数据了,然后整个系统才能正常运行。即:系统的正常运行是要依赖一些基础数据的
。
1.1、初始化权限数据
第一步:执行sql脚本文件auth_function.sql初始化权限数据
1.2、权限的分页查询
文件位置:/bos19/WebContent/WEB-INF/pages/admin/function.jsp
第一步:修改页面中datagrid的URL地址,访问FunctionAction的pageQuery()的分页查询方法,并加入分页条
第二步:创建FunctionAction类,提供pageQuery()的分页查询方法
package com.itheima.bos.web.action;
import java.io.IOException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import com.itheima.bos.domain.Function;
import com.itheima.bos.web.action.base.BaseAction;
/**
* 权限管理
* @author Bruce
*
*/
@Controller
@Scope("prototype")
public class FunctionAction extends BaseAction<Function> {
/**
* 权限的分页查询
* @return
* @throws IOException
*/
public String pageQuery() throws IOException {
functionService.pageQuery(pageBean);
String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"};
this.writePageBean2Json(pageBean, excludes);
return "none";
}
}
第三步:配置struts.xml
<!-- 权限管理:配置functionAction-->
<action name="functionAction_*" class="functionAction" method="{1}">
</action>
解决分页查询问题:
问题:分页对象pageBean中有一个page属性,模型对象Function中也有一个page属性,struts框架会把页面提交过来的参数(是字符串)优先给模型对象中的page(是字符串)设置值
,BaseAction中的page属性(是int类型)就赋值不成功,一直是默认值0。
说明数据库的表设计是有问题的。方式一
:修改数据库中权限表的字段名称和对应的权限类中的属性以及对应的映射文件。(推荐使用此方法,需要修改数据库表)方式二
:修改权限类Function.java中的属性page名称为新名称,再去修改映射文件Function.hbm.xml,让新名称依旧对应数据库权限表中page字段。(此方法也可以,不需要修改数据库表)方式三
:从model对象(Function)中获取page注入到pageBean对象中。(不推荐使用,但是本案例这样使用)
修改权限的分页查询代码,如下所示:
/**
* 权限的分页查询
* @return
* @throws IOException
*/
public String pageQuery() throws IOException {
// 从model对象中获取page注入到pageBean对象中
String page = model.getPage();
pageBean.setCurrentPage(Integer.parseInt(page));
functionService.pageQuery(pageBean);
String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"};
this.writePageBean2Json(pageBean, excludes);
return "none";
}
1.3、权限的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/function_add.jsp
第一步:修改添加页面中的combobox的URL地址,查询所有的权限,并展示到下拉框中
<tr>
<td>父功能点</td>
<td>
<input name="function.id" class="easyui-combobox"
data-options="valueField:'id',textField:'name',
url:'${pageContext.request.contextPath}/functionAction_listajax.action'"/>
</td>
</tr>
第二步:在FunctionAction中提供listajax()方法
/**
* 添加权限
* @return
*/
public String add() {
functionService.save(model);
return "list";
}
浏览器效果如下图所示:
第三步:修改combobox的name属性,为了便于封装数据,这样父功能点对应的的id的值就可以封装进Function里去了。
第四步:为保存按钮绑定事件提交表单
<script type="text/javascript">
$(function(){
// 点击保存
$('#save').click(function() {
// 对form表单进行校验,并提交
if ($('#functionForm').form('validate')) {
$('#functionForm').submit();
}
});
});
</script>
......
<form id="functionForm" method="post" action="${pageContext.request.contextPath}/functionAction_add.action">
浏览器效果如下图所示:
第五步:在Action中提供add()方法,保存一个权限数据
/**
* 添加权限
* @return
*/
public String add() {
functionService.save(model);
return "list";
}
Service层代码:
public void save(Function model) {
Function function = model.getFunction();
// 注意:String类型作为主键时,容易出现空串的情况,我们需要排除。
if (function != null && function.getId().equals("")) {
// 没有上级,说明是顶级目录
model.setFunction(null);
}
functionDao.save(model);
}
2、角色管理(添加、查询)
2.1、角色的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/role_add.jsp
第一步:使用ztree展示权限树,启用ztree的勾选效果
// 授权树初始化
var setting = {
data : {
key : {
title : "t"
},
simpleData : { // 启用简单json数据描述节点数据
enable : true
}
},
check : { // 启用ztree的勾选效果
enable : true,
}
};
第二步:页面上的数据要来自数据库,所以我们需要修改ajax方法的URL地址,访问Action,查询所有的权限数据,并返回简单json数据作为ztree的节点数据
// 发送ajax请求获取菜单数据构造ztree
// 若为“text”文本数据,需要转成json数据才可以使用
$.ajax({
url : '${pageContext.request.contextPath}/functionAction_listajax.action',
type : 'POST',
dataType : 'text',
success : function(data) {
var zNodes = eval("(" + data + ")");
$.fn.zTree.init($("#functionTree"), setting, zNodes);
},
error : function(msg) {
alert('树加载异常!');
}
});
// 发送ajax请求获取菜单数据构造ztree
// 若为“json”数据,则不需要转换,直接使用即可
$.ajax({
url : '${pageContext.request.contextPath}/functionAction_listajax.action',
type : 'POST',
dataType : 'json',
success : function(data) {
$.fn.zTree.init($("#functionTree"), setting, json);
},
error : function(msg) {
alert('树加载异常!');
}
});
注意:我们发现浏览器页面上展示的数据没有正常显示出来上下级关系,如下图所示:
为什么呢?答:这是由于响应的json数据中,没有键pId以及对应的值,那么我们就需要在实体类Function.java中临时添加一个getter()方法(我们姑且可以把它看作为临时的属性吧),示例代码如下:
// 响应json数据之前,我们先进行序列化,而序列化,找的就是实体类中的getter()方法
public String getpId() {
if (function != null) {
return function.getId();
} else {
return "0"; // 参考/bos19/WebContent/json/menu.json 中json的写法
}
}
增加后,浏览器页面的显示为:
第三步:为保存
按钮绑定事件,提交表单,发现ztree选中的节点没有提交,为什么呢?答:因为显示在页面上仅仅只是ztree的页面效果
而已,不是真正的表单控件<input type="checkbox">
。如何解决呢?答:我们需要使用ztree提供的API获得当前选中的节点,赋值给指定的隐藏域。
// 点击保存按钮
$('#save').click(function(){
// location.href='${pageContext.request.contextPath}/page_admin_privilege.action';
var v = $("#roleForm").form("validate");
if (v) {
var treeObj = $.fn.zTree.getZTreeObj("functionTree"); // 获得页面中的ztree对象
var nodes = treeObj.getCheckedNodes(true); // 在提交表单之前将选中的checkbox收集
var array = new Array();
for (var i = 0; i < nodes.length; i++) {
array.push(nodes[i].id);
}
var functionIds = array.join(","); // 11,112,113,12,121,13,131
$("input[name=functionIds]").val(functionIds); // 赋值给指定的隐藏域
$("#roleForm").submit();
}
});
......
<form id="roleForm" method="post" action="${pageContext.request.contextPath}/roleAction_add.action">
<!-- 设置隐藏域 -->
<input type="hidden" name="functionIds">
浏览器效果如下图所示:
第四步:创建RoleAction,提供add()方法
package com.itheima.bos.web.action;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import com.itheima.bos.domain.Role;
import com.itheima.bos.web.action.base.BaseAction;
/**
* 角色设置
* @author Bruce
*
*/
@Controller
@Scope("prototype")
public class RoleAction extends BaseAction<Role> {
// 采用属性驱动的方式,接收浏览器页面传过来的functionIds
private String functionIds;
public void setFunctionIds(String functionIds) {
this.functionIds = functionIds;
}
/**
* 添加角色
* @return
*/
public String add() {
roleService.save(model, functionIds);
return "list";
}
}
Service层代码:
package com.itheima.bos.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.itheima.bos.dao.IFunctionDao;
import com.itheima.bos.dao.IRoleDao;
import com.itheima.bos.domain.Function;
import com.itheima.bos.domain.Role;
import com.itheima.bos.service.IRoleService;
@Service
@Transactional
public class RoleServiceImpl implements IRoleService {
// 注入角色dao
@Autowired
private IRoleDao roleDao;
// 注入权限dao
@Autowired
private IFunctionDao functionDao;
public void save(Role model, String functionIds) { // 11,112,113,12,121,13,131
roleDao.save(model); // 使Role变成持久化对象
// 角色跟权限是多对多关系,我们现在要建立关联,对于多对多关系,谁关联谁都可以,但是我们要有意识的去查看下Hibernate映射文件,看看谁放弃了维护外键的权利
// 通过查看映射文件可知:角色关联权限
String[] ids = functionIds.split(",");
for (String fid : ids) {
// 方式一:自己去new(即构造)一个Function对象,效率高,因为我们关联是真正靠fid进行关联的
Function function = new Function(fid); // 托管态对象:离线对象
model.getFunctions().add(function);
// 方式二:根据权限id把权限对象查询出来,再让角色对象model去关联权限对象Function,需要发sql语句,效率低一些
// Function function = functionDao.findById(fid); // 持久化对象
// model.getFunctions().add(function); // 关联完之后,事务提交之后,Hibernate框架会给数据库发sql,会自动更新数据库,即根据快照去对比,看看我们取出来的持久化对象是否跟快照长得不一样,若不一样,就刷新缓存。
}
}
}
第五步:配置struts.xml
<!-- 角色管理:配置roleAction-->
<action name="roleAction_*" class="roleAction" method="{1}">
<result name="list">/WEB-INF/pages/admin/role.jsp</result>
</action>
2.2、角色的分页查询
文件位置:/bos19/WebContent/WEB-INF/pages/admin/role.jsp
方法步骤同权限的分页查询,不在赘述!
3、用户管理(添加、查询)
3.1、用户的分页查询
文件位置:/bos19/WebContent/WEB-INF/pages/admin/userlist.jsp
方法步骤同权限的分页查询,不在赘述!
3.2、用户的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/userinfo.jsp
第一步:发送ajax请求,从数据库中获取所有的角色数据,返回json数据,在浏览器页面中动态构造到checkbox中
<tr>
<td>选择角色:</td>
<td colspan="3">
<script type="text/javascript">
$(function() {
// 发送ajax请求,从数据库中获取所有的角色数据,返回json数据,在浏览器页面中动态构造到checkbox中
var url = "${pageContext.request.contextPath}/roleAction_listajax.action";
$post(url,{},function(data) {
// 回调函数,解析返回的json数据,动态构造checkbox对象
// ......
},'json');
});
</script>
<!-- 示例伪代码 -->
<input type="checkbox" name="roleIds" value="roleId">roleName
<input type="checkbox" name="roleId">管理员</br>
<input type="checkbox" name="roleId">收派员</br>
<input type="checkbox" name="roleId">用户(业务员)</br>
</td>
</tr>
浏览器debug调试结果为:
第二步:在RoleAction中提供listajax()方法
/**
* 查询所有角色
* @return
* @throws IOException
*/
public String listajax() throws IOException {
List<Role> list = roleService.findAll();
String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "users", "functions"};
this.writeList2Json(list, excludes);
return "none";
}
第三步:完善ajax方法的回调函数
<tr>
<td>选择角色:</td>
<td colspan="3">
<script type="text/javascript">
$(function() {
// 发送ajax请求,从数据库中获取所有的角色数据,返回json数据,在浏览器页面中动态构造到checkbox中
var url = "${pageContext.request.contextPath}/roleAction_listajax.action";
$post(url,{},function(data) {
// 回调函数,解析返回的json数据,动态构造checkbox对象
for (var i = 0; i < data.length; i++) {
var roleId = data[i].id;
var roleName = data[i].name;
$("#roleTD").append('<input type="checkbox" value="' + roleId + '" name="roleIds">' + roleName);
}
}, 'json'); // 最后一个参数“json”可以不用写,因为在BaseAction中,服务器向浏览器写数据的时候已经setContentType中设置过了。
});
</script>
</td>
</tr>
浏览器效果如下图所示:
第四步:为保存
按钮绑定事件,并提交表单
浏览器效果如下图所示:
第五步:在UserAction中提供add()方法,保存用户
/**
* 保存用户
* @return
*/
public String add() {
userServie.save(model, roleIds);
return "list";
}
Service层:
public void save(User model, String[] roleIds) {
// 将user持久化之前,要先将用户密码进行加密
String password = model.getPassword();
password = MD5Utils.md5(password);
model.setPassword(password);
userDao.save(model); // 将User持久化
// 用户跟角色是多对多关系,我们现在要建立关联,对于多对多关系,谁关联谁都可以,但是我们要有意识的去查看下Hibernate映射文件,看看谁放弃了维护外键的权利
// 通过查看映射文件可知:用户 关联 角色
for (String roleId : roleIds) {
// 用户 关联 角色
// 方式一:自己去new(即构造)一个Role对象,效率高,因为我们关联是真正靠roleId进行关联的
Role role = new Role(roleId);
model.getRoles().add(role);
// 方式二:根据角色id把角色对象查询出来,再让用户对象model去关联角色对象Role,需要发sql语句,效率低一些
// Role role = roelDao.findById(roleId); // 持久化对象
// model.getRoles().add(role); // 关联完之后,事务提交之后,Hibernate框架会给数据库发sql,会自动更新数据库,即根据快照去对比,看看我们取出来的持久化对象是否跟快照长得不一样,若不一样,就刷新缓存。
}
}
第六步:配置struts.xml,做页面跳转
<!-- 用户管理:配置userAction-->
<action name="userAction_*" class="userAction" method="{1}">
<!-- <result name="login">/login.jsp</result> -->
<result name="home">/index.jsp</result>
<result name="list">/WEB-INF/pages/admin/userlist.jsp</result>
</action>
第七步:在User类中提供getRoleNames()方法和getFormatBirthday()方法,使得服务器返回给浏览器的json数据中含有roleNames字段和formateBrithday字段
第八步:修改userlist.jsp页面中对应的field字段名称
浏览器运行结果:
4、修改自定义BOSRealm中的授权方法(基于数据库实现)
/**
* 授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 创建SimpleAuthenticationInfo简单授权信息对象,使用没有参数的构造方法
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 为当前用户授予staff权限(硬编码)--> perms["staff"]
// info.addStringPermission("staff");
// 为当前用户授予staff角色(硬编码)--> roles["staff"]
// info.addRole("staff");
// TODO 根据当前登录用户查询数据库,获取其对应的权限数据,再进行授权(软编码)
// 获取当前登录用户对象
User user = (User) principals.getPrimaryPrincipal();
List<Function> list = null;
if (user.getUsername().equals("admin")) {
// 说明是超级管理员用户,则需要查询到所有权限,给其授权
list = functionDao.findAll();
} else {
// 说明是普通用户,则需要根据用户id查询对应的权限
list = functionDao.findListByUserId(user.getId());
}
// 授权
for (Function function : list) {
info.addStringPermission(function.getCode());
}
return info;
}
在FunctionDao中提供根据用户id查询权限的方法:
/**
* 根据用户id查询对应的权限
*
* 我们已经知道,用户跟权限没有直接对应关系,但是可以通过权限找到角色,再通过角色找到用户
*/
public List<Function> findListByUserId(String userId) {
// 左外连接查询
String hql = "select distinct f from Function f left outer join f.roles r left outer join r.users u where u.id=?";
return this.getHibernateTemplate().find(hql, userId);
}
5、使用ehcache 缓存权限数据
第一步:导入ehcache的jar包到项目中
第二步:提供ehcache的xml配置文件(可以从其jar包中获得),删除掉其中的注释
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
第三步:在spring配置文件中注册一个缓存管理器,并注入给安全管理器
6、系统左侧菜单根据当前登录用户的权限动态展示
第一步:修改index.jsp页面中ajax方法的URL
// 基本功能菜单加载
$.ajax({
url : '${pageContext.request.contextPath}/functionAction_findMenu.action',
type : 'POST',
// dataType : 'text',
dataType : 'json',
success : function(data) {
// var zNodes = eval("(" + data + ")");
$.fn.zTree.init($("#treeMenu"), setting, data);
},
error : function(msg) {
alert('菜单加载异常!');
}
});
第二步:在FunctionAction中提供findMenu()方法
/**
* 根据当前登录用户查询对应的菜单数据(从权限表中查询)
* @return
* @throws IOException
*/
public String findMenu() throws IOException {
List<Function> list = functionService.findMenu();
String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"};
this.writeList2Json(list, excludes);
return "none";
}
第三步:在FunctionService中提供方法
public List<Function> findMenu() {
User user = BOSContext.getLoginUser();
List<Function> list = null;
if (user.getUsername().equals("admin")) {
// 说明是超级管理员
list = functionDao.findAllMenu();
} else {
// 说明是普通用户
list = functionDao.findMenuById(user.getId());
}
return list;
}
第四步:在FunctionDao中扩展方法
/**
* 根据用户id查询对应的权限,再查询对应的菜单并按照优先级排序
*/
public List<Function> findMenuById(String userId) {
String hql = "select distinct f from Function f left outer join f.roles r left outer join r.users u where u.id=? and f.generatemenu='1' order by f.zindex";
return this.getHibernateTemplate().find(hql, userId);
}