OA的学习--第六天的内容--权限模块和论坛模块

    OA第六天的内容是从第42集开始到63集,一共22集。这是所有天中学的视频集数最多的,当初学的时候,本来那天下午用来总结,就不会看这么多,奈何一是看的太起劲了,二是不想总结了,导致看的太多了.不知道这22,要弄出多少东西来,不过,好在很多东西前面都介绍了,后面需要单独介绍的应该不多了.

    看了第五天的学习内容,上面写着"应该再有4天就差不多学完了",现在想来,当时以为的是只要再按照计划学习4天就能学完,并且总结完.可惜啊,我没有按照原来的设想,一边看视频一边总结,导致视频是看完了,总结不想做了.,要不是写了第一篇雄心壮阔的博客,我估计也没有现在这几篇博客了.所以,以后看视频还是试试学完当天总结下吧.

    不过,照例还是先来介绍下第六天的学习内容:首先就是接着做权限功能,其中各种和权限有关的功能基本都有介绍,如登录注销,权限的分配,功能的是否显示.还有论坛模块的功能,需求分析、设计实体,增删改查不用说了,里面实现上下移动的功能,这个是通过操作后台来处理的。唉,看起来内容好像挺多的!

    感觉实现上下移动功能,有两种做法,一种是前台控制,一种是后台控制。前台的就是通过js控制上下移动,然后你真要保存,在点击保存,保存下修改,否则刷新页面,就回到最初;还有就是这种,每次点击上移,就更新一次数据库,然后页面上的显示就可以改变了,看起来就像上下移动了。

分配权限(TreeView)

    首先对于权限模块,最重要的功能就是权限分配,以及权限的控制(包括控制菜单是否显示,页面元素是否显示,以及是否登录控制).其中分配权限的效果,如下图所示.


    其中需要实现,1.树形结构显示,这样权限之间的关系会很明确;2.全选的选中和取消,影响下面所有权限的选中和取消;3.选中任意子节点,其所有直系父类都要选中;4.选中任意父结点,则其下所有子节点都要选中.而树形结构使用treeview就可以实现;其他都是靠jquery就能实现.

    首先看jsp页面的代码,引入treeview的js和css,以及文件夹图标的css.

	<script language="javascript"
			src="${pageContext.request.contextPath}/script/jquery_treeview/jquery.treeview.js"></script>
		<link type="text/css" rel="stylesheet"
			href="${pageContext.request.contextPath}/style/blue/file.css" />
		<link type="text/css" rel="stylesheet"
			href="${pageContext.request.contextPath}/script/jquery_treeview/jquery.treeview.css" />
    然后整个树形结构的表单页面代码就是这样,使用ul和li来控制缩进.其中,最外层的ul的id为tree.而所有的checkbox复选框的name都为privilegeIds.回显是通过s:property标签使用ognl的in效果是实现的.而class="folder",则是文件夹图标的效果.

<!-- 显示树状结构内容  -->
<ul id="tree">
<s:iterator value="#application.topPrivilegeList">
	<li>
		<input type="checkbox" name="privilegeIds" value="${id}" id="cb_${id}" <s:property value="%{id in privilegeIds ? 'checked' : ''}"/> />
		<label for="cb_${id}"><span class="folder">${name}</span></label>
		<ul>
		<s:iterator value="children">
			<li>
				<input type="checkbox" name="privilegeIds" value="${id}" id="cb_${id}" <s:property value="%{id in privilegeIds ? 'checked' : ''}"/> />
				<label for="cb_${id}"><span class="folder">${name}</span></label>
				<ul>
				<s:iterator value="children">
					<li>
						<input type="checkbox" name="privilegeIds" value="${id}" id="cb_${id}" <s:property value="%{id in privilegeIds ? 'checked' : ''}"/> />
						<label for="cb_${id}"><span class="folder">${name}</span></label>
					</li>
				</s:iterator>
				</ul>
			</li>		
		</s:iterator>
		</ul>
	</li>
</s:iterator>
</ul>  
     最后加上这段js,树形结构的效果就可以了.

<script language="javascript">
       $("#tree").treeview();
 </script>

     其中,3和4的效果,用这段js就能实现.

<script type="text/javascript">
	$(function(){
		// 指定事件处理函数
		$("[name=privilegeIds]").click(function(){
			// 当选中或取消一个权限时,也同时选中或取消所有的下级权限
			$(this).siblings("ul").find("input").attr("checked", this.checked);
			
			// 当选中一个权限时,也要选中所有的直接上级权限
			if(this.checked == true){
				$(this).parents("li").children("input").attr("checked", true);
			}
		});
	});
</script>
    而全选的效果也一样,找到所有复选框,然后他们的选中/取消和全选的保持一致.而label标签的for效果,只要指定一个id,那么点击label的效果和点击该id对象的效果是一样.也就是点击"全选"的文字,和点击复选框的效果是一样一样的.

<input type="checkbox" id="cbSelectAll" onClick="$('[name=privilegeIds]').attr('checked', this.checked)"/>
<label for="cbSelectAll">全选</label>
    最后,只要点击最下面的保存,提交form,就可以将privilegeIds,更新到数据库中.

登录和注销

    前面已经完成了分配权限的功能,现在可以对于登录和注销有一些处理.首先权限分为几种,大体分为两种,需要控制的功能和不需要控制的功能;其中需要控制又可以分为:登录功能,只要没有登录就能使用;以及不需要控制的功能,这部分功能,只要登录了,谁都能用,如使用主页和注销退出;而需要控制的功能,这部分的功能,只有有权限的才能操作,若删除/添加用户.



    对于登录和注销没有太复杂,都是跳转到一个页面就可以了.登录要求,输入用户名和密码,验证用户名和密码是否正确,错误显示错误信息,正确将用户信息加到值栈的session中,方便后面的使用,然后跳转到主页面.然后注销的话,先要将放到值栈的session中的用户对象移除.
    代码就是这样,在登录中先验证用户名和面是否正确,不正确的话,将错误信息添加到错误字段login中,而jsp页面用

<s:fielderror fieldName="login"></s:fielderror>

,就能接收到错误信息。若是成功了,就添加到session中,然后result为toIndex.然后注销的话,就是从session中remove,然后跳到logout.jsp页面.

@Controller  //交给容器管理
@Scope("prototype") //多例
public class UserAction extends BaseAction<User> {
	/** 登录UI **/
	public String loginUI() throws Exception {
		return "loginUI";
	}

	/** 登录 **/
	public String login() throws Exception {
		//2个请求
		User user = userService.findByLoginNameAndPassword(model.getLoginName(),model.getPassword());
		if(user == null) {
			addFieldError("login","用户名或密码不正确!");
			return "loginUI";
		}else {
			//登录用户
			ActionContext.getContext().getSession().put("user", user);
			return "toIndex";
		}	
	}
	
	/** 注销 **/
	public String logout() throws Exception {
		//1个请求
		ActionContext.getContext().getSession().remove("user");
		return "logout";
	}
	...
}
    其中,findByLoginNameAndPassword,验证用户名和密码.由于数据库中的密码加密了,所以要比较的话,传入的password也要进行加密处理.

@Service
@Transactional
public class UserServiceImpl extends DaoSupportImpl<User> implements UserService {
 
	public User findByLoginNameAndPassword(String loginName, String password) {
		//使用密码的MD5摘要进行对比,将传入的密码进行加密,将加密的密码查找user
		String md5Digest = DigestUtils.md5Hex(password);
		return (User)getSession().createQuery(//
				"FROM User u WHERE u.loginName=? AND u.password=?")//
				.setParameter(0,loginName)//
				.setParameter(1,md5Digest)//
				.uniqueResult();
	}	
}
    再次说明下,若错误要显示到jsp页面,,1.action要添加错误;2.jsp要接收显示错误.


   而对于result,为toIndex和logout和loginUI,在struts.xml中配置.开始的时候配置的是toIndex的type为redirectAction.但是后来报错,(There is no Action mapped for namespace / and action name index.jsp -[unknown location]).因为在Action中,没有写“/index.jsp”的方法,所以找不到对应的Action。所以这里type应该直接就是redirect,就直接是重定向到/index.jsp页面,而不是重定向到Action。

   而loginUI,由于登录只要没有登录都能登录,所以放到全局result配置中。

 <package name="default" namespace="/" extends="struts-default">
        ...
        <!-- 全局的result配置 -->
       <global-results>
      	 	<result name="loginUI">/WEB-INF/jsp/userAction/loginUI.jsp</result>
      	 	 ...
       </global-results>
       ...
	   <!-- 用户管理 -->
    	<action name="user_*" class="userAction" method="{1}">
    		...
    		<result name="logout">/WEB-INF/jsp/userAction/logout.jsp</result>
    		<result name="toIndex" type="redirect">index.jsp</result>
    	</action>
 </package>
    现在登录没有问题了,那么显示的主页面,该如何处理? 

主页面

    首先主页面的框架是这样子的.

    第一个frameset中,给第一行分配100px,第三行分配30px,剩下的都是中间的.然后第二个frameset,给第一列分配150px,剩下的都是第二列的.上面的比较好懂,下面的就是实际应用了.


    然后要求效果:在"left"中点击超链接,但在"right"中显示页面,这样写<a href="xxx.html" target="right">显示</a> 就可以了.
    然后看效果,输入地址写上home_index.action,就可以看到效果了.

   

    只是如何只输入index.jsp就能跳转到主页?如下,在index.jsp页面中,让他重定向到/home_index.action中就可以了.  


    现在可以看到左侧菜单是没有的,如何写呢?这里准备数据代码,以及显示的代码都不是很难.

左侧菜单

    需要注意,左侧的菜单都是权限,若是没有权限,应该看不到那个菜单.并且点击上级菜单,应该是可以展开和收缩菜单的.

    menu的下一个对象写上toggle(),顶级菜单就可以收缩和展开了.

    然后有权限的菜单才显示,只要加上if的判断,session中获取user信息,(#session,因为sessionMap栈中),然后将功能name传入,检查是否有这个功能,有就可以显示.


    并且,由于一级菜单是不会变化的,所以若是每次都查询就太浪费了,所以就把他缓存起来,只查一次.缓存在application中,既方便使用,也方便共享.而什么时候将数据缓存起来?在程序启动的时候就将数据缓存起来,所以要写监听器.
    写监听器就是先写一个自定义的监听器,该监听器要实现监听器接口,然后再在web.xml文件中配置监听器,由于该监听器需要使用Spring容器来注入对象,所以要写在Spring的监听器的下面.写完监听器之后,运行发现有懒加载异常.
    之前不用用OpenSessionInViewFilter解决了这个问题吗?因为之前的做法是,将session的关闭推迟到一个request请求之后结束,但是Tomcat启动的时候,初始化的这个,不是一个请求,所以将相关的懒加载去掉.

    然后,对于代码中注释掉的注解.现在没注释掉的写法是,serviceImpl的获取是通过获取到Spring容器,然后从容器中获取的.之前本来应该可以通过@Resource这样注入的,为什么在这里不行呢?

    首先对于serviceImpl来说,满足了2个条件,Spring能扫描到以及写了@Serivice的注解,所以Spring容器中是一定有的.而在InitListener中要注入service,InitListener也要写上注解@Component,交由容器来管理,这样才能顺利注入.但是即使这样做了,可以看看,web.xml中配置的监听器,写的是InitListener的全名,Tomcat实例化对象是通过new来实现的.尽管写上了注解,但是Tomcat不会用Spring中的,只会用它自己实例化的.所以,既然InitListener用的不是Spring,那么注入service就不行了.所以只能手动获取.

    而自己再new一个spring容器的话,本身已经有一个了,这样就有2spring容器.所以如何获取那个spring容器?

使用这个WebApplicationContextUtils工具,,传入一个指定的Key值就可以获取了,WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).

    最后效果就是这样


    现在左侧菜单已经显示了,并且还做了权限控制,没有权限的菜单就看不到.但是除了这个,还需要控制页面元素,对于某些功能没有权限的用户,应该控制一些链接看不到,如没有删除权限,则页面上的删除的链接应该看不到.

使用权限--显示有权限的链接

    权限的控制比较好做,只要和判断权限Name一样,判断url就可以.但是由于要控制的话,所有的a标签都需要进行权限的控制,所有的都要写这么一份代码,太费劲了,若是修改最原始的a标签,就可以去掉代码的重复了,但是如何修改源码?

    首先在struts-tags.tld,这个在struts-corejar,然后找到a标签,对应的类.


    不过源码肯定是改不了的,我们要如何做才能不改源码,但是达到该源码的效果?

    按照一个规则,若是jar包中和项目中有一个一样的名的类,那么会优先加载class文件中的,也就是我们项目中的.所以,在项目中添加一个包org.apache.struts2.views.jsp.ui和一个文件AnchorTag,然后重写doEndTag方法,在这里面,获取路径,并进行判断.

public class AnchorTag extends AbstractClosingTag {
     ...
    @Override
	public int doEndTag() throws JspException {
    	//当前登录用户
    	User user = (User)pageContext.getSession().getAttribute("user");
    	
    	//当前准备显示的链接对应的权限URL
    	//>>在开头加上'/'
    	String privUrl = "/"+ action;
      
    	if(user.hasPrivilegeByUrl(privUrl)) {
    		return super.doEndTag(); //正常的生成并显示超链接标签,并继续执行页面中后面的代码
    	} else {
    		return EVAL_PAGE;//什么都不做,只是继续执行页面中后面的代码 SKIP_PAGE:跳过页面中剩余的其他代码
    	}
    }   
     ...

    其中hasPrivilegeByUrl,是将url链接如

<s:acssClass="ForumPageTopic"action="forum_show?id=%{id}">${name}</s:a>,要去掉后面的id参数,只保留forum_show和数据库中的链接进行比较.如果不在数据库中,则可以登录,其中若是乱写一个action,因为在struts.xml中没有对应的配置,会直接报错;若在,就检查下该用户的角色是否有该权限.有就可以显示链接,否则就不显示.

/**
 * 判断本用户是否有指定url的权限
 * @param privUrl
 * @return
 */
public boolean hasPrivilegeByUrl(String privUrl) {
	//超级管理员有所有的权限
	if(isAdmin()) {
		return true;
	}		
	//>>去掉后面的参数
	int pos = privUrl.indexOf("?");
	if(pos > -1) {
		privUrl = privUrl.substring(0,pos);
	}
	//>>去掉UI后缀
	if(privUrl.endsWith("UI")) {
		privUrl = privUrl.substring(0,privUrl.length()-2);
	}   	
	//如果本URL不需要控制,则登录用户就可以使用
	Collection<String> allPrivilegeUrls =(Collection<String>) ActionContext.getContext().getApplication().get("allPrivilegeUrls");
	if(!allPrivilegeUrls.contains(privUrl)) {
		return true;
	} else {		
		//普通用户要判断是否含有这个权限
		for(Role role:roles) {
			for(Privilege priv:role.getPrivileges()) {
				if(privUrl.equals(priv.getUrl())) {
					return true;	
				}
			}
		}
	}
	return false;
}

   到目前为止,权限已经控制的很好了.可以控制菜单的是否显示,以及页面链接元素的是否显示.还有一种需要控制,就是是否登录的控制,对于所有没有登录的,必须要让其去登录,登录成功才能访问。

拦截验证所有请求的权限

    所以,要拦截所有的请求,进行是否登录的判断,没有登录的跳转到登录页面,让其登录,登录了就要看请求是否有权限,没有权限就跳转到“没有权限的”提示页面。当然这个拦截器要放在所有拦截器的最外面,第一个进行判断。整个原理是这样的,

  

    先写一个拦截器,然后配置到struts.xml,由于需要Action在的请求最开始就拦截,所以要重新用一个默认拦截器栈,将验证权限放在最上面.
    代码,拦截器要继承AbstractInterceptor 抽象的拦截器,然后获取到当前登录用户,以及从invocation中获取url,如果还没有登录,正要登录,就不用权限控制,放行;如果不是就去登录;如果已经登录,就判断是否有权限,有就放行,没有就转到noPrivilegeError.jsp页面,提示"没有权限访问此功能".

public class CheckPrivilegeInterceptor extends AbstractInterceptor {

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		//获取信息
		User user = (User)ActionContext.getContext().getSession().get("user");//当前登录用户
		String namespace = invocation.getProxy().getNamespace();
		String actionName = invocation.getProxy().getActionName();
		String privUrl = namespace+actionName;//对应的权限URL
		
		//如果未登录,就转到登录页面
		if(user == null) {
			//如果是去登录,则放行
			if(privUrl.startsWith("/user_login")) {
				return invocation.invoke();
			}else {
				//如果不是就转到登录页面
				return "loginUI";
			}
		}
		//如果已登录,就判断权限
		else {
			if(user.hasPrivilegeByUrl(privUrl)) {
				//如果有权限,就放行
				return invocation.invoke();
			}else {
				//如果没有权限,就转到提示页面
				return "noPrivilegeError";
			}
		}
	}
}

    然后配置struts.xml,配置拦截器栈.

<interceptors>
       	<!--  声明有一个拦截器 -->
       	<interceptor name="checkPrivilege" class="cn.itcast.oa.util.CheckPrivilegeInterceptor"></interceptor>
      		
      	<!-- 重新定义默认的拦截器栈 -->
      	<interceptor-stack name="defaultStack">
      		<interceptor-ref name="checkPrivilege"></interceptor-ref>
      		<interceptor-ref name="defaultStack"></interceptor-ref>
      	</interceptor-stack>
</interceptors>
    效果就是这样.没有登录的效果就是跳到登录页面,没有权限的,就跳到提示页面。

    权限的功能基本就完了。下面的论坛管理中的上下移动功能。

上下移动

    对于版块需要有排序,并且可以修改顺序。所以,有上移和下移的功能。效果就是这样


    最上面的不能上移,最下面的不能下移;并且点击上移和下移就可以修改位置。做法一般有两种,1种是前台JS修改顺序,然后再进行保存;还有就是这种,上移和下移是直接和后台交互。

   下面先上JSP页面的代码,其中iterator有一个属性为status状态,然后keystatus,然后用firstlast就能判断是不是iterator中的第一条或者最后一条记录.

<style type="text/css">
    .disabled{
    	color: gray;
    	cursor: pointer;
    }
</style>

<s:iterator value="#forumList" status="status">
		<tr class="TableDetail1 template">
			<td>${name} </td>
			<td>${description} </td>
			<td>
				<s:a action="forumManage_delete?id=%{id}" οnclick="return delConfirm()">删除</s:a>
				<s:a action="forumManage_editUI?id=%{id}">修改</s:a>
					
				<!-- 最上面的不能上移 -->
				<s:if test="#status.first">
					<span class="disabled">上移</span>
				</s:if>
				<s:else>
					<s:a action="forumManage_moveUp?id=%{id}">上移</s:a>
				</s:else>
					
				<!-- 最下面的不能下移 -->
				<s:if test="#status.last">
					<span class="disabled">下移</span>
				</s:if>
				<s:else>
					<s:a action="forumManage_moveDown?id=%{id}">下移</s:a>
				</s:else>		
			</td>
		</tr>
</s:iterator>
    而status,对应的是IteraotrStatus,可以看到first和last的大概说明。

    现在前台代码准备好了,写后台的代码。Action中的代码,就是调用service中的moveUp和moveDown,所以直接上serviceImpl中的moveUp和moveDown代码.思路就是:对于上移,获取当前选中的,然后通过sql语句,找到当前上面的那个,然后交换他们的position字段的值.

/**
 * 上移
 */
public void moveUp(Long id) {
	//上移,找出相关的Forum,
	Forum forum = getById(id);//当前要移动的Forum
	//找到当前Forum上面的Forum,
	Forum other = (Forum) getSession().createQuery(//
						"FROM Forum f WHERE f.position<? ORDER BY f.position DESC")//
						.setParameter(0, forum.getPosition())//
						.setFirstResult(0)//
						.setMaxResults(1)//
						.uniqueResult(); //我上面的那个Forum
	//最上面的不能上移
	if(other == null) {
		return;
	}
	
	//交换position的值
	int temp = forum.getPosition();
	forum.setPosition(other.getPosition());
	other.setPosition(temp);
	 	
	//更新到数据库中,可以不写,因为对象现在是持久化状态,会自动更新
	getSession().update(forum);
	getSession().update(other);
}

/**
 * 下移
 */
public void moveDown(Long id) {
	//下移,找出相关的Forum,
	Forum forum = getById(id);//当前要移动的Forum
	//找到当前Forum的下面一个Forum
	Forum other = (Forum) getSession().createQuery(//
						"FROM Forum f WHERE f.position>? ORDER BY f.position ASC")//
						.setParameter(0, forum.getPosition())//
						.setFirstResult(0)//
						.setMaxResults(1)//
						.uniqueResult(); //我上面的那个Forum
	//最上面的不能上移
	if(other == null) {
		return;
	}
	
	//交换position的值
	int temp = forum.getPosition();
	forum.setPosition(other.getPosition());
	other.setPosition(temp);
	
	//更新到数据库中,可以不写,因为对象现在是持久化状态,会自动更新
	getSession().update(forum);
	getSession().update(other);
}
     这样上下移动也就可以完成了.

    第六天的总结,写完了,深刻了解了不少权限控制的操作,还有上下移动,还有最后一篇就完了,加油!


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值