11.数据范围控制
同一角色用户会受到数据范围的限制,显示的数据内容会有所不同。
例如:在Guns框架中,同角色用户subject所属部门不同,该用户所能访问到的数据也不同。
在权限管理的基础上添加数据范围控制:Guns中通过用户subject的部门字段(可自定义)进行数据范围控制。
数据范围控制原理:对原有SQL查询语句进行包装后进行含DataScope的SQL查询。
例:查询用户信息列表
原语句: select id, account, name, birthday, sex, email, avatar, phone, roleid, deptid, status, createtime, version from user where status != 3
被包装后: select * from (select id, account, name, birthday, sex, email, avatar, phone, roleid, deptid, status, createtime, version from user where status != 3) temp_data_scope where temp_data_scope.deptid in (25,26,27,24)
看SQL语句本身,包装即为:select * from (sql) temp_data_scope where temp_data_scope.deptid in (25,26,27,24)。
意思就是包装后进行了嵌套查询,最外层又添加了查询条件,并限定了数据范围(where后部分)。
那么开展下一个问题,如何添加数据范围控制?
1. 定义DataScope
public class DataScope {
/**
* 限制范围的字段名称
*/
private String scopeName = "deptid";
/**
* 具体的数据范围
*/
private List<Integer> deptIds;
//getter、setter方法
}
这里需要注意的是,限制范围的字段名称需要和数据库字段一致,数据范围是一个列表(总公司下数据范围应该为所有部门)。
如何获取具体的数据范围?
答案是:通过shiro权限管理工具类:ShiroKit.getDeptDataScope()来进行具体数据范围的获取。
/**
* 获取当前用户的部门数据范围的集合
*/
public static List<Integer> getDeptDataScope() {
//获取当前用户的部门ID
Integer deptId = getUser().getDeptId();
//根据当前获取的用户部门ID进行子部门查询,返回当前部门的所有子部门ID
List<Integer> subDeptIds = ConstantFactory.me().getSubDeptId(deptId);
//将自身部门ID加入 这样具体的部门范围就构成了
subDeptIds.add(deptId);
return subDeptIds;
}
当自定义数据范围字段:dataScope.setScopeName("xxx");的时候对应的具体数据范围获取方法应该同步更改。
2. 在需要进行数据范围过滤的查询方法上,在方法参数上,增加DataScope对象
坐标UserMgrDao.java
/**
* 根据条件查询用户列表
*/
List<Map<String, Object>> selectUsers(@Param("dataScope") DataScope dataScope, @Param("name") String name, ...)
DataScope dataScope 添加到了方法参数上,表明当前的方法要进行数据范围控制。
对应的Controller:
//获取具体数据范围
DataScope dataScope = new DataScope(ShiroKit.getDeptDataScope());
//执行查询
List<Map<String, Object>> users = managerDao.selectUsers(dataScope, name, beginTime, endTime, deptid);
3. 配置Mybatis拦截器,对所需执行的SQL语句进行拦截处理
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataScopeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//通用配置省略
//查找参数中包含DataScope类型的参数
DataScope dataScope = findDataScopeObject(parameterObject);
//如果没有DataScope对象,直接放行。
if (dataScope == null) {
return invocation.proceed();
} else {
//获取DataScope的范围字段。
String scopeName = dataScope.getScopeName();
//获取DataScope的具体数据范围
List<Integer> deptIds = dataScope.getDeptIds();
//集合工具类通过conjunction为分隔符将集合转换为字符串
String join = CollectionKit.join(deptIds, ",");
//拼接新的SQL语句
originalSql = "select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + scopeName + " in (" + join + ")";
metaStatementHandler.setValue("delegate.boundSql.sql", originalSql);
//放行
return invocation.proceed();
}
}
/**
* 查找参数是否包括DataScope对象
*/
public DataScope findDataScopeObject(Object parameterObj) {
if (parameterObj instanceof DataScope) {
return (DataScope) parameterObj;
} else if (parameterObj instanceof Map) {
for (Object val : ((Map<?, ?>) parameterObj).values()) {
if (val instanceof DataScope) {
return (DataScope) val;
}
}
}
return null;
}
}
拦截策略:判断参数列表中是否含有DataScope对象,如果没有就拦截通过。反之,从DataScope中获取具体数据范围,重新拼装SQL语句,之后放行。
通过数据范围控制结合权限管理对数据展示进行了进一步的控制,有利于数据的合理管理和展示。
12.缓存管理
对经常访问的数据信息进行存储,方面再次访问,减轻数据检索压力,提高系统性能。
关于缓存管理需要注意的地方:
1. Spring的缓存技术
2. 常用缓存的配置
**1.**Spring中的缓存技术
Spring 3.1引入了基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EhCache或Redis),而是一个对缓存使用的抽象。
通过在既有代码中添加少量它定义的各种annotation,即能够达到缓存方法的返回对象的效果。
特点:
通过少量的配置annotation注释即可使得既有代码支持缓存
支持开箱即用Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存
支持Spring Express Language,能使用对象的任何属性或者方法来定义缓存的key和condition
支持AspectJ,并通过其实现任何方法的缓存支持
支持自定义key和自定义缓存管理者,具有相当的灵活性和扩展性
Spring提供了4个注解来声明缓存规则:以下注解均是添加到方法上。
1.@Cacheable表明在调用方法之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,则会返回缓存的值,否则执行该方法,并将返回值放到缓存中(query)
参数:
value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,
如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值。
condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,
如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存。
unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
2.@CachePut表明在方法调用前不会检查缓存,方法始终都会被调用,调用之后把结果放到缓存中(insert)
3.@CacheEvict表明spring会清除一个或者多个缓存(update或delete)
4.@Caching分组的注解,可以同时应用多个其他缓存注解,可以相同类型或者不同类型
使用注解方式实现缓存管理实质就是Spring AOP的动态代理技术,关于AOP动态代理,会在后续事务管理后做学习记录。
**2.**EhCache的配置
需要提供一个CacheManager接口的实现,这个接口告诉spring有哪些cache实例,spring会根据cache的名字查找cache的实例。
@Configuration
@EnableCaching //声明开启缓存
public class EhCacheConfig {
/**
* EhCache的配置
*/
@Bean
public EhCacheCacheManager cacheManager(CacheManager cacheManager) {
return new EhCacheCacheManager(cacheManager);
}
/**
* EhCache的配置
*/
@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
//读取XML中的缓存相关配置
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
当声明了@EnableCaching后,springboot会根据以下顺序去检索缓存提供者。
Spring Boot根据实现自动配置合适的CacheManager,只要缓存支持通过@EnableCaching注释启用即可。
* Generic
* JCache (JSR-107)
* EhCache 2.x
* Hazelcast
* Infinispan
* Redis
* Guava
* Simple
配置文件:ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘缓存地址-->
<diskStore path="java.io.tmpdir/ehcache"/>
<defaultCache
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600" />
<!-- 自定义缓存策略 通过name进行缓存策略的区分 Guns这里采用了CONSTANT为缓存策略命名-->
<cache
name="CONSTANT"
eternal="false"
maxElementsInMemory="100"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
配置参数:以下资料来源网络
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。