1、用户登录认证自定义拦截器编码
用户登录的校验,通过拦截器来进行过滤
这里肯定是通过Struts2的拦截器进行功能的实现了。intecepter
MethodFilterIntercepter拦截器增强:可以对action里面的业务方法进行拦截设置。
MethodFilterIntercepter有两个属性
protected Set<String> excludeMethods = Collections.emptySet(); protected Set<String> includeMethods = Collections.emptySet();
excludeMethods - 要从拦截器处理中排除的方法名称
includeMethods - 要包含在拦截器处理中的方法名称
如果方法名称在includeMethods和excludeMethods中都可用,则它将被视为包含的方法:includeMethods优先于excludeMethods
那么现在要做的就是进行loginInteceptor拦截器的编写,以及对login方法的放行。
(1)LoginInteceptor类(拦截器,判断是否登录。未登录的返回到登录界面。无法跳过登录直接进入系统界面)
1、继承MethodFilterInterceptor(注意,这是一个类而不是接口)
@SuppressWarnings("all") @Component("loginInterceptor") public class LoginInteceptor extends MethodFilterInterceptor{ @Override protected String doIntercept(ActionInvocation invocation) throws Exception { //判断用户是否登录,从session中找用户信息 User existUser=(User)ServletActionContext.getRequest().getSession().getAttribute("exitUser"); //未登录,返回未登录错误码 if(existUser==null) { return "no_login"; }else { //如果已经登录了,调用下一个组件。通过invocation去调用invoke方法。 return invocation.invoke(); } } }
如果session中有用户信息就证明以及登录过了。调用下一个组件,通过invocation的invoke方法调用。
拦截器由Struts2的前端控制器来创建,交由spring进行管理。这里拦截器设置成单例就可以了。
2、在struts.xml中进行注册
不能讲拦截器写在action标签中,这样只有走这个action的时候才会执行拦截器。
要写在package标签中
<package name="bos" extends="json-default"> <!-- 注册拦截器 --> <interceptors> <!-- 通过伪类名来注册拦截器,因为是由spring创建的,所以使用@Component("loginInterceptor")的名字就可以了,一定要一致 。完成注册。放入栈中。这个拦截器对所有action都有效 --> <interceptor name="myLogin" class="loginInterceptor"> </interceptor> <!-- 栈 加一个是默认栈,必须要加。 --> <interceptor-stack name="mystack"> <!-- 自己定义拦截器方法拦截设置 --> <interceptor-ref name="myLogin"> <!-- 这里一定要写excludeMethods,执行login方法的时候不会执行拦截器操作。 --> <param name="excludeMethods">login</param> </interceptor-ref> <!-- 名称必须是defaultStack :默认栈 --> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 拦截器作用域。注册完之后启动,拦截器的作用域。需要对所有拦截器都有效 .它对这个包里面的所有的action都有效。但是userAction也在因为设置了它的父包是bos @ParentPackage("bos"),因此对它的UserAction下的方法也有效。 但是由于login方法需要走拦截器,如果login方法都需要登录的话,是永远登录不上的。 如果登录就会提示找不到方法,登录不上。 --> <!-- 不写这段代码的话就相当于拦截器没有执行,只是定义了 --> <default-interceptor-ref name="mystack"></default-interceptor-ref> <!-- 定义全局结果集视图 --> <global-results> <result name="no_login" type="redirect">/login.jsp</result> </global-results> <action name="index"> <result>/index.jsp</result> </action> <!-- 需要进行权限控制的页面访问 --> <action name="page_*_*"> <result type="dispatcher">/WEB-INF/pages/{1}/{2}.jsp</result> </action> </package>
package
Content Model : (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-
class-ref?, global-results?, global-exception-mappings?, action*)
按照这个来编写顺序。
首先进行来loginInceptor注册。这里要通过伪类名进行注册,因为是有spring进行创建的。@Component("loginInterceptor")
<interceptor name="myLogin" class="loginInterceptor"></interceptor>
所以这里的class填写@Component的名称就可以了。
拦截器栈:
<!-- 栈 加一个是默认栈,必须要加。 --> <interceptor-stack name="mystack"> <!-- 自己定义拦截器方法拦截设置 --> <interceptor-ref name="myLogin"> <!-- 这里一定要写excludeMethods,执行login方法的时候不会执行拦截器操作。 --> <param name="excludeMethods">login</param> </interceptor-ref> <!-- 名称必须是defaultStack :默认栈 --> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack>
在栈内添加自定义的拦截器,并设置这个拦截器对login方法放行。这里的name一定要完全写excludeMethods,MethodFilterInteceptor的属性名称。
然后本地栈一定要进行添加。
<!-- 名称必须是defaultStack :默认栈 --> <interceptor-ref name="defaultStack"></interceptor-ref>
这样是完成了拦截器的注册,但是需要进行拦截器作用域的设置,否则该拦截器是不生效的。它对这个bos包中的所有action都有效。由于UserAction因为设置了父包为bos。
@ParentPackage("bos")
因此它对UserAction下的方法也有效。所以UserAction的login方法需要走拦截器,但是如果login方法也要走loginInteceptor拦截器的话,那它永远是登录不上的。所以要添加
在这个拦截器中添加excludeMethods
拦截器作用域,这句话是所有action自动调用的拦截器栈。(个人理解为将自己设置的栈添加到默认拦截器栈中,所有页面都要走这个拦截器)
<default-interceptor-ref name="mystack"></default-interceptor-ref>
在未检测到session中有用户信息的时候。返回no_login.定义它的全局结果集。
<!-- 定义全局结果集视图 --> <global-results> <result name="no_login" type="redirect">/login.jsp</result> </global-results>
及:未通过登录直接跳转到/login.jsp页面。
2、在页面显示用户的用户名和IP
[<strong>超级管理员</strong>],欢迎你!您使用[<strong>192.168.1.100</strong>]IP登录!
[<strong>超级管理员</strong>],欢迎你${sessionScope.existUser.username}! 您使用[<strong>${pageContext.request.remoteAddr}</strong>]IP登录!
显示从session中获取的数据。
3、修改密码的正则校验
首先一个弹窗:EasyUI提供了这种功能:
窗体里显示用户的一些信息
在点击修改密码的时候弹出一个窗口。
function editPassword() { $('#editPwdWindow').window('open'); }
添加点击事件 <div class="easyui-layout" fit="true"> <div region="center" border="false" style="padding: 10px; background: #fff; border: 1px solid #ccc;"> <table cellpadding=3> <tr> <td>新密码:</td> <td><input id="txtNewPass" type="Password" class="txt01" /></td> </tr> <tr> <td>确认密码:</td> <td><input id="txtRePass" type="Password" class="txt01" /></td> </tr> </table> </div> <div region="south" border="false" style="text-align: right; height: 30px; line-height: 30px;"> <a id="btnEp" class="easyui-linkbutton" icon="icon-ok" href="javascript:void(0)" >确定</a> <a id="btnCancel" class="easyui-linkbutton" icon="icon-cancel" href="javascript:void(0)">取消</a> </div> 其中确定的ID id="btnEp"
修改密码需要定义一些东西:比如,不能不输入,两次输入要一致,密码长度限制
这些限制和校验都是在确定里面做的。
确定添加点击事件。
$("#btnEp").click(function(){ //修改密码的实现方法 //1、获取新密码,判断这个密码是否有效 JS的校验 (是否为空。特殊字符,长度) var newPwd=$("#txtNewPass").val(); if(newPwd==""||newPwd==null){ $.messager.alert("警告","新密码必须填写","warning"); return; } //空白字符 var reg=/s+/; //+代表一个。这里是有一个或多个空白字符就会被检测到 if(reg.test(newPwd)){ $.messager.alert("警告","密码不能包含空格","warning"); return; } //定义长度,不在这个长度范围的就会提示 if(newPwd.length<=0||newPwd.length>=7){ $.messager.alert("警告","密码为1-6位","warning"); return; } //2、重复密码判断 两次密码要求一致 新旧密码不一致就会提示 if(newPwd!=$("#txtRePass").val()){ $.messager.alert("警告","密码不一致","warning"); return; } //3、密码一致 有效 发送ajax请求给action --->service--->Dao 数据库 当前用户密码的update操作 //4、关闭窗口 });
4、当前action的父包是bos,而bos继承“json-default” ,所以可以用json格式
editPassword方法
@Action(value = "userAction_editPassword",results= {@Result(name="editPassword",type="json")}) public String editPassword() { try { User existUser=(User)getSessionAttribute("existUser"); serviceFacade.getUserService().editPassword(model.getPassword(),existUser.getId()); push(true);//压栈 }catch (Exception e) { e.printStackTrace(); push(false); //给到页面 } return "editPassword"; }
//修改密码 spring data jpa @Modifying @Query("update User set password=?1 where id=?2") //只有这个不能修改,因为这个是查找,必须加@Modifying才可以修改 public void editPassword(String password, String id);
这里必须添加 @Modifying注解,因为@Query是只限于查询。
涉及到增删改的都要添加事务。在配置文件中添加jps事务管理。
<!-- spring data jpa 事务管理 配置 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
//打开注解扫码
<tx:annotation-driven transaction-manager="transactionManager"/>
5、json-plugin插件原理说明
@Action(value = "userAction_editPassword",results= {@Result(name="editPassword",type="json")}) public String editPassword() { try { User existUser=(User)getSessionAttribute("existUser"); serviceFacade.getUserService().editPassword(model.getPassword(),existUser.getId()); push(true); }catch (Exception e) { e.printStackTrace(); push(false); //给到页面 } return "editPassword"; }
上面的代码是修改了密码后将结果集压栈。所以在栈顶的就是true/fasle。
结果集使用的是json-default,
action的父包是bos,而bos又是json-default的子包。
<package name="bos" extends="json-default">
所以type=json的底层就是走下面的这个结果集
<result-types>
<result-type name="json" class="org.apache.struts2.json.JSONResult"/>
</result-types>
底层是通过response返回ajax请求,数据来自createJSONString(HttpServletRequest,Object);
会判断是否有用户自定义配置,如果有用户自定义配置就找到名称为root的值,obj。如果没有就去值栈中获取栈顶的元素。
6、 取派员表和实体类生成
利用power designer生成数据表,然后通过反向生成实体类来建立表。
先配置reveng.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd" > <hibernate-reverse-engineering> <!-- 以下标签是Oracle使用的 --> <!-- <schema-selection match-table="T_USER" match-schema="SH1208"/> --> <!-- <schema-selection match-table="BC_.*" match-schema="SH1208"/> --> <!-- <table name="BC_DECIDEDZONE" schema="SH1208" --> <!-- class="com.zero.bos.domain.bc.DecidedZone"> --> <!-- <primary-key> --> <!-- <generator class="uuid"></generator> --> <!-- </primary-key> --> <!-- </table> --> <!-- match-table:访问表的 (代表这个表能够被找到).对于数据库zero_bos下面t_user表定义实体类生成规则 --> <!-- 可以被扫码到 --> <schema-selection match-table="t_user" match-catalog="zero_bos" /> <!--.代表任意字符,*代表0或多个 --> <schema-selection match-table="bc_.*" match-catalog="zero_bos" /> <!-- 实体类生成规则定义, --> <table name="bc_staff" catalog="zero_bos" class="com.zero.bos.domain.bc.Staff"> <primary-key> <!-- uuid主键(字符) identity:主键自增长 --> <generator class="uuid"></generator> </primary-key> </table> <table name="bc_decidedzone" catalog="zero_bos" class="com.zero.bos.domain.bc.DecidedZone"> <primary-key> <!-- uuid主键(字符) identity:主键自增长 --> <generator class="uuid"></generator> </primary-key> </table> <table name="bc_region" catalog="zero_bos" class="com.zero.bos.domain.bc.Region"> <primary-key> <!-- uuid主键(字符) identity:主键自增长 --> <generator class="uuid"></generator> </primary-key> </table> <table name="bc_subarea" catalog="zero_bos" class="com.zero.bos.domain.bc.Subarea"> <primary-key> <!-- uuid主键(字符) identity:主键自增长 --> <generator class="uuid"></generator> </primary-key> </table> <!-- 实体类生成规则定义, --> <table name="t_user" catalog="zero_bos" class="com.zero.bos.domain.user.User"> <primary-key> <!-- uuid主键(字符) identity:主键自增长 --> <generator class="uuid"></generator> </primary-key> </table> </hibernate-reverse-engineering>
然后maven执行:hibernate3:hbm2java
建模完成。
7、取派员的增加
点击保存按钮,实现取派员信息的添加。
1 <div region="north" style="height:31px;overflow:hidden;" split="false" border="false" > 2 <div class="datagrid-toolbar"> 3 <a id="save" icon="icon-save" href="#" class="easyui-linkbutton" plain="true" >保存</a> 4 </div> 5 </div> 6 7 <div region="center" style="overflow:auto;padding:5px;" border="false"> 8 <form id="addStaffForm" action="${pageContext.request.contextPath }/bc/staffAction_save" method="post"> 9 <table class="table-edit" width="80%" align="center">
对click添加点击事件
编写action代码
// 取派员添加 @Action(value = "staffAction_save", results = { @Result(name = "save", location = "/WEB-INF/pages/base/staff.jsp") }) public String save() { try { serviceFacade.getStaffService().save(model); } catch (Exception e) { e.printStackTrace(); push(false); } return "save"; }
转发是可以到web-inf目录下的,重定向不行。
父类中注入门面类
// 父类中 注入 门面业务层
@Autowired
protected FacadeService serviceFacade;
DAO继承
public interface StaffDao extends JpaRepository<Staff, String> { }
完成
JavaEE 开发 1: 客户端 页面 js 编写 (easyui +jquery) 2:服务器端: 接受请求数据? Action( 类名必须以Action 结尾 包名一定要包含 action|actions|struts|struts2 @Controller @Scope @Namespace @ParentPackage) -service (注解开发 接口+实现类(@Service @Transaction) dao 接口 继承 JPARepostitory ) 3:结果集跳转 (转发跳转 WEB-INF 查询: ajax)
8、取派员的分页查询和显示
(1)首先页面使用的假数据格式是这样的
{ "total":100, "rows":[ {"id":"001","name":"李大","telephone":"13912345678","haspda":"1","deltag":"0","standard":"10-20公斤","station":"杭州分部"}, {"id":"002","name":"李二","telephone":"13912345678","haspda":"1","deltag":"0","standard":"10-20公斤","station":"杭州分部"} ] }
所以我们后台返回的数据也要是这种格式的才可以正常显示。
(2)easyui对分页查询的实现
从服务器端拿json格式数据。上面的数据就是easyui框架需要的分页数据
要求服务器端是数据格式要有:(1)total:取派员总记录数
(2)rows:每页记录数 [{},{}] 要求是数组
然后ajax json数据回送 easyui插件自动实现分页
客户端:进行分页查询的时候,客户端必须提供 请求页码和记录数
在点击下一页的时候就会向url发送请求,并且带参数
page和rows,这个插件在进行分页查询 时候,它会自动的向我们的datagrid的url地址发送最新的页码和每页记录数
然后后台代码会进行数据库查询再返回数据
分页查询的结论:客户端 url:实际的服务器请求地址
url : "${pageContext.request.contextPath}/bc/staffAction_pageQuery",
服务器端:BaseAction接收page和rows即可。
staffAction---->service--->dao-->List<Staff>
但是List<Staff>序列号会变成
[{},{},{}] 缺少total
所以服务器端要对其进行扩展
这种数据格式最典型的就是map
所以要把数据往map里放
map.putTotal
map.putData
栈顶--->json-plugin
自动map--->json 字符串
功能实现:
(1)在BaseAction中添加
// 分页操作 接受页面 和 每页显示记录 protected int page;// 页码 protected int rows;// 每页显示记录数 //struts2属性注入 将请求数据 自动注入 public void setPage(int page) { this.page = page; } public void setRows(int rows) { this.rows = rows; }
(2)将page和rows传递给StaffAction
// 取派员分页查询 @Action(value = "staffAction_pageQuery", results = { @Result(name = "pageQuery", type="json") }) public String pageQuery() { Map<String,Object> data=new HashMap<String,Object>(); try { PageRequest pageable=new PageRequest(page-1, rows); Page <Staff> pageData=serviceFacade.getStaffService().pageQuery(pageable); //dao 参数 Pageable pageable 自动完成分页查询 将分页结果数据 自动封装到Page<T>,所以这里不能是List<Staff>,否则无法封装 //从Page 对象获取总记录数 和 每页分页记录数List<Staff> data.put("total", pageData.getTotalElements()); data.put("rows", pageData.getContent()); push(data); } catch (Exception e) { e.printStackTrace(); push(false); } return "pageQuery"; }
底层两个参数page和rows走的是dao层。
StaffDao是继承JpaRepository<Staff, String>
而JpaRepository接口继承PagingAndSortingRepository
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
---------- @NoRepositoryBean public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { ---------- @NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { 这里就可以看到PagingAndSortingRepository做的分页。所以要实现分页功能,要按它的要求提供相关的参数。 `@NoRepositoryBean public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { /** * Returns all entities sorted by the given options. * * @param sort * @return all entities sorted by the given options */ Iterable<T> findAll(Sort sort); /** * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object. * * @param pageable * @return a page of entities */ Page<T> findAll(Pageable pageable); }` ----------
需要 Pageable,给它这个,它就会自动完成分页。结果数据
`Page <Staff> pageData=serviceFacade.getStaffService().pageQuery(page,rows);`
这里的Pageable也是一个接口。所以要看它有哪些实现类和方法。
它只有一个PageRequest实现类。
为spring jpa服务,要讲页码和每页记录数封装到Pageable
easyui要的map格式,所以要把page对象放到map中,这样就可以序列化成想要的样式。
在么有配root的情况下,easyui从栈顶拿序列化的数据。
为什么不能直接序列化pageData而是要先放入map再进行序列化。
如果此时添加取派员,数据库会增加,但是前端页面并不会显示添加的信息。
同时后台会报错,延迟加载错误。
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.zero.bos.domain.bc.Staff.decidedZones, no session or session was closed
典型的延迟加载的错误。
问题原因:
前面每页10条的时候不需要分页,每页去查下一页。现在更改为每页1条时,就需要查下一页。会用到limit
每页立刻去发送语句查询。
原理:事务管理 ---service层
事务结束 session就没有了
延迟数据(entitymanager)在的一级缓存区里。
没有分页查询的语句,只有记录总数的。
对staff的this.decidedZones = decidedZones;是延迟的,不用的时候不加载。
StaffAction Page<Staff> ----- >延迟的定区数据set
action 没有延迟数据只有Staff数据
会去找是
不掉get方法就不会序列号
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "staff") @JSON(serialize=false) public Set<DecidedZone> getDecidedZones() { return this.decidedZones; }
问题2:
但是这个只有只有一页,而且显示的数据并不是第一条的数据。
查询是从第二页查的,而不是第一页。
问题出在action中。
在PageRequest源码看就会知道,它的页面是从0开始的。第一页的下标是0,所以这里我们让它-1就可以了。
但是总页面还是不对。那么就是数据格式显示的问题,total数据没有传递过来。
问题3:当我们点击取派员的时候,格式显示的不正确,
原因是jps默认的起始页是0.所以page要-1.
Page<Staff> 没有有延迟的那个数据,只有satff的数据,没有延迟的数据。
数据放入值栈, json-plugin序列号数据
调用Staff实体类所有的getter 获取数据才能json字符生成
但是集合 延迟数据已经没有了,没有session了。就会报错。
@JSON 插件从值栈中获取数据 进行json字符串生成的时候 调用目标对象get方法!
Serialize=false 插件不会调用 get 方法获取数据
Page<T> findAll= ;一行就实现了分页。
9、小结:
hibernate默认是lazy,懒加载。没有向数据库发数据,但是缓存有。
有这些数据都是基于事务的,一旦事务结束。数据就没有了。
或者可以把事务放到web层,放大。web层一直存在。