关于前后端值的传递变化
ajax的load函数说明
jquery中的load函数为一个异步加载的ajax函数
此函数用于在指定位置异步加载资源(并将返回的内容添加到指定位置)
例如: $(“#mainContentId”).load(url);
等效于下面:
$.get(url,function(result){
$(“#mainContentId”).html(result)
})
关于实体类实现Serializable接口以及添加序列化版本id的说明
首先,建议:所有用于封装数据的对象都建议实现序列化接口(Serializable)
1)序列化:将对象转换为字节的过程称之为对象序列化
2)反序列化:将字节转换为对象的过程称之为反序列化
3)应用场景:对对象进行缓存,将对象进行钝化(写入文件),将对象通过网络进行传输
为什么要实现序列化id
对象在序列化和反序列化时会基于此id进行数据处理.。
将对象序列化时会将这个id作为版本写入到字节中。
将字节反序列化会从字节中提取这个版本id然后和类中的版本id进行比对,一致则进行反序列化。
project–clean和run as–maven clean的区别
project clean:是将编译好的.class文件删除并重新编译.java文件,即表示重新编译项目
右键项目run as–maven clean:是将maven本地仓库的jar包和target下的目录删除
几种封装数据的对象
在Java语言,可以将当讲内存中的对象分为两大类
1)存储数据的对象(设计的关键在属性上),典型的pojo(简单的Java对象),
bo(封装业务层数据)
vo(视图层封装数据的对象,通常是web向模板渲染引擎层传输的对象,比如model),不同企业可能定位不一样,有的称作是value object即值对象,有的是view object即显示层对象
do(数据层封装数据的对象,和数据库表结构一一对应)
而pojo是do/bo/vo的统称
2)执行业务的对象(设计的关键在方法上),典型的controller,server,dao
关于自定义异常
自定义非检查异常,应使用有业务含义的自定义异常
目的:对业务中的信息进行更好的定位和反馈
前端定义请求参数的两种格式格式
var params="pageCurrent=1"
var params={"pageCurrent":1};
第二种方式添加参数的方式:params.username=uname;
为批量元素注册事件
on函数用于在指定HTML元素上注册事件,当点击HTML元素内部的子元素时可以触发事件
$(function(){
$("#pageId")
.on("click",".first,.pre,.next,.last",doJumpToPage)
})
关于data函数,
data函数为jquery中的一个数据存储函数,每个dom对象都有一个这样的函数。
data函数语法:data(key[,value]),key和value由自己指定
data函数只有key时表示取值,既有key,又有value表示存储值
例如:
$("#pageId").data("pageCurrent",pageObject.pageCurrent);
$("#pageId").data("pageCount",pageObject.pageCount);
关于EL表达式${}的注意事项
springboot项目默认好像不能直接使用${}输出定义域中的数据、
${}好像只能在静态页面上使用,如果要是在js中使用要添加双引号
而且这种形式的写法在很多地方都有应用,比如在jstl中,thmyeleaf中,都会使用这种形式接受数值,而且还可以用做es6新增语法之${}:这是es6中新增的字符串方法。可以配合反单引号完成拼接字符串的功能,这样就不用使用加号和双引号拼接字符串;
关于js函数中$(this)的使用
在事件注册函数中使用
在数组遍历中使用
关于MD5加密算法
是一种消息摘要加密算法,其特点是:
1)不可逆,但是可以暴力破解
2)相同内容加密结果相同
关于resultMap的使用
resultMap是mybatis中用于实现高级映射的一个非常重要的元素,主要应用在自定义映射规则的设计,例如字段的自定义映射,表嵌套查询映射,表关联查询映射
应用场景:
1)表中字段名与内存中的映射对象属性不一致(set方法不匹配)
2)表关联查询映射,表嵌套查询映射。
<resultMap type="com.cy.pj.sys.pojo.SysRoleMenu" id="sysRoleMenu">
<!-- id元素用于实现主键字段映射:尤其是当在再次基于主键值执行其它查询,
建议将id主键值进行手动映射 -->
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="note" column="note"/>
<association property="sysDept"
column="deptId"
select="com.cy.pj.sys.dao.SysDeptDao.findById">
</association>
<collection property="menuIds"
column="id"
select="com.cy.pj.sys.dao.SysRoleMenuDao.findMenuIdsByRoleId"/>
</resultMap>
id节点:id元素用于实现主键字段映射:尤其是当在再次基于主键值执行其它查询,id字段将不会被查询到,所以建议将id主键值进行手动映射,
result节点:这里的property和column属性和id节点的作用一样,都是实现表中字段和对象属性的映射。
association节点:一般应用于many2one或one2one做关联查询,将column中的字段值作为条件发起子查询,并将结果映射到对象的SysDept属性中,在当前应用是基于deptId查询部门信息并将其存储到SysUserDeptVo对象的sysDept属性中。
collection节点:和association效果差不多,都是为了实现关联查询的,但是它是应用于many2many的查询。
AOP——面向切面编程详细介绍
什么是AOP:(切入点方法执行的时候执行拓展业务;代理对象执行切面方法)
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
@Aspect 注解描述的类型为Spring AOP中的切面对象类型。此对象中可以封装:
- 1)切入点(定义在哪些目标对象的哪些方法上进行功能扩展)
- 2)通知(封装功能扩展的业务逻辑)
代码示例:
@Slf4j
@Aspect
@Component
public class SysLogAspect {
/**
* PointCut注解用于定义切入点,具体方式可以基于特定表达式进行实现。例如
* 1)bean为一种切入点表达式类型
* 2)sysUserServiceImpl 为spring容器中的一个bean的名字
* 这里的涵义是当sysUserServiceImpl对象中的任意方法执行时,都由本切面
* 对象的通知方法做功能增强。
*/
@Pointcut("bean(sysUserServiceImpl)")//连接点的集合是切入点,这里边的每一个方法都是一个连接点
public void doLogPointCut() {}//此方法中不需要写任何代码
/**环绕通知:此环绕通知使用的切入点为bean(sysUserServiceImpl)
* 环绕通知特点:
1)编写:
a)方法的返回值为Object.
b)方法参数为ProceedingJoinPoint类型.
c)方法抛出异常为throwable.
2)应用:
a)目标方法执行之前或之后都可以进行功能拓展
b)相对于其它通知优先级最高。
@param jp 为一个连接对象(封装了正在要执行的目标方法信息)
@return 目标方法的执行结果
*/
@Around("doLogPointCut()")//也可以这样写:@Around("bean(sysUserServiceImpl)")
public Object around(ProceedingJoinPoint jp)throws Throwable{
log.info("method start {}",System.currentTimeMillis());
try {
Object result=jp.proceed();//最终会执行目标方法
log.info("method end {}",System.currentTimeMillis());
return result;
}catch(Throwable e) {
log.error("method error {},error msg is {}", System.currentTimeMillis(),e.getMessage());
throw e;
}
}
}
过程总结:
当发现有被@Aspect注解修饰的类的时候,spring会为类中的切入点类创建指定的代理对象,并在代理类对象中通过反射调用切面类中的环绕通知方法,目的是在对象运行时动态织入一些扩展功能或控制对象执行 ,其中切面类就是为了封装扩展功能。这里的代理对象有两类:jdk代理(通过组合方式创建代理对象)和CGLIB代理(通过继承方式创建代理对象)。
**实现方式:**代理对象调用切面方法做功能增强,有两种代理方式:
使用jdk代理:
使用CGLIB代理
Spring AOP底层基于代理机制实现功能扩展:
1.假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口)。
2.假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)。
- 切面通知类型优先级
@AroundBefore>@Before>@AroundAfter>@After>@AfterThrowing或者@AfterReturning - 异常切面对象@AfterThrowing的使用
代码示例:
/**
* 定义一个异常切面对象,在此切面的通知方法中实现异常信息的记录
* @author qilei
* 记住:一个切面对象中不是一定要写上所有的通知方法,具体用什么方法由业务而定。
*/
@Slf4j
@Component
@Aspect
public class SysExceptionAspect {
/**
* 通过这个通知方法实现一个简易的异常信息的记录
* @param jp 连接点对象,此对象封装了你要执行的目标方法对象
* @param e 执行目标方法时出现的异常,这个名字一定要
* 与@AfterThrowing中的throwing属性值相同。
*/
@AfterThrowing(value="bean(*ServiceImpl)",throwing = "e")
public void doRecordExceptionMsg(JoinPoint jp,Throwable e) {
//通过连接点对象获取正在执行的目标对象类型名称
String targetClassName=jp.getTarget().getClass().getName();
//通过连接点对象获取正在执行的方法的方法签名
MethodSignature ms=(MethodSignature)jp.getSignature();
log.error("{} invoke exception msg is {}",targetClassName+"."+ms.getName(),e.getMessage());
}
}
如何为业务操作添加日志信息记录
首先创建一个切面类,在类中定义切入点,并制定切入点的通知方法。目标对象方法执行结束后,在通知方法里可以通过参数ProceedingJoinPoint 来获取所调用的业务方法信息以及方法所在类的信息,根据这些信息可以创建日志类对象,将日志对象保存到数据库中。切面类代码实例如下:
package com.cy.pj.common.aspect;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.util.IPUtils;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
/**
* @Aspect 注解描述的类型为Spring AOP中的切面对象类型。此对象中可以封装:
* 1)切入点(定义在哪些目标对象的哪些方法上进行功能扩展)
* 2)通知(封装功能扩展的业务逻辑)
*/
//@Slf4j
@Order(1)
@Aspect
@Component
public class SysLogAspect {
private static final Logger log =
LoggerFactory.getLogger(SysLogAspect.class);
/**
* PointCut注解用于定义切入点,具体方式可以基于特定表达式进行实现。例如
* 1)bean为一种切入点表达式类型
* 2)sysUserServiceImpl 为spring容器中的一个bean的名字
* 这里的涵义是当sysUserServiceImpl对象中的任意方法执行时,都由本切面
* 对象的通知方法做功能增强。
*/
@Pointcut("bean(sysUserServiceImpl)")
//注解方式的切入点表达式
//@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
public void doLogPointCut() {}//此方法中不需要写任何代码
/**由@Around注解描述的方法为一个环绕通知方法,我们可以在此方法内部
* 手动调用目标方法(通过连接点对象ProceedingJoinPoint的proceed方法进行调用)
* 环绕通知:此环绕通知使用的切入点为bean(sysUserServiceImpl)
* 环绕通知特点:
1)编写:
a)方法的返回值为Object.
b)方法参数为ProceedingJoinPoint类型.
c)方法抛出异常为throwable.
2)应用:
a)目标方法执行之前或之后都可以进行功能拓展
b)相对于其它通知优先级最高。
@param jp 为一个连接对象(封装了正在要执行的目标方法信息)
@return 目标方法的执行结果
*/
@Around(value="doLogPointCut()")
public Object around(ProceedingJoinPoint jp)throws Throwable{
System.out.println("SysLogAspect.@Around.before");
long start=System.currentTimeMillis();
log.info("method start {}",start);
try {
Object result=jp.proceed();//最终会执行目标方法(sysUserServiceImpl对象中的方法)
long end=System.currentTimeMillis();
log.info("method end {}",end);
System.out.println("SysLogAspect.Around.after");
//将用户的正常的行为信息写入到数据库
saveUserLog(jp,(end-start));
return result;
}catch(Throwable e) {
log.error("method error {},error msg is {}",
System.currentTimeMillis(),e.getMessage());
throw e;
}
}
@Autowired
private SysLogService sysLogService;
private void saveUserLog(ProceedingJoinPoint jp,long time)throws Exception {
//1.获取用户行为日志信息
//获取目标对象(要执行的那个目标业务对象)类型
Class<?> targetCls=jp.getTarget().getClass();
//获取方法签名对象(此对象中封装了要执行的目标方法信息)
MethodSignature ms=(MethodSignature) jp.getSignature();
//获取目标方法对象,基于此对象获取方法上的RequiredLog注解,进而取到操作名
Method targetMethod=targetCls.getMethod(ms.getName(),ms.getParameterTypes());
RequiredLog required=targetMethod.getAnnotation(RequiredLog.class);
//获取操作名
String operation="operation";
if(required!=null) {//注解方式的切入点无须做此判断
operation=required.value();
}
//获取目标方法类全名
String targetMethodName=targetCls.getName()+"."+ms.getName();
//2.构建用户行为日志对象封装用户行为信息
SysLog log=new SysLog();
log.setIp(IPUtils.getIpAddr());
log.setUsername("liuqing");//将来的登陆用户,现在可以先写个假数据
log.setOperation(operation);//不知道该写什么
log.setMethod(targetMethodName);//类全名.方法名
log.setParams(Arrays.toString(jp.getArgs()));
log.setTime(time);
log.setCreatedTime(new java.util.Date());
//3.用户行为日志写入到数据库
sysLogService.saveObject(log);
}
}
MethodSignature 方法签名获取对象的两种方式
- Method targetMethod=methodSignature .getMethod(),在jdk代理中获取的是目标对象所实现的接口中的方法对象,在CGLib中获取的就是目标对象中的方法对象
- Method targetMethod = targetCls.getMethod(ms.getName(), ms.getParameterTypes()),这种方式无论在那种代理中都是获取目标对象中的方法对象。
事务的隔离级别(我还不是很理解)视频在七月22日
查询或者是统计表中数据的时候不允许别的事务对表中数据进行插入删除,即加锁
@Transactional 常用属性应用说明:
**timeout:**事务的超时时间,默认值为-1,表示没有超时显示。如果配置了具体时间,则超过该时间限制但事务还没有完成,则自动回滚事务。这个时间的记录方式是在事务开启以后到sql语句执行之前。
**read-only:**指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true。对添加,修改,删除业务read-only的值应该为false。
**rollback-for:**用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for: 抛出no-rollback-for 指定的异常类型,不回滚事务。
**isolation:**事务的隔离级别,默认值采用 DEFAULT。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不希望出现这些现象可考虑修改事务的隔离级别(但隔离级别越高并发就会越小,性能就会越差)
Spring 业务的异步实现
实现过程:
1.在基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,Spring Boot版的项目中,将@EnableAsync注解应用到启动类上
2.在需要异步执行的业务方法上,使用@Async方法进行异步声明。
@Async:
使用此注解描述的方法会运行在由spring框架提供的一个线程中。
@EnableAsync:
此注解表示启动spring框架中的异步操作(底层会创建一个线程池)
举例:当我对用户操作添加了一个日志记录切面后,在切面类中有记录日志的方法;那么程序的执行过程就会是:当我调用用户操作的方法时,就会进入切面类中的通知方法,在通知方法中会调用日志记录的方法,然后再返回给用户,但是这样是比较耽误时间的,所以可以把记录日志的方法放在一个新的线程中异步实现。
说明:
对于@Async注解默认会基于ThreadPoolTaskExecutor对象获取工作线程,然后调用由@Async描述的方法,让方法运行于一个工作线程,以实现异步操作。但是假如系统中的默认拒绝处理策略,任务执行过程的异常处理不能满足我们自身业务需求的话,我可以对异步线程池进行自定义.(SpringBoot中默认的异步配置可以参考自动配置对象TaskExecutionAutoConfiguration).
相关配置
task: execution: pool: core-size: 10 #核心线程数,当池中线程数没达到core-size时,每来一个请求都创建一个新的线程
queue-capacity: 256 #队列容量,当核心线程都在忙,再来新的任务,会将任务放到队列
max-size: 128 #当核心线程都在忙,队列也满了,再来新的任务,此时会创建新的线程,直到达到maxSize
keep-alive: 60 #当任务高峰过后,有些线程会空闲下来,这空闲现线程达到一定的时间会被释放。
allow-core-thread-timeout: false
thread-name-prefix: db-service-task-
Spring 中事务传播特性
其实就是一个业务执行时调用了其他的业务方法,这样如果上一个业务事务的传播方式是默认的,事务就是被传播到被调用的方法上
事务传播(Propagation)特性指”不同业务(service)对象”中的事务方法之间相互调用时,事务的传播方式
如图所示:
其中,常用事务传播方式如下:
@Transactional(propagation=Propagation.REQUIRED)
如果没有事务创建新事务, 如果当前有事务参与当前事务(内部事务被丢弃), Spring 默认的事务传播行为是PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:
Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中
@Transactional(propagation=Propagation.REQUIRES_NEW)。
必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务。
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void saveObject(SysLog entity) {
sysLogDao.insertObject(entity);
}
当有一个业务对象调用如上业务方法时,此方法会始终运行在一个新的事务中。
举例说明:
当我在一个方法上同时添加了日志记录切面和事务切面,默认是事务切面优先级高,如果我设置事务切面readOnly,这样会出错,因为在执行日志记录操作时,会向数据库中添加数据。所以我们可以在记录日志的方法类中添加@Transactional(propagation = Propagation.REQUIRES_NEW)注解,这样原来的事务就会被挂起(暂停执行),等新的内部事务执行结束后再被唤起。
spring中缓存的配置
- 启动类上添加@EnableCaching注解
- 业务方法上应用缓存配置@Cacheable(value = “menuCache”)。在需要进行缓存的业务方法上通过@Cacheable注解对方法进行相关描述.表示方法的返回值要存储到Cache中,其中,value属性的值表示要使用的缓存对象,名字自己指定,其中底层为一个map对象,当向cache中添加数据时,key默认为方法实际参数的组合。
- 在相关模块更新时,清除指定缓存数据,在方法上加上注解: @CacheEvict(value=“menuCache”,allEntries=true)
shiro安全框架详细介绍
shiro框架配置和认证的使用
这里的用户认证方式有两种:第一种是当框架校验发现用户登录成功后,会把用户信息存储到session中,下次用户访问的时候,发现session中有值,就会允许访问;第二种是通过配置把session中的值取出来放到一个cookie中,然后设置他的最大保存时间,并把这个cookie返回到浏览器,这样的好处就是即使浏览器关闭了,用户也无需再次登录,这就是后边要说到的 shiro记住我 功能
但是记得添加:filterMap.put(“//“, “user”);user表示可以通过用户端提交的cookie信息进行认证
其实过程也很简单,就是客户端传递过来用户名和密码,给认证管理器对象,然后它内部调用shiroUserRealm中的登录认证方法进行判断,其中的参数就是我们封装后的用户名和密码,在内部进行用户名的比对,而密码的比对交给框架
- 添加依赖shiro-spring
- 创建配置类SpringShiroConfig,并添加方法securityManager(Realm realm)和 public ShiroFilterFactoryBean shiroFilterFactory ()
其实这一步就可以完成基本的过滤,对url的过滤;但是还不能完成认证功能,比如对用户登录的认证; - 在用户接口中创建基于用户名查找用户信息的抽象方法
- 定义ShiroUserRealm类,相当于业务层类,完成认证及授权业务数据的获取及封装
- 在配置类的securityManager(Realm realm)方法中添加参数Realm,并把参数注入给SecurityManager对象。
- 在controller类中接收用户请求数据,并将结果传给shiro的SecurityManager。
- 添加统一异常处理方法
shiro框架的配置类代码
package com.cy.pj.common.config;
import java.util.LinkedHashMap;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** @Configuration 注解描述的类为spring框架中的一个配置类 */
@Configuration
public class SpringShiroConfig {
/**
* SecurityManager 对象shiro框架的核心。
* @Bean 通常会配置@Configuration注解进行使用,其它特点:
* 1)此注解描述方法会交给spring管理
* 2)@Bean注解没有指定其value属性的值,则bean的名字默认为方法名
* @return
*/
//@Bean(value="sManager")
@Bean
public SecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager sManager=new DefaultWebSecurityManager();
sManager.setRealm(realm);
return sManager;
}
/**
* Spring容器在管理ShiroFilterFactoryBean对象,
* 会基于ShiroFilterFactoryBean对象, 创建过滤器工厂对象(SpringShiroFilter),
* 然后通过过滤器工厂创建过滤器(filter)对象,最后通过Filter对请求数据进行过滤,
* 例如调用securityManager的方法判定此请求是否已经过认证,假如没有经过认证
* 则跳转到登陆页面进行认证即可。
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
ShiroFilterFactoryBean fBean=new ShiroFilterFactoryBean();
fBean.setSecurityManager(securityManager);
//设置需要进行认证的登陆页面
fBean.setLoginUrl("/doLoginUI");
//设置过滤规则(有顺序,允许匿名访问的放在上面)
LinkedHashMap<String,String> filterMap=new LinkedHashMap<>();
filterMap.put("/bower_components/**","anon");//anno为shiro框架定义,会对应一个过滤器对象,这里表示允许匿名访问
filterMap.put("/build/**","anon");
filterMap.put("/dist/**","anon");
filterMap.put("/plugins/**","anon");
filterMap.put("/user/doLogin", "anon");//登陆操作允许匿名访问
filterMap.put("/doLogout", "logout");//logout为登出操作,此操作执行时会进入登陆页面
filterMap.put("/**", "authc");//authc为设置需要认证访问的资源
fBean.setFilterChainDefinitionMap(filterMap);
return fBean;
}
}
ShiroUserRealm类代码如下:
package com.cy.pj.sys.service.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.pojo.SysUser;
@Service
public class ShiroUserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
/**负责完成授权信息的获取和封装*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//....
return null;
}
/**负责完成认证信息的获取和封装*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//1.获取用户提交的认证信息
UsernamePasswordToken upToken=(UsernamePasswordToken)token;
//2.基于用户名查找用户信息
SysUser user=sysUserDao.findUserByUserName(upToken.getUsername());
//3.判定用户是否存在
if(user==null)
throw new UnknownAccountException();
//4.判定用户是否已被禁用(被禁用则不允许登陆)
if(user.getValid()==0)
throw new LockedAccountException();
//5.封装认证信息并返回
ByteSource credentialsSalt=ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info=
new SimpleAuthenticationInfo(
user,//principal 用户身份(传什么,将来取出来就是什么)
user.getPassword(),//hashedCredentials (已加密的密码)
credentialsSalt,//credentialsSalt
this.getName());//realmName
return info;//返回值会传递给SecurityManager,此对象基于认证信息进行认证。
}
/**
* 设置加密算法
*/
// @Override
// public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
// //构建加密匹配器对象
// HashedCredentialsMatcher cMatcher=
// new HashedCredentialsMatcher("MD5");
// //设置加密次数
// cMatcher.setHashIterations(1);
// super.setCredentialsMatcher(cMatcher);
// }
//设置加密对象也可以使用getCredentialsMatcher方法进行封装
@Override
public CredentialsMatcher getCredentialsMatcher() {
//构建加密匹配器对象
HashedCredentialsMatcher cMatcher=
new HashedCredentialsMatcher("MD5");
//设置加密次数
cMatcher.setHashIterations(1);
return cMatcher;
}
}
Controller层代码如下:
@RequestMapping("doLogin")
public JsonResult doLogin(String username,String password){
//1.获取Subject对象
Subject subject=SecurityUtils.getSubject();
//2.通过Subject提交用户信息,交给shiro框架进行认证操作
//2.1对用户进行封装
UsernamePasswordToken token=
new UsernamePasswordToken(
username,//身份信息
password);//凭证信息
//2.2对用户信息进行身份认证
subject.login(token);
//分析:
//1)token会传给shiro的SecurityManager
//2)SecurityManager将token传递给认证管理器
//3)认证管理器会将token传递给realm
return new JsonResult("login ok");
}
总之,流程图如下:
shiro框架授权操作
授权其实很简单,就是在客户端传过来用户的权限名称,然后我通过ShiroUserRealm(用户授权和认证信息的获取和封装类)访问数据库获取用户对应的所有的限名称,并一一进行比对,如果有相同的则有权限,没有相同的则抛出没有权限异常给客户端浏览器
- 在需要进行授权访问的业务层(Service)方法上,添加执行此方法需要的权限标识,参考代码@RequiresPermissions(“sys:user:update”)
- 在ShiroUserReam类中,重写对象realm的doGetAuthorizationInfo方法,并完成用户权限信息的获取以及封装,最后将信息传递给授权管理器完成授权操作。
流程总结:
@RequiresPermissions 这个注解描述的方法为一个切入点方法。此方法在执行之前
需要进行权限检测(负责这个过程的方法是一个通知方法),假如用户权限中包含
@RequiresPermissions 注解value属性指定的值,则授权访问,不包含则抛出异常。
思考:假如你去设计这个切入点对应的通知方法,你会做什么?
1)目标方法执行执行获取方法上的@RequiresPermissions注解,进而取到注解中内容。
2)将注解中内容提交给SecurityManager对象(此对象负责授权操作)
3)SecurityManager会基于realm去查找用户拥有的权限(这部分我们自己实现)。
4)SecurityManager会判断用户拥有权限中是否包含RequiresPermissions注解中的内容
5)SecurityManager基于用户权限进行授权或抛出异常。
shiro实现对权限的缓存
其实很简单,就两步配置,他其实就是把查到的权限信息做了一个缓存,下次数据可以从缓存中取,仅此而已
- 第一步:在SpringShiroConfig中配置缓存Bean对象(Shiro框架提供)。
配置shiro框架的缓存管理器对象(这个对象不是缓存对象,是管理缓存的一个对象)
基于此配置可以在Shiro框架内部初始化一个Cache对象,此Cache对象可以存储用户的权限
信息,当用户访问一个授权才可以访问的方法时,我们需要从数据库获取用户权限信息,
然后还可以将用户的这个权限信息缓存起来,下次需要时从缓存获取即可。
@Bean
public CacheManager shiroCacheManager() {
return new MemoryConstrainedCacheManager();
}
- 第二步:修改securityManager的配置,将缓存对象注入给SecurityManager对象。
Shiro中内置缓存应用实现使用的是SoftHashMap,值对象的引用是**软引用,**方便在jvm内存不足的时候自动释放内存
shiro记住我
其实就是取到session中的值(用户名和密码)放到一个cookie中,然后返回给浏览器,并设置一个保存时长,就实现了记住用户的功能
- 基于前端是否勾选记住我按钮,在controller方法中进行判断,其中isRememberMe是前端传过来的布尔变量值
if(isRememberMe) {
token.setRememberMe(true);
}
- 第二步:在SpringShiroConfig配置类中添加记住我配置
/**
* 配置记住我管理器对象,此对象可以通过cookie对象存储账户信息,并将此信息
* 写到客户端,下次客户端可以访问服务端时,可以携带cookie中的信息进行自动
* 认证。
*/
@Bean
public RememberMeManager rememberMeManager() {
CookieRememberMeManager cManager=
new CookieRememberMeManager();
SimpleCookie cookie=new SimpleCookie("rememberMe");
cookie.setMaxAge(7*24*60*60);//设置cookie在浏览器的最大存活时间
cManager.setCookie(cookie);
return cManager;
}
- 第三步:在SpringShiroConfig中修改securityManager的配置,为
securityManager注入rememberManager对象
sManager.setRememberMeManager(rememberManager);
- 第四步:修改shiro的过滤认证级别,将/=author修改为/=users
filterMap.put("/**", "user");//user表示可以通过用户端提交的cookie信息进行认证
**问:**就是文档上的记住我这个功能,是不是创建一个cookie对象,然后把session中的数据保存到cookie中返回到浏览器
Shiro会话时长配置 官网说明
自动配置 securityManager
添加依赖:
org.apache.shiro
shiro-spring-boot-web-starter
1.5.3
- 使用shiro框架实现认证操作,用户登录成功会将用户信息写入到会话对象session中,其默认时长为30分钟,如果想要修改保存时长
首先在配置类中添加会话管理器
/**
配置会话管理对象(Session管理器),在Shiro框架的应用中,用户登陆成功以后
默认会将用户信息存储到session(服务端的一个对象)。
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sManager=
new DefaultWebSessionManager();
sManager.setGlobalSessionTimeout(60*60*1000);
return sManager;
}
- 在SpringShiroConfig配置类中,对安全管理器 securityManager 增加 sessionManager值的注入
sManager.setSessionManager(sessionManager);
从session中获取登陆用户的方法:
(SysUser)SecurityUtils.getSubject().getPrincipal();
另一种配置shiro框架的方式
- 配置类写法
/** @Configuration 注解描述的类为spring框架中的一个配置类 */
@Configuration
public class SpringShiroConfig {
@Bean
public Realm realm() {
return new ShiroUserRealm();
}
@Bean
protected CacheManager shiroCacheManager() {
return new MemoryConstrainedCacheManager();
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition =
new DefaultShiroFilterChainDefinition();
LinkedHashMap<String,String> filterMap=new LinkedHashMap<>();
filterMap.put("/bower_components/**","anon");//anno为shiro框架定义,会对应一个过滤器对象,这里表示允许匿名访问
filterMap.put("/build/**","anon");
filterMap.put("/dist/**","anon");
filterMap.put("/plugins/**","anon");
filterMap.put("/user/doLogin", "anon");//登陆操作允许匿名访问
//filterMap.put("/doIndexUI", "anon");//首页页面允许匿名访问
filterMap.put("/doLogout", "logout");//logout为登出操作,此操作执行时会进入登陆页面
//filterMap.put("/**", "authc");//authc为设置需要认证访问的资源
filterMap.put("/**", "user");//user表示可以通过用户端提交的cookie信息进行认证
chainDefinition.addPathDefinitions(filterMap);
return chainDefinition;
}
}
**注意:**ShiroUserRealm()类上边不加@Service
2. yml文件添加配置
#shiro
shiro:
loginUrl: /doLoginUI
sessionManager:
globalSessionTimeout: 3600000
rememberMeManager:
cookie:
name: rememberMe
maxAge: 604800
分页插件pageHelper的使用
pageHelper是通过在数据层操作数据库的过程中设置拦截器实现的,我们只需要将数据库中的数据查询出来封装到List集合中,在这之前只需要添加一行代码:
导入的依赖:
com.github.pagehelper
pagehelper-spring-boot-starter
1.3.0
Page<SysLog> page=PageHelper.startPage(pageCurrent, pageSize);
List<SysLog> records=sysLogDao.findPageObjects(username);
这样就会在程序执行sql语句的之前,在拦截器中做点手脚,即帮助我们添加配置信息,包括总页数,总记录数,都会被封装到page中,下面是控制台的输出,我们不难看出是底层帮助我们添加了配置
原映射文件中的sql语句:
<select id="findPageObjects"
resultType="com.cy.pj.sys.pojo.SysLog">
select *
from sys_logs
<if test="username!=null and username!=''">
<where>
username like concat("%",#{username},"%")
</where>
</if>
</select>
控制台输出:
有关服务端和客户端时间日期的转换问题
下面是一个实体类的一个属性,该属性上有两个注解
@DateTimeFormat(pattern = "yyyy/MM/dd")//此注解用于描述属性或set方法,告诉spring mvc 按指定格式接收客户端数据
@JsonFormat(pattern="yyyy/MM/dd",timezone="Asia/Shanghai")// 此注解用于描述属性或get方法,将日期以指定格式显示到客户端
private Date startTime;//java.util.Date
在@JsonFormat()中,pattern:日期格式 timezone:时区
使用下边的api,可以获取所有的市区:
Set list = ZoneId.getAvailableZoneIds();
注解@JsonFormat主要是后台到前台的时间格式的转换
注解@DataFormAT主要是前后到后台的时间格式的转换
但是对于@JsonFormat 注解的作用,在thymeleaf中还有另一种相似的实现:
es6新增语法之${}
这是es6中新增的字符串方法
可以配合反单引号完成拼接字符串的功能,这样就不用使用加号和双引号拼接字符串
1、反单引号怎么打出来?
将输入法调整为英文输入法,单击键盘上数字键1左边的按键。
2、用法
step1: 定义需要拼接进去的字符串变量
step2: 将字符串变量用${}包起来,再写到需要拼接的地方
3、示例代码:
let a=‘Karry Wang’;
let str=I love ${a}, because he is handsome.
;
将一个普通的maven项目配置成一个springboot项目
1.复制pom文件中的parent节点
2.添加启动类
3.添加aplication.properties或者application.yml配置文件(properties文件的优先级要高于yml文件,而且两者配 置的书写格式也不同)
4.在resource包下添加static和templates包