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

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

通用权限设计

每一个网站,都有着相似的后台界面,现在比较流行的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

初始化信息

一个网站的初始化信息,也代表最基本的信息。应该包含的内容如下:

一个“基础服务”菜单,该菜单下面拥有四个资源,分别是:菜单管理、角色管理、用户管理、权限管理。这几个资源,分别代表了网站固件的一个基本模块。

  • 菜单管理:用于管理该后台网站的菜单信息,包含了添加菜单、删除菜单、更新菜单、列出全部菜单等功能
  • 角色管理:用于管理该后台网站的角色信息,包含了添加角色、删除角色、更新角色、列出全部角色、管理角色下的用户、管理角色可以访问的权限、管理角色可以访问的菜单
  • 用户管理:用户管理该后台网站的用户信息,包含了添加用户、删除用户、更新用户、流出全部用户、配置用户的角色、权限、菜单
  • 权限管理:用户管理该后台网站的权限信息,包含了添加权限、删除权限、更新权限、列出全部权限

超级管理员

为了让方便考虑,需要一个用户,具备访问该网站的全部功能模块,这个用户我们称之为超级管理员。在初始化一个后台网站时,超级管理员需要做的事情如下:

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 [email protected]
 *
 * */
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 [email protected]
 * */
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 [email protected]
 * */
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 [email protected]
 * */
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>

恩,代码就补贴那么多了,需要源码的直接发我邮箱即可,有时间我直接把源码发你...哈哈,有兴趣的,可以自己完善代码。

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

时间: 2024-10-09 00:53:44

通用权限设计(springmvc + mybatis + ligerui)的相关文章

通用权限设计

1.  可以跟角色给用户权限,用户权限字段存的数据格式为:ID_菜单英文名称; ID_菜单英文名称;……; ID_功能英文名称;……. 2.  用户登录时,权限存在session里: 3.  初始化菜单,判断用户权限是否存在该菜单,如果存在就出现,不存在就隐藏. 4.  页面功能java在拦截器了判断权限,PHP在同一入口了拦截,.net就在页面的基类里判断或者在Page_Init里设置控件隐藏或显示. 通用权限设计

多租户通用权限设计(基于casbin)

多租户通用权限设计(基于 casbin) 所谓权限控制, 概念并不复杂, 就是确认某个操作是否能做, 本质上仅仅就是个bool判断. 权限几乎是每个系统必不可少的功能, 和具体业务结合之后, 在系统中往往表现的非常复杂和难于控制, 很大部分原因是把权限和具体业务结合的太过紧密, 把业务的复杂度也加入到权限控制中来了. 一直以来, 都有个想法, 想做一套简单好用的通用权限系统, 和任何业务都没有关系, 仅仅就是权限本身的功能. 对此, 做过很多尝试, 由于设计能力有限, 最后都不了了之, 没能坚持

.net通用权限设计源码

经过一年多的设计编码和测试,公司通用权限系统对业务部门提供了比较稳定.便捷和灵活的支撑. 该系统分为两部分:配置界面和WCF服务. 界面:权限系统管理员可以通过配置界面的方式对各部门或者组织机构的人员进行业务权限分配操作. WCF服务:业务开发人员可以通过权限系统提供的接口服务,获取业务权限数据,对业务系统进行开发. 该设计的好处就是权限系统的可移植性更高,权限系统可以单独部署,发布对外的服务接口即可,和业务系统之间耦合性更低,从而达到设计模式的核心价值高内聚,低耦合. IIS部署示例如下: 权

一个基于RBAC0的通用权限设计清单

注:RBAC0与RBAC1不同在于权限继承.关于RBAC1的权限设计,敬请关注作者后续CSDN博客.1,用户表 保存系统用户信息,如张三.李四,字段可以有id.name.fullname.email.phone.……2,角色表 保存角色信息,如学生.管理员,字段有id.name.……3,权限表 保存系统的权限信息,可定义系统哪些模块公开,或者什么时段可访问,字段有id,权限名4,用户角色表 关联用户和角色的关系表,如张三-学生,李四-管理员,字段有id.用户id.角色id,根据用户就知道所属的角

大话设计,没有模式—通用权限设计与实现

当代码写多了,总有些是经验,但经验是什么呢?if-else用的次数比别人多?显然不是.有些超棒的设计可以谓之经验! 功能权限网络上流行的经典的权限设计是[主体]- [领域] - [权限]( who.what.how问题原型 ) 的设计思想,其中: [主体]可以是用户,可以是角色,也可以是一个部门 [领域]可以是一个模块,可以是一个页面,也可以是页面上的按钮 [权限]可以是"可见",可以是"只读",也可以是"可用"(如按钮可以点击) 为了简化程序开

一个基于RBAC的通用权限设计清单

RBAC即角色访问控制(Role Based Access Control) RBAC认为权限授权实际上是Who.What.How的问题.在RBAC模型中,who.what.how构成了访问权限三元组,也就是"Who对What(Which)进行How的操作".Who:权限的拥用者或主体(如Principal.User.Group.Role.Actor等等)What:权限针对的对象或资源(Resource.Class).How:具体的权限(Privilege,正向授权与负向授权). 基于

通用权限管理设计

权限设计(初稿)     1. 前言:     权限管理往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断"Who对What(Which)进行How的操作"的逻辑表达式是否为真.针对不同的应用,需要根据项目的实际情况和具体架构,在维护性.灵活性.完整性等N多个方案之间比较权衡,选择符合的方案.     2. 目标:     直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要简单,包括概念数量上的简单和意义上的简单还有功能上的简单.想用一个权限系统

通用权限管理设计 ( 数据库结构设计)

一,前言  权限管理系统的应用者应该有三种不同性质上的使用, A,使用权限 B,分配权限 C,授权权限 本文只从<使用权限>和<分配权限>这两种应用层面分析,暂时不考虑<授权权限>这种. 二,初步分析 用户和角色 说到权限管理,首先应该想到,当然要设计一个用户表,一个权限表.这样就决定了一个人有什么样的权限. 做着做着就会发现这样设计太过繁琐,如果公司里面所有员工都有这样的权限呢,每一个人都要配置?那是一件很痛苦的事情.因此再添加一个角色表,把某些人归为一类,然后再把权

通用权限管理设计 之 数据权限

阅读目录 前言 初步分析 通用查询机制 数据权限规则 实际应用 结语 前言 前一篇文章<通用权限管理设计 之 数据库设计方案>介绍了[主体]- [领域] - [权限]( who.what.how问题原型 ) 的设计思想 本文将对这种设计思想作进一步的扩展,介绍数据权限的设计方案. 权限控制可以理解,分为这几种 : [功能权限]:能做什么的问题,如增加产品.[数据权限]:能看到哪些数据的问题,如查看本人的所有订单.[字段权限]:能看到哪些信息的问题,如供应商账户,看不到角色. 部门等信息. 上面