作为一个后端开发,在开发管理后台,一定会遇到【角色-资源管理】,那么,如何使用开源框架,快速的设计一棵资源权限树呢?
- jsTree API文档(中文)
- js Tree API官方文档
1. 数据库的设计
1. 系统角色表[sys_role]
CREATE TABLE `sys_role` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`ROLE_CODE` varchar(50) DEFAULT NULL COMMENT '角色编码',
`ROLE_NAME` varchar(128) DEFAULT NULL COMMENT '角色名称',
`STATUS` varchar(1) DEFAULT NULL COMMENT '是否启用,1 启用,0 禁用',
`ROLE_DESC` varchar(500) DEFAULT NULL COMMENT '角色描述',
`CREATOR` varchar(40) DEFAULT NULL COMMENT '创建者',
`CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UPDATER` varchar(40) DEFAULT NULL COMMENT '修改者',
`MODIFY_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`ID`),
UNIQUE KEY `i_role_code` (`ROLE_CODE`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统角色表';
2. 系统资源表[sys_resource]
CREATE TABLE `sys_resource` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`RES_CODE` varchar(50) DEFAULT NULL COMMENT '资源编码',
`RES_NAME` varchar(128) DEFAULT NULL COMMENT '资源名称',
`RES_KEY` varchar(40) DEFAULT NULL COMMENT '资源关键字',
`RES_TYPE` char(1) DEFAULT NULL COMMENT '资源类型,1 模块,2 菜单,3 操作',
`PARENT_RES_CODE` varchar(50) DEFAULT NULL COMMENT '父级资源编码',
`DISPLAY_ORDER` int(11) DEFAULT NULL COMMENT '同级中显示顺序号',
`IS_ENABLE` char(1) DEFAULT NULL COMMENT '是否启用,1 启用,0 禁用',
`DEL_FLAG` char(1) DEFAULT NULL COMMENT '删除标识,0 未删除,1 已删除',
`CREATOR` varchar(40) DEFAULT NULL COMMENT '创建者',
`CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`UPDATER` varchar(40) DEFAULT NULL COMMENT '修改者',
`MODIFY_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
COMMENT '修改时间',
`REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`ID`),
UNIQUE KEY `i_resource_code` (`RES_CODE`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
3. 系统角色-资源表[sys_role_res_ref]
CREATE TABLE `sys_role_res_ref` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`ROLE_ID` int(11) DEFAULT NULL COMMENT '角色ID',
`RES_CODE` varchar(50) DEFAULT NULL COMMENT '资源ID',
PRIMARY KEY (`ID`),
KEY `i_role_res_ref` (`ROLE_ID`,`RES_CODE`)
) ENGINE=InnoDB AUTO_INCREMENT=8339 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='系统角色资源关联表';
需要注意的是,角色和资源是n:n的关系,故需要一个中间表让两表进行关联。
2. 后端获取角色资源
1. 确定响应对象的key
由上面jstree.js源码可知。我们想要借助jsTree形成资源树,需要使用jstree.js源码中已定义的值(也就是key)。例如:children、text、state、id、selected。
2. 多叉资源树的遍历
资源树是一个多叉树,当我们得到它的根节点(此处使用00-00-00-00)那么我们可以遍历整棵树。
在【sys_resource 资源表】中含有【PARENT_RES_CODE 父级资源编码】字段。即子节点中包含父节点的信息。
对于树的遍历,一般采用递归算法。
对于递归方法,我们可以将其看做一个普通方法,这个方法的返回值就是【方法开始-递归方法之间的逻辑】。
对于整棵树来说,我们采用【前序遍历】,第一次访问父节点时,处理父节点。
递归要含有【递归出口】,即当遇到【子节点】时,采取某些操作,终止递归。
需要注意的是,此处的【角色】有两种,一类是【登录用户的角色】,一类是【登录用户创建的角色】,他们是父子集关系。
3. 代码分析
此处的Mapper层逻辑,使用的是mybaties-generate自动生成。
@Autowired
private SysResourceMapper sysResourceMapper;
@Autowired
private SysRoleResRefMapper sysRoleResRefMapper;
public JSONObject getSysRescTreeJson(int roleId) {
SysResourceExample example = new SysResourceExample();
example.createCriteria().andResCodeEqualTo("00-00-00-00");
//获取根节点的节点对象
List rootNodes = sysResourceMapper.selectByExample(example);
SysResource rootNode = null;
if (rootNodes != null && rootNodes.size() > 0) {
rootNode = rootNodes.get(0);
}
//获取登录用户创建的角色资源(子集)
Set roleRescSet = getRoleRescSet(roleId);
//获取资源列表对象
Map treeInfoMap = getNodeInfoMap(rootNode, roleRescSet);
return (JSONObject) JSONObject.toJSON(treeInfoMap);
}
1. 获取登录用户创建的角色资源
private Set getRoleRescSet(Integer roleId) {
//获取角色资源列表
Set roleRescSet = new HashSet();
SysRoleResRefExample example = new SysRoleResRefExample();
example.createCriteria().andRoleIdEqualTo(roleId);
List sysRoleResRefs = sysRoleResRefMapper.selectByExample(example);
//放入Set集合,以便后续快递比较
if (sysRoleResRefs != null && sysRoleResRefs.size() > 0) {
for (SysRoleResRef sysRoleResRef : sysRoleResRefs) {
roleRescSet.add(sysRoleResRef.getResCode());
}
}
return roleRescSet;
}
2. 多叉资源树的变量-获取资源信息
可以看到,我们在后台代码中使用了id、text、state、children、selected值作为key,以便和jsTree对应。
//参数值一个是当前节点对象、用户创建的角色资源列表
private Map getNodeInfoMap(SysResource node, Set roleRescSet) {
//最终输出的是第一次创建的map
Map map = new HashMap();
map.put("id", node.getResCode()); //角色编码
//角色名称[角色关键字]
map.put("text", node.getResName() + "[" + node.getResKey() + "]");
Map statMap = new HashMap();
//资源状态(引用传递,此处保留的是空对象)
map.put("state", statMap);
//获取当前节点的子节点列表信息
List resInfoByParentResCode = findResInfoByParentResCode(node.getResCode());
//获取shiro中的资源信息(登录用户的角色信息。UserAuthorizingInfo是自定义对象,里面含义一个Set字段,包含了登录用户资源信息)
Set currentResourcesSet= (UserAuthorizingInfo)(SecurityUtils.getSubject().getPrincipal()).getUserResources();
//递归出口 (非子节点进行处理)
if (resInfoByParentResCode != null && resInfoByParentResCode.size() > 0) {
//处理子节点(子节点信息)
List
3. 获取该节点的子节点
private List findResInfoByParentResCode(String parentResCode) {
SysResourceExample sysResourceExample = new SysResourceExample();
//查询该资源的子级资源,状态:未删除、启动
sysResourceExample.createCriteria().andParentResCodeEqualTo(parentResCode).andDelFlagEqualTo("0").andIsEnableEqualTo("1");
List sysResources = sysResourceMapper.selectByExample(sysResourceExample);
return sysResources;
}
4. Controller层代码
@Controller
@RequestMapping("role")
public class RoleAction {
private final Logger logger = LoggerFactory.getLogger(RoleAction.class);
@Autowired
private RoleService roleService;
/**
* 获取资源节点树数据
*/
@RequestMapping(value = "/getRescTreeData")
@ResponseBody
public ResponseVo getRescTreeData(Integer roleId) {
ResponseVo respVo = new ResponseVo();
try {
JSONObject treeData = roleService.getSysRescTreeJson(roleId);
System.out.println(JSONObject.toJSONString(treeData));
respVo.setData(treeData); //private Object data;
respVo.setRetcode(ResponseVo.SUCC);
respVo.setMessage("获取资源树数据成功");
} catch (Exception e) {
respVo.setRetcode(ResponseVo.FAIL);
respVo.setMessage("获取资源树数据失败 原因:" + e.getMessage());
logger.debug("获取资源树数据失败 原因:", e);
logger.error("获取资源树数据失败 原因:" + e);
}
return respVo;
}
}
3. 前端生成资源树
1. 展示权限树
此处就需要一个id为tree的div来生成树,是不是很方便~~~
//设置权限树
function setRescTree(roleId) {
$.ajax({
url: "./role/getRescTreeData?roleId=" + roleId,
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (json) {
if (json.retcode == 200) {
var treeData = json.data;
//返回一个存在的引用
var tree = jQuery.jstree.reference("#tree");
if (tree != null) {
tree.destroy();
}
// 当根节点(root)第一次加载时触发。
$("#tree").bind("loaded.jstree", function (e, data) {
//禁止编辑节点(children_d是所有子级均不可编辑;若是使用children,只是直接子级不能编辑)
//#代表是整棵树都不能被编辑
data.instance.disable_node($('#tree').jstree(true)._model.data['#']['children_d']);
}).jstree({
'plugins': ["wholerow", "checkbox", "types"],
'checkbox': {cascade: "", three_state: true},
'core': {
"themes": {
"responsive": false
},
"expand_selected_onload": true,
'data': treeData
},
"types": {
"default": {
"icon": "fa fa-folder icon-state-warning icon-lg"
},
"file": {
"icon": "fa fa-file icon-state-warning icon-lg"
}
}
});
// 当所有节点都加载完毕时触发。
$("#tree").on("ready.jstree", function (event, data) {
//禁止展开根节点(00-00-00-00)下的第一层-孩子节点
data.instance.close_node($('#tree').jstree(true)._model.data['00-00-00-00']['children'], false);
});
} else {
layer.alert(json.message, {icon: 2});
}
},
error: function () {
layer.alert("获取资源树数据失败", {icon: 2});
}
});
}
2. 保存权限树
如何编辑权限树,使其保存到数据库里面呢?
前端上送资源编码
使用$("#tree").jstree().get_checked();
获取整棵树被选中的资源。
//绑定点击事件
var save = function () {
var resc = $("#tree").jstree().get_checked();
//点击上交时,将资源树id提交上去。
var formData= $('.edit-from').serializeJsonStr();
$.ajax({
url: "./role/save",
data: JSON.stringify({
rescList:resc
}),
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
success:function (json) {
}
})
}
后台获取资源编码
@ResponseBody
@RequestMapping("save")
public ResponseVo saveUserInfo(@RequestBody SysRoleAndRescVo sysRoleAndRescVo){
logger.info(JSON.toJSONString(sysRoleAndRescVo));
//用户操作
return null;
}
请求参数:
本质上是上送一个id的List集合,故我们直接使用List
public class SysRoleAndRescVo {
private List rescList;
public List getRescList() {
return rescList;
}
public void setRescList(List rescList) {
this.rescList = rescList;
}
}