8 BOS项目第08天
对权限涉及的5张表,进行CRUD的操作
8.1 初始化权限数据
-
权限功能需要一张权限表记录数据
-
重要字段
- page:访问路径
- generatemenu:是否生成菜单
- zindex:优先级,实现排序
- pid:父级目录的id
打开
auth_function.sql
,复制到Navicat
8.2 功能权限管理列表
8.2.1 数据列表展示
- 写Dao,Service,Action,Jsp代码
- model中的是generatemenu,需要修改jsp中对应的字段
generateMenu - url改为:
${pageContext.request.contextPath}/functionAction_pageQuery.action
- 加上分页代码:
- model中的是generatemenu,需要修改jsp中对应的字段
pageList:[5,10,15],
pageSize:10,
pagination:true,
- exclude三个属性
- 排除Function模型中两个会造成循环引用的属性:
"function", "functions"
- 权限与角色是多对多关系,
权限表
不用直接显示角色属性,所以要exclude属性roles
- 排除Function模型中两个会造成循环引用的属性:
- 解决分页的Bug
-
因为Function和BaseAction都有参数page,page参数会优先传递给Model,分页无法获取参数,一直默认为第一页.解决方案就是手动给PageBean赋值
-
8.2.2 功能权限信息添加
- 把jsp中的id换成关键字,id不需要自己输入
- 是否生成关菜单,用select表示,1用生成表示,0用不生成表示,注意,这里的
generateMenu也要改成小写
{
field : 'generatemenu',
title : '是否生成菜单',
formatter : function(data, row, index){
if(data=="1"){
return "生成";
}else{
return "不生成";
}
},
width : 200
},
父功能点:parentFunction.id改成function.id,要与模型对应
-
实现save业务链
- Jsp:在表单上提供action属性:
${pageContext.request.contextPath}/functionAction_save.action
- save按钮的点击事件:
$("#functionForm").submit();
- Jsp:在表单上提供action属性:
-
显示父功能点,后台返回Json,textField展示name
- JSP部分
<input name="function.id" class="easyui-combobox" data-options="valueField:'id',textField:'name',url:'${pageContext.request.contextPath}/functionAction_listJson.action'"/>
- Action部分
- JSP部分
public void listJson(){
List<Function> functions = functionService.findAll();
responseJson(functions, new String[]{"function", "functions"});
}
- 是否生成
8.3 角色管理
8.3.1 添加角色功能
8.3.2 修改授权数据的来源
- 分析授权数据的获取源码
- 本质就是将
基础功能
的树,再次显示 - 从
权限表
获取数据:${pageContext.request.contextPath}/functionAction_listJson.action
- 后台返回的json,要提供pId属性
- Fuction代码
- 本质就是将
private String pId;
public String getpId() {
if(function != null){
return function.getId();
}
return "0";
}
- 获取授权数据,勾选的所有id
var treeObj = $.fn.zTree.getZTreeObj("functionTree");
var nodes = treeObj.getCheckedNodes(true);
var ids = new Array();
for(var i = 0; i < nodes.length; i++){
var id = nodes[i].id;
ids.push(id);
}
//拼接所有id
var idsStr = ids.join(",");
8.3.4 数据提交与后台处理
- 表单提交
- JSP将显示id换成关键字
- 添加一个隐藏的input,名字为ids,用于提交参数
- URL:
"${pageContext.request.contextPath}/roleAction_save.action"
- 后台处理
- DAO,Service
- 添加一个RoleAction来处理表单提交
- 中间表由Hibernate自动维护,只需写好映射即可
@Override
public void save(Role role, String functionIds) {
//1.保存角色
roleDao.save(role);
//2.添加角色,权限,中间表ids
//2.1拆分id
String[] functionIdsArr = functionIds.split(",");
for (String functionId : functionIdsArr) {
//把id封装成Function模型
Function function = new Function();
function.setId(functionId);
//把function存在Role里面去
role.getFunctions().add(function);//内部执行insert语句
}
}
8.3.5 角色管理列表实现
- pageQuery方法,简单实现
url : '${pageContext.request.contextPath}/roleAction_pageQuery.action?page=1&rows=20',
8.4 用户管理模块
8.4.1 显示用户列表数据
- UserAction中实现分页查询
- 几个注意点:
- 之前的测试账户admin,要删掉,因为工资,生日等数据都为空,会造成转JSON异常
- Data是util包下的,而不是sql包下的
- User模型提供一个getBirthdayStr方法,对应birthdayStr属性,便于转换日期格式
- remark字段不显示,所以也应该exclude,roles也要exclude
8.4.2 添加用户
-
效果图
首先需要获得角色数据,Action代码
public void listJson() throws IOException {
List<Role> roles = roleService.findAll();
responseJson(roles, new String[]{"users", "functions"});
}
- JS动态添加checkbox
//获取角色数据
var url = "${pageContext.request.contextPath}/roleAction_listJson.action";
$.post(url, function (data) {
for (var i = 0;i<data.length;i++) {
var name = data[i].name;
var id = data[i].id;
var inputTag = '<input type="checkbox" value="'+id+'" name="roleIds">' + name;
$("#roleTd").append(inputTag);
}
});
- 查看表单请求参数
- userAction的添加方法实现
- userService的实现
@Override
public void save(User model, String[] roleIds) {
//密码使用MD5
String pwd = MD5Utils.text2md5(model.getPassword());
model.setPassword(pwd);
userDao.save(model);
for (String roleId : roleIds) {
Role role = new Role();
role.setId(roleId);
model.getRoles().add(role);
}
}
- 用户列表中生日的显示,util包中Date格式的日期返回的是
默认返回的生日数据格式如图
{
"birthday": {
"date": 1,
"day": 4,
"hours": 0,
"minutes": 0,
"month": 5,
"nanos": 0,
"seconds": 0,
"time": 1496246400000,
"timezoneOffset": -480,
"year": 117
}
- 给User模型添加一个get方法,修改界面字段为birthdayStr,这再次说明了属性是get方法截取后的产物
public String getBirthdayStr(){
if (birthday != null){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(birthday);
}else{
return "未提交生日";
}
}
- 把界面的电话字段改成显示角色
1 在User模型中添加一个getRolesStr方法
public String getRolesStr(){
String str = "";
for (Role role : roles) {
str += role.getName() + "、";
}
return str;
}
-
效果
8.5 修改BOSRealm中的授权方法
8.5.1 授权的逻辑
- 如果是admin,则拥有所有权限(Function所有的功能都能用)
- 如果是非管理员,只能通过
sql
语句,用用户id查找权限- 一个用户,可能有多个角色,一个角色又有多个权限,那么通过id查询权限,肯定有重复的权限数据,所以要DISTINCT,去除重复数据
- 推荐第二种写法,易于理解,三张主表都是多对多关系,使用左外连接
#根据用户ID,查找他所拥有的的权限function
SELECT f.id, f.name, f.page, f.code
FROM auth_function f
LEFT OUTER JOIN role_function rf
ON rf.function_id = f.id
LEFT OUTER JOIN auth_role r
ON rf.role_id = r.id
LEFT OUTER JOIN user_role ur
ON r.id = ur.role_id
WHERE ur.user_id = '40289f196c9f9da7016c9fa12f3a0000';
#第二种方式
SELECT DISTINCT f.id,f.name,f.page,f.code
FROM
auth_function f,
role_function rf,
auth_role r,
user_role ur
WHERE
rf.function_id = f.id and
rf.role_id = r.id and
r.id = ur.role_id and
ur.user_id = '40289f196c9f9da7016c9fa12f3a0000';
- 用户查找权限hql语句
@Override
public List<Function> findListByUserId(String userId) {
String hql = "SELECT DISTINCT f FROM Function f ";
hql += "LEFT OUTER JOIN f.roles r ";
hql += "LEFT OUTER JOIN r.users u ";
hql += "WHERE u.id = ?";
return hibernateTemplate.find(hql, userId);
}
- 给登录用户,授予权限,Realm代码
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//拿到用户
User loginUser = (User) principals.getPrimaryPrincipal();
//根据用户ID查权限
List<Function> functions = null;
//admin超级管理员
if (loginUser.getUsername().equals("admin")){
functions = functionDao.findAll();
}else{
functions = functionDao.findListByUserId(loginUser.getId());
}
//往shiro添加权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Function function : functions) {
info.addStringPermission(function.getCode());
}
return info;
}
-
授权测试:JSP添加一个shiro标签
-
在数据库中添加一个admin
这样用hr登录,就没有作废功能,作废按钮不显示
-
对数据库的一点补充
- 左外连接,就是左连接,此二者等价
- 还有全外连接,一般不用
-
多对多关系,本质就是通过中间表,做所有的连接,然后查询想要的数据,并去除重复
8.6 使用ehcache缓存权限数据
-
导入ehcache的jar包,3个
使用ehcache缓存权限数据
<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配置文件中添加授权缓存策略
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>
<!--2.配置shiro的安全管理者-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置realm对象-->
<property name="realm" ref="realm"></property>
<!--配置缓存管理者-->
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--3.配置一个realm对象,这个对象要继承AuthorizingRealm-->
<bean id="realm" class="com.kdj.bos.web.realm.BOSRealm"></bean>
8.7 修改主界面菜单从数据库获取
- 主界面菜单根据登录用户的角色不用来显示不同的菜单,管理员拥有全部功能
- dao代码
@Override
public List<Function> findMenuByUserId(String id) {
String hql = "SELECT DISTINCT f From Function f ";
hql += "LEFT OUTER JOIN f.roles r ";
hql += "LEFT OUTER JOIN r.users u ";
hql += "WHERE u.id = ? AND f.generatemenu = '1' ORDER BY f.zindex DESC";
return (List<Function>) this.hibernateTemplate.find(hql,id);
}
@Override
public List<Function> findAllMenu() {
String hql = "From Function f where f.generatemenu = '1' ORDER BY f.zindex DESC";
return (List<Function>) this.hibernateTemplate.find(hql);
}
- 前后端交互的本质:前端发送一个请求,后端返回一个json,就这么简单