Guns框架学习记录-6-数据范围控制+缓存管理

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(最少访问次数)。

猜你喜欢

转载自blog.csdn.net/Nerver_77/article/details/81564266