通用权限设计(springmvc + mybatis + ligerui)

最新提示:该项目由于属于毕业设计,所以存在着比较多的不足,所以读者可以参考下设计思路即可,请不要直接沿用该设计

最近一直在思考,如何设计一个毕业论文呢?后台就回想起自己以前大学做的项目,每次都需要开发一个后台网站,及其繁琐,后来就慢慢有了个想法,开发一个网站固件,可以动态配置,于是就动手设计了起来...

        经过一个多月的研究,还系统也初步完善了起来。为了让开发更加人性化,这里面加入了代码生成器模块(详情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. 菜单与资源:1-N关系,也就是一个菜单可以拥有多个资源。
  2. 菜单与菜单自身:1-N
  3. 菜单与用户:N-N,也就是一个菜单可以被多名用户访问,一个用户可以访问多个菜单
  4. 角色与用户:N-N,也就是一个角色可以拥有多个用户,一个用户可以同时属于多个角色
  5. 角色与权限:N-N,也就是一个角色可以拥有多个权限,一个权限可以同时属于多个角色
  6. 角色与菜单:N-N,也就是一个角色可以拥有多个菜单,一个菜单可以同时属于多个角色
  7. 用户与权限:N-N,也就是一个用户可以拥有多个权限,一个权限可以同时被多个用户访问
  8. 权限与权限自身:1-N

下面我们看看数据库结构



从图中我们可以发现,sys_role_menu,sys_user_menu同时关联着三个表,为什么这样子设计呢?
主要是为了程序更加的具备灵活性。因为在配置一个角色的菜单时,需要具体到每一个链接,所以同时关联着三张表。同理,sys_user_menu也是这样子设计的。当时这样子设计同样会出现一些复杂性,比如在程序中,就先得获得一个角色的菜单集合,然后再遍历菜单集合,才能得出每个菜单对应的resource(就是连接)。
下面给出数据库的DML,大家需要的,可以copy一下...
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>
恩,代码就补贴那么多了,需要源码的直接发我邮箱即可,有时间我直接把源码发你...哈哈,有兴趣的,可以自己完善代码。


下面我们看看工程跑起来的相关界面,目前处于开发中,所以还有几个模块在开发中,需要源码的,可以联系我



评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值