最新提示:该项目由于属于毕业设计,所以存在着比较多的不足,所以读者可以参考下设计思路即可,请不要直接沿用该设计
最近一直在思考,如何设计一个毕业论文呢?后台就回想起自己以前大学做的项目,每次都需要开发一个后台网站,及其繁琐,后来就慢慢有了个想法,开发一个网站固件,可以动态配置,于是就动手设计了起来...
经过一个多月的研究,还系统也初步完善了起来。为了让开发更加人性化,这里面加入了代码生成器模块(详情http://blog.csdn.net/u010469003/article/details/46358859),以及自动生成权限信息模块。项目github地址:https://github.com/luohong19910602/springmvc_mybatis。
项目架构设计:spring + spring mvc + mybatis + liger ui + jsp等。
为了进一步优化查询用户权限系统模块,这里面使用如下sql对查询用户权限进行优化。
优化查询用户权限sql
#第一步,查询出用户角色所关联的权限
SELECT
sys_privilege.id,
sys_user.id,
sys_user.user_name,
sys_privilege.privilege_name,
sys_privilege.privilege_url,
sys_privilege.privilege_name,
sys_privilege.privilege_parent_id
FROM
sys_user,
sys_role,
sys_privilege,
sys_role_user,
sys_role_privilege
WHERE
sys_user.id = sys_role_user.user_id
AND sys_role_user.role_id = sys_role.id
AND sys_role.id = sys_role_privilege.role_id
AND sys_role_privilege.privilege_id = sys_privilege.id
UNION
#第二步,查询出用户自己的权限
#第三步,两者合并起来就是用户所具备的权限
SELECT
sys_privilege.id,
sys_user.id,
sys_user.user_name,
sys_privilege.privilege_name,
sys_privilege.privilege_url,
sys_privilege.privilege_name,
sys_privilege.privilege_parent_id
FROM sys_user,sys_privilege,sys_user_privilege
where
sys_user.id = sys_user_privilege.user_id
AND sys_user_privilege.privilege_id=sys_privilege.id;
这里面将用户权限划分为两个部分,第一部分是用户关联角色具备的权限,另外一部分是用户的个人权限,合并起来得到用户的全部权限。同理,用户的菜单也可以使用同样的优化技术。具体的数据库设计大家可以观察下面的数据库部分,发现菜单与权限模块的表设计是一样的。
通用权限设计
每一个网站,都有着相似的后台界面,现在比较流行的DWZ、LIGERUI、EXTJS等js框架,都提供了相似的界面。同时,后台网站的安全也是非常重要的。那么,为了开发上的方便,很有必要将一个后台网站做成一个通用的网站固件。这样子带来的好处:可以动态配置后台网站,极大简便了重复造轮子的功夫。主要内容
界面组件授权和验证权限
界面组件主要指的是用户可以访问的菜单与资源
授权主要指的是授予用户可以访问的url资源链接
验证权限程序验证用户的请求是否合法,如果不合法,那么程序将不允许用户进行访问
下面先抽象出几个实体
- 菜单:就是用于导航与分类的界面组件,通常情况下,一个菜单可以拥有多个子菜单与资源。比如“基础服务菜单”,包含了“权限管理”、“角色管理”、“用户管理”、“菜单管理”四个资源,也简称为四个链接
- 资源:菜单下面的叶子节点,也是一个界面组件,代表一个可以访问的url链接
- 权限:用于形成网站可访问的资源集,一个集合内的权限,形成一个包,权限之间存在着层次关系。比如“用户管理权限”,包含了“添加用户”、“删除用户”、“添加用户”、“列出全部用户”四个权限
- 用户:该网站的后台用户。
- 角色:每个用户可以拥有自己的菜单与权限,所以每次添加一个新用户,都需要配置菜单、权限信息,这样子操作比较繁琐,所以抽象出一个角色实体,用于划分用户所属的类别。角色可以拥有多个子角色、用户、菜单、权限
实体之间的ER关系
- 菜单与资源:1-N关系,也就是一个菜单可以拥有多个资源。
- 菜单与菜单自身:1-N
- 菜单与用户:N-N,也就是一个菜单可以被多名用户访问,一个用户可以访问多个菜单
- 角色与用户:N-N,也就是一个角色可以拥有多个用户,一个用户可以同时属于多个角色
- 角色与权限:N-N,也就是一个角色可以拥有多个权限,一个权限可以同时属于多个角色
- 角色与菜单:N-N,也就是一个角色可以拥有多个菜单,一个菜单可以同时属于多个角色
- 用户与权限:N-N,也就是一个用户可以拥有多个权限,一个权限可以同时被多个用户访问
- 权限与权限自身:1-N
下面我们看看数据库结构
CREATE TABLE `sys_menu` (
`id` varchar(50) NOT NULL,
`menu_parent_id` varchar(50) DEFAULT NULL,
`menu_name` varchar(50) DEFAULT NULL,
`menu_desc` varchar(50) DEFAULT NULL,
`menu_created_time` varchar(50) DEFAULT NULL,
`menu_creator` varchar(50) DEFAULT NULL,
`menu_updated_time` varchar(50) DEFAULT NULL,
`menu_updator` varchar(50) DEFAULT NULL,
`menu_del_flag` int(1) DEFAULT '0',
`menu_pic` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table sys_resource(
id varchar(50) primary key,
resource_name varchar(50) not null,
resource_url varchar(50),
resource_desc varchar(50),
resource_menu_id varchar(50),
resource_created_time varchar(50),
resource_creator varchar(50),
resource_updated_time varchar(50),
resource_updator varchar(50),
resource_del_flag int(1) default 0,
foreign key (resource_menu_id) references sys_menu(id)
on delete cascade on update cascade
);
create table sys_user(
id varchar(50) primary key,
user_name varchar(50),
user_login_name varchar(50),
user_password varchar(50),
user_tel varchar(50),
user_qq varchar(20),
user_email varchar(50),
user_blog varchar(50),
user_address varchar(200),
user_current_address varchar(200),
user_birthday varchar(20),
user_login_count int(11) default 0,
user_updated_time varchar(50),
user_updator varchar(50),
user_created_time varchar(50),
user_creator varchar(50),
user_del_flag int(1) default 0,
user_super_user_flag int(1) default 0
);
--用户可以访问的url
create table sys_user_resource(
id varchar(50) primary key,
user_resource_user_id varchar(50) not null,
user_resource_url varchar(200) not null,
foreign key (user_resource_user_id) references sys_user(id)
on delete cascade on update cascade
);
--角色
create table sys_role(
id varchar(50) primary key,
role_name varchar(50) not null,
role_parent_id varchar(50),
role_updated_time varchar(50),
role_updator varchar(50),
role_created_time varchar(50),
role_creator varchar(50),
role_del_flag int(1) default 0
);
create table sys_role_menu(
id varchar(50) primary key,
role_id varchar(50),
menu_id varchar(50),
resource_id varchar(50),
foreign key (role_id) references sys_role(id)
on delete cascade on update cascade,
foreign key (menu_id) references sys_menu(id)
on delete cascade on update cascade,
foreign key (resource_id) references sys_resource(id)
on delete cascade on update cascade
);
drop table sys_privilege;
create table sys_privilege(
id varchar(50) primary key,
privilege_name varchar(50) not null,
privilege_url varchar(200),
privilege_desc varchar(200),
privilege_parent_id varchar(50),
privilege_created_time varchar(50),
privilege_creator varchar(50),
privilege_updated_time varchar(50),
privilege_updator varchar(50),
privilege_del_flag int(1) default 0,
foreign key (privilege_parent_id) references sys_privilege(id)
on delete cascade on update cascade
);
create table sys_role_privilege(
id varchar(50) primary key,
role_id varchar(50),
privilege_id varchar(50),
foreign key (role_id) references sys_role(id)
on delete cascade on update cascade,
foreign key (privilege_id) references sys_privilege(id)
on delete cascade on update cascade
);
create table sys_role_user(
id varchar(50) primary key,
role_id varchar(50),
user_id varchar(50),
foreign key (role_id) references sys_role(id)
on delete cascade on update cascade,
foreign key (user_id) references sys_user(id)
on delete cascade on update cascade
);
create table sys_user_privilege(
id varchar(50) primary key,
user_id varchar(50),
privilege_id varchar(50),
foreign key (user_id) references sys_user(id)
on delete cascade on update cascade,
foreign key (privilege_id) references sys_privilege(id)
on delete cascade on update cascade
);
create table sys_user_menu(
id varchar(50) primary key,
user_id varchar(50),
menu_id varchar(50),
resource_id varchar(50),
foreign key (user_id) references sys_user(id)
on delete cascade on update cascade,
foreign key (menu_id) references sys_menu(id)
on delete cascade on update cascade,
foreign key (resource_id) references sys_resource(id)
on delete cascade on update cascade
);
初始化信息
一个网站的初始化信息,也代表最基本的信息。应该包含的内容如下:
一个“基础服务”菜单,该菜单下面拥有四个资源,分别是:菜单管理、角色管理、用户管理、权限管理。这几个资源,分别代表了网站固件的一个基本模块。
- 菜单管理:用于管理该后台网站的菜单信息,包含了添加菜单、删除菜单、更新菜单、列出全部菜单等功能
- 角色管理:用于管理该后台网站的角色信息,包含了添加角色、删除角色、更新角色、列出全部角色、管理角色下的用户、管理角色可以访问的权限、管理角色可以访问的菜单
- 用户管理:用户管理该后台网站的用户信息,包含了添加用户、删除用户、更新用户、流出全部用户、配置用户的角色、权限、菜单
- 权限管理:用户管理该后台网站的权限信息,包含了添加权限、删除权限、更新权限、列出全部权限
超级管理员
为了让方便考虑,需要一个用户,具备访问该网站的全部功能模块,这个用户我们称之为超级管理员。在初始化一个后台网站时,超级管理员需要做的事情如下:1、添加权限
2、配置网站的菜单信息
3、添加角色
4、添加用户
5、配置用户的菜单、权限
所以通常而言,超级管理员属于开发阶段的开发人员使用。每当一个新的功能加入时,就需要配置一个权限,然后将权限关联到相关菜单下面。
使用技术:spring mvc + mybatis + ligerui + javascript + css + jsp + html + mysql
下面看看工程的包组织,这里使用了按照模块划分来组织包。
下面是jsp的组织,同样采用按照模块来组织,并且将所有的jsp界面放在了WEB-INF下面,可以确保文件的相关安全
在开发过程中,最主要的是抽象与设计架构,所以这里主要做了一个树组件的抽象。也就是将数据库的数据,变成js框架中的组件。于是设计了一个liger ui的树组件
下面先看看liger ui树组件的数据
<span style="font-size:18px;">var indexdata =
[
{ text: '基础',isexpand:false, children: [
{url:"demos/base/resizable.htm",text:"改变大小"},
{url:"demos/base/drag.htm",text:"拖动"},
{url:"demos/base/drag2.htm",text:"拖动2"},
{url:"demos/base/dragresizable.htm",text:"拖动并改变大小"},
{url:"demos/base/tip.htm",text:"气泡"},
{url:"demos/base/tip2.htm",text:"气泡2"}
]
}
];
</span>
通过观察,我们可以使用下面的接口来模拟
<span style="font-size:18px;">package net.itaem.view;
import java.util.List;
/**
* liger ui中Tree模型
*
* 菜单模型使用树的结构进行组织
* 一个ITreeModel有两个形态,一个是Menu,一个Leaf
* 遍历节点的时候,会递归遍历结点
*
* 这个接口主要用来描述菜单的一些相关操作
* 每一个后台框架都应该实现该接口,然后对外体现出一致性
* 目前框架提供了LigerUI Tree的实现
*
* <br>
* 如果用户如果感兴趣,可以提供一个DWZ, EXTJS的实现
* 在判断一个结点是否是同一个结点,这里使用结点ID来判断
* 为了配置菜单的显示顺序,这里需要默认指定一个排序方式
*
* @see LigerUiTree
*
* @date 2014-08-19 10:17 am
* @author 骆宏
* @email 846705189@qq.com
*
* */
public interface ITreeModel {
/**
* 定义一个字符串,用来表示树模型中的菜单节点
* */
String MENU = "menu";
/**
* 定义一个字符串,用来表示数模型中的叶子节点
* */
String LEAF = "leaf";
/**
* 返回当前菜单的结构层次
* @return 返回菜单的结构层次
* 如果是叶子节点,那么返回0
* */
public int level();
/**
* 返回当前节点的所有子节点,子节点包括了子菜单以及叶子节点
* @return 返回当前菜单的全部子节点
* */
public List<ITreeModel> children();
/**
* 返回当前节点的父节点
* @return 返回当前节点的父亲节点,如果没有父亲节点,返回null
* */
public ITreeModel parent();
/**
* 返回当前节点的节点类型,这里的节点类型一共有两种,一种是菜单,另外一种是叶子节点
* @return 节点类型
* @see ITreeModel#MENU
* @see ITreeModel#LEAF
* */
public String nodeType();
/**
* 返回当前节点的url
* @return 当前节点的url
* */
public String url();
/**
* 放回当前节点的id
* @return 当前节点id
* */
public String id();
/**
* 返回节点的名字
* @return 节点名字
* */
public String name();
/**
* 当前节点如果是菜单,那么该菜单默认是否展开呢?如果是返回true,代表展开;否则,代表不展开
* @return 返回菜单节点的展开状态
* */
public boolean isexpand();
/**
* 设置菜单名字
* @param name
* */
public void setName(String name);
/**
* 设置菜单url
* @param url
* */
public void setUrl(String url);
/**
* 设置菜单展开状态
* @param isexpend
* */
public void setIsexpand(boolean isexpand);
/**
* 设置父节点
* @param parent
* */
public void setParent(ITreeModel parent);
/**
* 设置孩子节点
* @param children
* */
public void setChildren(List<ITreeModel> children);
/**
* 设置节点id
* @param id
* */
public void setId(String id);
/**
* 返回该节点的json数据,包含该节点下面的所有子节点
* @return 返回当前节点的json数据
* */
public String toTreeJson();
/**
* 返回从根节点到当前节点的所有节点集合,包含当前节点
* 该集合的第一个元素为最大根节点,第二个元素为第二个根结点,依次类推
* @return 返回根节点到当前节点的集合
* */
public List<ITreeModel> route();
/**
* 返回当前结点下面的第position结点
* @return 返回以当前节点子根节点的子树
* */
public ITreeModel subTree(int position);
/**
* 添加子节点,添加到结点的结尾处
* @param subTree 要添加的子节点
* */
public boolean addSubTree(ITreeModel subTree);
/**
* 在指定position添加结点
* @param position 下标
* @param subTree 要添加的子节点
* */
public void addSubTree(int position, ITreeModel subTree);
/**
* 删除子节点
* @param subTree 要删除的结点
* */
public boolean deleteSubTree(ITreeModel subTree);
/**
* 删除子节点
* @param position 要删除的结点在子节点中的位置
* */
public boolean deleteSubTree(int position);
/**
* 判断类型
* @return
* */
public boolean isMenu();
/**
* 判断类型
* @return
* */
public boolean isLeaf();
/**
* 返回Menu的图片
* @return
* */
public String pic();
/**
* 返回图标的图片
* @param pic 图片url地址
* */
public void setPic(String pic);
} </span>
下面提供liger ui组件的实现
<span style="font-size:18px;">package net.itaem.view.ligerui;
import java.util.ArrayList;
import java.util.List;
import net.itaem.view.ITreeModel;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
/**
* 这里是liger ui树的插件实现类
*
* 在每次add、delete之后,都需要计算树的level
*
*
*
* @author 骆宏
* @date 2014-08-19 19:39 am
* @email 846705189@qq.com
* */
public class LigerUiTree implements ITreeModel{
//定义一个level,用来保存树的层次
private int level = 1;
//定义一个url,用来保存当前节点的url
private String url;
//定义一个id,用来保存当前节点id
private String id;
//定义一个isexpend,用来保存节点展开状态
private boolean isexpand;
//定义一个name,用来保存节点的名称
private String name;
//定义一个parent,用来保存节点的父亲节点
private ITreeModel parent;
//定义一个children,用来保存当前节点的所有子节点
private List<ITreeModel> children = new ArrayList<ITreeModel>();
//定义一个nodeType,用来保存结点类型
private String nodeType = LEAF;
//定义一个pic,用来保存图片的url地址
private String pic;
//用来缓存树的json数据
private String jsonCache;
//用来保存用户的操作状态,如果树已经构建好,并且没有删除、添加,那么继续使用jsonCache
private boolean hasChange;
public LigerUiTree(){
}
/**
* 定义一个基本的构造方法,该构造方法的参数都不能为空
* @param id 节点id
* @param name 节点name
* @param url 节点url
* */
public LigerUiTree(String id, String name, String url){
if(id == null || name == null || url == null) throw new RuntimeException("id name url都不能为空");
this.id = id;
this.name = name;
this.url = url;
hasChange = true;
}
public LigerUiTree(String id, String name, String url, ITreeModel parent) {
this(id, name, url);
this.parent = parent;
hasChange = true;
}
public LigerUiTree(String id, String name, String url, List<ITreeModel> children) {
this(id, name, url);
this.children = children;
hasChange = true;
}
@Override
public void setUrl(String url) {
this.url = url;
hasChange = true;
}
@Override
public void setId(String id) {
this.id = id;
hasChange = true;
}
@Override
public void setIsexpand(boolean isexpand) {
this.isexpand = isexpand;
hasChange = true;
}
@Override
public void setName(String name) {
this.name = name;
hasChange = true;
}
@Override
public void setParent(ITreeModel parent) {
this.parent = parent;
hasChange = true;
}
/**
* 这里同时会计算树的层次
* 并且这里会同时维护parant - children之间的关联管理,也就是在设置当前节点的子节点时,同时会指点这些子节点的父亲节点为当前节点
* */
@Override
public void setChildren(List<ITreeModel> children) {
if(children == null || children.size() == 0) return; //如果为null,do nothing
this.children = children; //设置当前结点的子节点为children
int max = 0;
ITreeModel child = null;
for(int i=0; i < children.size(); i++){
child = children.get(i);
child.setParent(this);
if(max < child.level()) max = child.level();
}
level += max;
nodeType = MENU;
hasChange = true;
}
@Override
public int level() {
//每次要计算树的高度,都必须遍历整棵树的,然后确定树的高度,由于树随时可以被改变,所以这里不适合使用缓存模式
return level;
}
@Override
public List<ITreeModel> children() {
return children;
}
@Override
public ITreeModel parent() {
return parent;
}
@Override
public String nodeType() {
return nodeType;
}
@Override
public String url() {
return url;
}
@Override
public String id() {
return id;
}
@Override
public boolean isexpand() {
return isexpand;
}
@Override
public String name(){
return name;
}
@Override
public String toTreeJson() {
if(hasChange && jsonCache == null){
JSONObject json = new JSONObject();
//生成这个节点的基本数据
json.put("text", name);
json.put("isexpand", isexpand);
json.put("id", id);
json.put("icon", pic);
if(url != null && !"".equals(url))
json.put("url", url);
if(parent != null){
json.put("pid", parent.id());
}
//生成这个节点的子菜单数据
JSONArray childrenJson = new JSONArray();
if(children != null && children.size() != 0){
for(ITreeModel child: children){
//让每个子menu递归的去生成json数据
childrenJson.add(toJson(child));
}
json.put("children", childrenJson);
}
jsonCache = json.toString();
return jsonCache;
}else
return jsonCache;
}
/**
* 递归入口
* @see MenuVo#toJson()
* */
private String toJson(ITreeModel tree){
JSONObject json = new JSONObject();
if(tree.children() != null && tree.children().size() != 0){
//生成这个菜单节点的基本数据
json.put("text", tree.name());
json.put("id", tree.id());
json.put("icon", tree.pic());
if(tree.parent() != null){
json.put("pid", tree.parent().id());
}
json.put("isexpand", tree.isexpand());
//生成这个菜单节点的子菜单数据
JSONArray childrenJson = new JSONArray();
if(tree.children() != null){
for(ITreeModel child: tree.children()){
//让每个子menu递归的去生成json数据
childrenJson.add(toJson(child));
}
json.put("children", childrenJson);
}
}else{ //这个节点不是菜单,是菜单下面的一个具体子节点,该节点已经没有子节点了
json.put("id", tree.id());
if(tree.parent() != null){
json.put("pid", tree.parent().id());
}
json.put("text", tree.name());
json.put("url", tree.url());
json.put("icon", tree.pic());
}
return json.toString();
}
@Override
public List<ITreeModel> route() {
List<ITreeModel> route = new ArrayList<ITreeModel>();
ITreeModel current = this;
while(current != null){
route.add(current);
current = current.parent();
}
java.util.Collections.reverse(route);
return route;
}
@Override
public ITreeModel subTree(int position) {
if(position < 0) throw new RuntimeException("position 小于0");
if(children != null && children.size() > 0 && position <= children.size()-1){
return children.get(position);
}
return null;
}
/**
* 生成hashCode
* */
@Override
public int hashCode() {
return id.hashCode() * 37 + 5;
}
/**
* 比较两个菜单是否相等
* */
@Override
public boolean equals(Object obj) {
return id.equals(((ITreeModel)obj).id());
}
/**
* 返回节点的基本信息
* @return
* */
@Override
public String toString() {
return "LigerUiTree [" + "id=" + id + ", name=" + name
+ ", level=" + level + ", url=" + url
+ ", nodeType=" + nodeType() + ", isexpand=" + isexpand + ", pic=" + pic + "]";
}
@Override
public boolean addSubTree(ITreeModel subTree) {
if(subTree == null) return false;
nodeType = MENU;
subTree.setParent(this);
boolean addedFlag = children.add(subTree);
calculateLevel0(subTree);
hasChange = true;
return addedFlag;
}
@Override
public void addSubTree(int position, ITreeModel subTree) {
if(position < 0 || position >= children.size()) return;
children.add(position, subTree);
calculateLevel0(subTree);
if(children.size() > 0)
nodeType = MENU;
hasChange = true;
}
/**
* 增加一个结点,计算level,分为四种情况
* */
private void calculateLevel0(ITreeModel subTree){
if(this.isLeaf() && subTree.isLeaf()){
level = 2;
}else if(this.isMenu() && subTree.isMenu()){
level += subTree.level();
}else if(this.isLeaf() && subTree.isMenu()){
level = subTree.level() + 1;
}else{
//is menu, so add a new leaf, the level not change
}
}
@Override
public boolean deleteSubTree(ITreeModel subTree) {
boolean deletedFlag = false;
for(int i=0; i<children.size(); i++){
if(children.get(i).equals(subTree)){
children.remove(i);
break;
}
}
if(children.size() > 0)
nodeType = MENU;
calculateLevel();
hasChange = true;
return deletedFlag;
}
@Override
public boolean deleteSubTree(int position) {
if(position < 0 || children == null || children.size() == 0 || position >= children.size()) return false;
ITreeModel tree = children.remove(position);
if(children.size() > 0)
nodeType = MENU;
calculateLevel();
hasChange = true;
if(tree == null) return false;
else return true;
}
/**
* 计算输的层次,每次删除一个结点,需要遍历当前所有子节点,看看当前的子节点中,最大的level,然后将这个值+1即可
* */
private void calculateLevel(){
//设置level,遍历所有的children树,然后取最大值
int max = -1;
for(int i=0; i<children.size(); i++){
children.get(i).setParent(this); //维护parent-children的相互关联关系
if(children.get(i).level() > max) max = children.get(i).level();
}
//如果添加的节点都是叶子节点,那么当前层次为2
//否则计算最大的树层次 = 子节点最大的层次 + 1
if(max != -1){
level = max + 1;
}else{
level = 2;
}
}
@Override
public boolean isMenu() {
return nodeType.equals(ITreeModel.MENU);
}
@Override
public boolean isLeaf() {
return nodeType.equals(ITreeModel.LEAF);
}
@Override
public String pic() {
return pic;
}
@Override
public void setPic(String pic) {
this.pic = pic;
}
}</span>
有了模型,下面只需要定义几个工具类,实现数据库对象与liger ui数据json转换即可
<span style="font-size:18px;">package net.itaem.view;
import net.itaem.menu.entity.Menu;
import net.itaem.privilege.entity.Privilege;
import net.itaem.role.entity.Role;
/**
* 实现菜单、角色、权限-->Liger Ui Tree转换
*
* @author luohong
* @date 2014-12-24
* @email 846705189@qq.com
* */
public interface IToTree {
/**
* 将Menu变成一个Tree
* @param menu
* @return
* */
public ITreeModel menuToTree(Menu menu);
/**
* 将Role变成一个Tree
* @param role
* @return
* */
public ITreeModel roleToTree(Role role);
/**
* 将一个Privilege变成一个Tree
* @param privilege
* @return
* */
public ITreeModel privilegeToTree(Privilege privilege);
}
</span>
<span style="font-size:18px;">
</span>
<pre name="code" class="html"><span style="font-size:18px;">package net.itaem.view.ligerui;
import java.util.ArrayList;
import java.util.List;
import net.itaem.view.ITreeModel;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
/**
* 这里是liger ui树的插件实现类
*
* 在每次add、delete之后,都需要计算树的level
*
*
*
* @author 骆宏
* @date 2014-08-19 19:39 am
* @email 846705189@qq.com
* */
public class LigerUiTree implements ITreeModel{
//定义一个level,用来保存树的层次
private int level = 1;
//定义一个url,用来保存当前节点的url
private String url;
//定义一个id,用来保存当前节点id
private String id;
//定义一个isexpend,用来保存节点展开状态
private boolean isexpand;
//定义一个name,用来保存节点的名称
private String name;
//定义一个parent,用来保存节点的父亲节点
private ITreeModel parent;
//定义一个children,用来保存当前节点的所有子节点
private List<ITreeModel> children = new ArrayList<ITreeModel>();
//定义一个nodeType,用来保存结点类型
private String nodeType = LEAF;
//定义一个pic,用来保存图片的url地址
private String pic;
//用来缓存树的json数据
private String jsonCache;
//用来保存用户的操作状态,如果树已经构建好,并且没有删除、添加,那么继续使用jsonCache
private boolean hasChange;
public LigerUiTree(){
}
/**
* 定义一个基本的构造方法,该构造方法的参数都不能为空
* @param id 节点id
* @param name 节点name
* @param url 节点url
* */
public LigerUiTree(String id, String name, String url){
if(id == null || name == null || url == null) throw new RuntimeException("id name url都不能为空");
this.id = id;
this.name = name;
this.url = url;
hasChange = true;
}
public LigerUiTree(String id, String name, String url, ITreeModel parent) {
this(id, name, url);
this.parent = parent;
hasChange = true;
}
public LigerUiTree(String id, String name, String url, List<ITreeModel> children) {
this(id, name, url);
this.children = children;
hasChange = true;
}
@Override
public void setUrl(String url) {
this.url = url;
hasChange = true;
}
@Override
public void setId(String id) {
this.id = id;
hasChange = true;
}
@Override
public void setIsexpand(boolean isexpand) {
this.isexpand = isexpand;
hasChange = true;
}
@Override
public void setName(String name) {
this.name = name;
hasChange = true;
}
@Override
public void setParent(ITreeModel parent) {
this.parent = parent;
hasChange = true;
}
/**
* 这里同时会计算树的层次
* 并且这里会同时维护parant - children之间的关联管理,也就是在设置当前节点的子节点时,同时会指点这些子节点的父亲节点为当前节点
* */
@Override
public void setChildren(List<ITreeModel> children) {
if(children == null || children.size() == 0) return; //如果为null,do nothing
this.children = children; //设置当前结点的子节点为children
int max = 0;
ITreeModel child = null;
for(int i=0; i < children.size(); i++){
child = children.get(i);
child.setParent(this);
if(max < child.level()) max = child.level();
}
level += max;
nodeType = MENU;
hasChange = true;
}
@Override
public int level() {
//每次要计算树的高度,都必须遍历整棵树的,然后确定树的高度,由于树随时可以被改变,所以这里不适合使用缓存模式
return level;
}
@Override
public List<ITreeModel> children() {
return children;
}
@Override
public ITreeModel parent() {
return parent;
}
@Override
public String nodeType() {
return nodeType;
}
@Override
public String url() {
return url;
}
@Override
public String id() {
return id;
}
@Override
public boolean isexpand() {
return isexpand;
}
@Override
public String name(){
return name;
}
@Override
public String toTreeJson() {
if(hasChange && jsonCache == null){
JSONObject json = new JSONObject();
//生成这个节点的基本数据
json.put("text", name);
json.put("isexpand", isexpand);
json.put("id", id);
json.put("icon", pic);
if(url != null && !"".equals(url))
json.put("url", url);
if(parent != null){
json.put("pid", parent.id());
}
//生成这个节点的子菜单数据
JSONArray childrenJson = new JSONArray();
if(children != null && children.size() != 0){
for(ITreeModel child: children){
//让每个子menu递归的去生成json数据
childrenJson.add(toJson(child));
}
json.put("children", childrenJson);
}
jsonCache = json.toString();
return jsonCache;
}else
return jsonCache;
}
/**
* 递归入口
* @see MenuVo#toJson()
* */
private String toJson(ITreeModel tree){
JSONObject json = new JSONObject();
if(tree.children() != null && tree.children().size() != 0){
//生成这个菜单节点的基本数据
json.put("text", tree.name());
json.put("id", tree.id());
json.put("icon", tree.pic());
if(tree.parent() != null){
json.put("pid", tree.parent().id());
}
json.put("isexpand", tree.isexpand());
//生成这个菜单节点的子菜单数据
JSONArray childrenJson = new JSONArray();
if(tree.children() != null){
for(ITreeModel child: tree.children()){
//让每个子menu递归的去生成json数据
childrenJson.add(toJson(child));
}
json.put("children", childrenJson);
}
}else{ //这个节点不是菜单,是菜单下面的一个具体子节点,该节点已经没有子节点了
json.put("id", tree.id());
if(tree.parent() != null){
json.put("pid", tree.parent().id());
}
json.put("text", tree.name());
json.put("url", tree.url());
json.put("icon", tree.pic());
}
return json.toString();
}
@Override
public List<ITreeModel> route() {
List<ITreeModel> route = new ArrayList<ITreeModel>();
ITreeModel current = this;
while(current != null){
route.add(current);
current = current.parent();
}
java.util.Collections.reverse(route);
return route;
}
@Override
public ITreeModel subTree(int position) {
if(position < 0) throw new RuntimeException("position 小于0");
if(children != null && children.size() > 0 && position <= children.size()-1){
return children.get(position);
}
return null;
}
/**
* 生成hashCode
* */
@Override
public int hashCode() {
return id.hashCode() * 37 + 5;
}
/**
* 比较两个菜单是否相等
* */
@Override
public boolean equals(Object obj) {
return id.equals(((ITreeModel)obj).id());
}
/**
* 返回节点的基本信息
* @return
* */
@Override
public String toString() {
return "LigerUiTree [" + "id=" + id + ", name=" + name
+ ", level=" + level + ", url=" + url
+ ", nodeType=" + nodeType() + ", isexpand=" + isexpand + ", pic=" + pic + "]";
}
@Override
public boolean addSubTree(ITreeModel subTree) {
if(subTree == null) return false;
nodeType = MENU;
subTree.setParent(this);
boolean addedFlag = children.add(subTree);
calculateLevel0(subTree);
hasChange = true;
return addedFlag;
}
@Override
public void addSubTree(int position, ITreeModel subTree) {
if(position < 0 || position >= children.size()) return;
children.add(position, subTree);
calculateLevel0(subTree);
if(children.size() > 0)
nodeType = MENU;
hasChange = true;
}
/**
* 增加一个结点,计算level,分为四种情况
* */
private void calculateLevel0(ITreeModel subTree){
if(this.isLeaf() && subTree.isLeaf()){
level = 2;
}else if(this.isMenu() && subTree.isMenu()){
level += subTree.level();
}else if(this.isLeaf() && subTree.isMenu()){
level = subTree.level() + 1;
}else{
//is menu, so add a new leaf, the level not change
}
}
@Override
public boolean deleteSubTree(ITreeModel subTree) {
boolean deletedFlag = false;
for(int i=0; i<children.size(); i++){
if(children.get(i).equals(subTree)){
children.remove(i);
break;
}
}
if(children.size() > 0)
nodeType = MENU;
calculateLevel();
hasChange = true;
return deletedFlag;
}
@Override
public boolean deleteSubTree(int position) {
if(position < 0 || children == null || children.size() == 0 || position >= children.size()) return false;
ITreeModel tree = children.remove(position);
if(children.size() > 0)
nodeType = MENU;
calculateLevel();
hasChange = true;
if(tree == null) return false;
else return true;
}
/**
* 计算输的层次,每次删除一个结点,需要遍历当前所有子节点,看看当前的子节点中,最大的level,然后将这个值+1即可
* */
private void calculateLevel(){
//设置level,遍历所有的children树,然后取最大值
int max = -1;
for(int i=0; i<children.size(); i++){
children.get(i).setParent(this); //维护parent-children的相互关联关系
if(children.get(i).level() > max) max = children.get(i).level();
}
//如果添加的节点都是叶子节点,那么当前层次为2
//否则计算最大的树层次 = 子节点最大的层次 + 1
if(max != -1){
level = max + 1;
}else{
level = 2;
}
}
@Override
public boolean isMenu() {
return nodeType.equals(ITreeModel.MENU);
}
@Override
public boolean isLeaf() {
return nodeType.equals(ITreeModel.LEAF);
}
@Override
public String pic() {
return pic;
}
@Override
public void setPic(String pic) {
this.pic = pic;
}
}</span>
<span style="font-size:18px;">
</span>
恩,代码就补贴那么多了,需要源码的直接发我邮箱即可,有时间我直接把源码发你...哈哈,有兴趣的,可以自己完善代码。
下面我们看看工程跑起来的相关界面,目前处于开发中,所以还有几个模块在开发中,需要源码的,可以联系我